How can I exclude multiple folders using Get-ChildItem -exclude?
ListPowershellDirectoryList Problem Overview
I need to generate a configuration file for our Pro/Engineer CAD system. I need a recursive list of the folders from a particular drive on our server. However I need to EXCLUDE any folder with 'ARCHIVE' in it including the various different cases.
I've written the following which works except it doesn't exclude the folders !!
$folder = "T:\Drawings\Design\*"
$raw_txt = "T:\Design Projects\Design_Admin\PowerShell\raw.txt"
$search_pro = "T:\Design Projects\Design_Admin\PowerShell\search.pro"
$archive = *archive*,*Archive*,*ARCHIVE*
Get-ChildItem -Path $folder -Exclude $archive -Recurse | where {$_.Attributes -match 'Directory'} | ForEach-Object {$_.FullName} > $search_pro
List Solutions
Solution 1 - List
My KISS approach to skip some folders is chaining Get-ChildItem
calls. This excludes root level folders but not deeper level folders if that is what you want.
Get-ChildItem -Exclude folder1,folder2 | Get-ChildItem -Recurse | ...
- Start excluding folders you don't want
- Then do the recursive search with non desired folders excluded.
What I like from this approach is that it is simple and easy to remember. If you don't want to mix folders and files in the first search a filter would be needed.
Solution 2 - List
I'd do it like this:
Get-ChildItem -Path $folder -r |
? { $_.PsIsContainer -and $_.FullName -notmatch 'archive' }
Note that -notmatch
accepts a Regular Expression:
https://docs.microsoft.com/powershell/module/microsoft.powershell.core/where-object#parameters
Solution 3 - List
I apologize if this answer seems like duplication of previous answers. I just wanted to show an updated (tested through POSH 5.0) way of solving this. The previous answers were pre-3.0 and not as efficient as modern solutions.
The documentation isn't clear on this, but Get-ChildItem -Recurse -Exclude
only matches exclusion on the leaf (Split-Path $_.FullName -Leaf
), not the parent path (Split-Path $_.FullName -Parent
). Matching the exclusion will just remove the item with the matching leaf; Get-ChildItem
will still recurse into that leaf.
In POSH 1.0 or 2.0
Get-ChildItem -Path $folder -Recurse |
? { $_.PsIsContainer -and $_.FullName -inotmatch 'archive' }
Note: Same answer as @CB.
In POSH 3.0+
Get-ChildItem -Path $folder -Directory -Recurse |
? { $_.FullName -inotmatch 'archive' }
Note: Updated answer from @CB.
Multiple Excludes
This specifically targets directories while excluding leafs with the Exclude
parameter, and parents with the ilike
(case-insensitive like) comparison:
#Requires -Version 3.0
[string[]]$Paths = @('C:\Temp', 'D:\Temp')
[string[]]$Excludes = @('*archive*', '*Archive*', '*ARCHIVE*', '*archival*')
$files = Get-ChildItem $Paths -Directory -Recurse -Exclude $Excludes | %{
$allowed = $true
foreach ($exclude in $Excludes) {
if ((Split-Path $_.FullName -Parent) -ilike $exclude) {
$allowed = $false
break
}
}
if ($allowed) {
$_
}
}
Note: If you want your $Excludes
to be case-sensitive, there are two steps:
- Remove the
Exclude
parameter fromGet-ChildItem
. - Change the first
if
condition to:if ($_.FullName -clike $exclude) {
Note: This code has redundancy that I would never implement in production. You should simplify this quite a bit to fit your exact needs. It serves well as a verbose example.
Solution 4 - List
The exclusion pattern should be case-insensitive, so you shouldn't have to specify every case for the exclusion.
That said, the -Exclude
parameter accepts an array of strings, so as long as you define $archive
as such, you should be set.
$archive = ("*archive*","*Archive*","*ARCHIVE*");
You also should drop the trailing asterisk from $folder
- since you're specifying -recurse
, you should only need to give the top-level folder.
$folder = "T:\Drawings\Design\"
Fully revised script. This also changes how you detect whether you've found a directory, and skips the Foreach-Object
because you can just pull the property directly & dump it all to the file.
$folder = "T:\Drawings\Design\";
$raw_txt = "T:\Design Projects\Design_Admin\PowerShell\raw.txt";
$search_pro = "T:\Design Projects\Design_Admin\PowerShell\search.pro";
$archive = ("*archive*","*Archive*","*ARCHIVE*");
Get-ChildItem -Path $folder -Exclude $archive -Recurse | where {$_.PSIsContainer} | select-Object -expandproperty FullName |out-file $search_pro
Solution 5 - List
I know this is quite old - but searching for an easy solution, I stumbled over this thread... If I got the question right, you were looking for a way to list more than one directory using Get-ChildItem. There seems to be a much easier way using powershell 5.0 - example
Get-ChildItem -Path D:\ -Directory -Name -Exclude tmp,music
chaos
docs
downloads
games
pics
videos
Without the -Exclude clause, tmp and music would still be in that list. If you don't use -Name the -Exclude clause won't work, because of the detailed output of Get-ChildItem. Hope this helps some people that are looking for an easy way to list all directory names without certain ones.
Solution 6 - List
You can exclude like this, the regex 'or' symbol, assuming a file you want doesn't have the same name as a folder you're excluding.
$exclude = 'dir1|dir2|dir3'
ls -r | where { $_.fullname -notmatch $exclude }
ls -r -dir | where fullname -notmatch 'dir1|dir2|dir3'
Solution 7 - List
VertigoRay, in his answer, explained that -Exclude works only at the leaf level of a path (for a file the filename with path stripped out; for a sub-directory the directory name with path stripped out). So it looks like -Exclude cannot be used to specify a directory (eg "bin") and exclude all the files and sub-directories within that directory.
Here's a function to exclude files and sub-directories of one or more directories (I know this is not directly answering the question but I thought it might be useful in getting around the limitations of -Exclude):
$rootFolderPath = 'C:\Temp\Test'
$excludeDirectories = ("bin", "obj");
function Exclude-Directories
{
process
{
$allowThrough = $true
foreach ($directoryToExclude in $excludeDirectories)
{
$directoryText = "*\" + $directoryToExclude
$childText = "*\" + $directoryToExclude + "\*"
if (($_.FullName -Like $directoryText -And $_.PsIsContainer) `
-Or $_.FullName -Like $childText)
{
$allowThrough = $false
break
}
}
if ($allowThrough)
{
return $_
}
}
}
Clear-Host
Get-ChildItem $rootFolderPath -Recurse `
| Exclude-Directories
For a directory tree:
C:\Temp\Test\
|
├╴SomeFolder\
| |
| └╴bin (file without extension)
|
└╴MyApplication\
|
├╴BinFile.txt
├╴FileA.txt
├╴FileB.txt
|
└╴bin\
|
└╴Debug\
|
└╴SomeFile.txt
The result is:
C:\Temp\Test\
|
├╴SomeFolder\
| |
| └╴bin (file without extension)
|
└╴MyApplication\
|
├╴BinFile.txt
├╴FileA.txt
└╴FileB.txt
It excludes the bin\ sub-folder and all its contents but does not exclude files Bin.txt or bin (file named "bin" without an extension).
Solution 8 - List
The simplest short form to me is something like:
#find web forms in my project except in compilation directories
(gci -recurse -path *.aspx,*.ascx).fullname -inotmatch '\\obj\\|\\bin\\'
And if you need more complex logic then use a filter:
filter Filter-DirectoryBySomeLogic{
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
$fsObject,
[switch]$exclude
)
if($fsObject -is [System.IO.DirectoryInfo])
{
$additional_logic = $true ### replace additional logic here
if($additional_logic){
if(!$exclude){ return $fsObject }
}
elseif($exclude){ return $fsObject }
}
}
gci -Directory -Recurse | Filter-DirectoryBySomeLogic | ....
Solution 9 - List
Based on @NN_ comment on @Guillem answer, I came up with the below code. This allows you to exclude folders and files:
Get-ChildItem -Exclude 'folder-to-exclude','second-folder-exclude' |
foreach {
Get-ChildItem -Path $_ -Exclude 'files-to-exclude','*.zip','*.mdmp','*.out*','*.log' -Recurse |
Select-String -Pattern 'string-to-look-for' -List
}
Solution 10 - List
You could also do this in a single statement:
$j = "Somepath"
$files = Get-ChildItem -Path $j -Include '*.xlsx','*.zip' -Recurse -ErrorAction SilentlyContinue –File | ? {$_.Directory -notlike "$j\donotwantfoldername"}
Solution 11 - List
This is how I did it:
Get-ChildItem -Recurse -Name | ? {$_ -notmatch 'node_modules' }
This lists the full path of every file recursively that does NOT contains node_modules in its path. You should obviously change node_modules with any string you want to filter
Solution 12 - List
#For brevity, I didn't define a function.
#Place the directories you want to exclude in this array.
#Case insensitive and exact match. So 'archive' and
#'ArcHive' will match but 'BuildArchive' will not.
$noDirs = @('archive')
#Build a regex using array of excludes
$excRgx = '^{0}$' -f ($noDirs -join ('$|^'))
#Rather than use the gci -Recurse option, use a more
#performant approach by not processing the match(s) as
#soon as they are located.
$cmd = {
Param([string]$Path)
Get-ChildItem $Path -Directory |
ForEach-Object {
if ($_.Name -inotmatch $excRgx) {
#Recurse back into the scriptblock
Invoke-Command $cmd -ArgumentList $_.FullName;
#If you want all directory info change to return $_
return $_.FullName
}
}
}
#In this example, start with the current directory
$searchPath = .
#Start the Recursion
Invoke-Command $cmd -ArgumentList $searchPath
Solution 13 - List
I wanted a solution that didn't involve looping over every single item and doing if
s. Here's a solution that is just a simple recursive function over Get-ChildItem
. We just loop and recurse over directories.
function Get-RecurseItem {
[Cmdletbinding()]
param (
[Parameter(ValueFromPipeline=$true)][string]$Path,
[string[]]$Exclude = @(),
[string]$Include = '*'
)
Get-ChildItem -Path (Join-Path $Path '*') -Exclude $Exclude -Directory | ForEach-Object {
@(Get-ChildItem -Path (Join-Path $_ '*') -Include $Include -Exclude $Exclude -File) + ``
@(Get-RecurseItem -Path $_ -Include $Include -Exclude $Exclude)
}
}
Solution 14 - List
$CurrentPath = (Get-Location -PSProvider FileSystem).ProviderPath # Or your favorite path
$IncludeNames = "okFolder1", "okFolder2" # Items names to search
$ExcludeNames = "koFolder1", "koFolder2" # Items names not to search
$depth = 3 # Max level of depth to search
$FoldersToRemove = Get-ChildItem .\ -include $IncludeNames -Recurse -Depth $depth
-Attributes D # If you want only directories, change it as you desire
| ? { $_.fullname -inotmatch ($ExcludeNames -join '|') } #case insensitive or use -cnotmatch for case sensitive comparison
| foreach {$_.fullname} # If you want to project only full path information of matches
Solution 15 - List
Here is another method using a remote server. The task here is to get a list of folders but exclude a number of well known folders on the remote server's C: drive. The final variable $AllFolders contains the result.
$Server = "<ServerName>"
$TopFolder = "\\$Server\C$"
$Exclude = @("*Inetpub*")
$Exclude += "*Program Files*"
$Exclude += "*Program Files (x86)*"
$Exclude += "*Windows*"
$ServerFolders = Get-ChildItem -Path $TopFolder -Exclude $Exclude | where {$_.PSIsContainer}
ForEach ($ServerFolder in $ServerFolders)
{
$CurrentFolders = Get-ChildItem -path $ServerFolder -recurse | Where-Object { $_.PSIsContainer }
$AllFolders = $AllFolders + $CurrentFolders
}
Solution 16 - List
Simpliest form in my opinion. Use -NotMatch on Fullname. Yes, it needs a recent version of PowerShell because I use -Directory.
$folder = "T:\Drawings\Design\*"
$search_pro = "T:\Design Projects\Design_Admin\PowerShell\search.pro"
$archive = 'archive'
Get-ChildItem -Path $folder -Directory | Where-Object Fullname -NotMatch $archive | Select-Object Fullname | Out-File $search_pro
Solution 17 - List
I needed to exclude specific paths, not just directories of the same name anywhere in the tree, so I built on Jason Brower's answer to match directory paths instead of their names.
Solutions like Get-Childitem filespec -Recurse | Where-Object {$_ -excludecondition}
do work, but they unnecessarily look into the excluded folders before dismissing them which can get expensive. (With pipes: "Filter left, process right")
$strSearchPath = 'D:\Project'
# Files to search for
$arFilePatterns = @(
'*.ps?',
'*.cmd'
)
# Directories to skip
# Example: you got 'Archive', 'Archive.old', 'Archive.bak' and want to include only 'Archive' in the search
# (think) exact matches
$arSkipDirs = @(
'D:\Project\Archive.old',
'D:\Project\Archive.bak'
)
# (think) wildcard to the right
<#
$arSkipDirs = @(
'D:\Project\Archive.'
)
#>
Function ListDirsSkipSome ($strPath, $strExcludeRegEx) {
Get-ChildItem -Path $strPath -Directory |
ForEach-Object {
if ($_.FullName -inotmatch $strExcludeRegEx) {
# recurse down the tree
ListDirsSkipSome $_.FullName $strExcludeRegEx
return $_.FullName
}
}
}
#Build a regex using array of excludes
# exact matches
$strRegEx = '^{0}$' -f (($arSkipDirs | ForEach-Object { [regex]::Escape($_) }) -join ('$|^'))
# wildcards to the right
#$strRegEx = '^{0}' -f (($arSkipDirs | ForEach-Object { [regex]::Escape($_) }) -join ('|^'))
# include root of search path
$arSearchDirs = @($strSearchPath)
# add list of directories excluding some
$arSearchDirs += ListDirsSkipSome $strSearchPath $strRegEx
# save current directory
$strPWD = (Get-Location).Path
# find files in listed dirs
# set type in case there is only 1 result
[array]$arSearchResult = $arSearchDirs |
ForEach-Object {
# dive into each directory
Set-Location -Path $_
# list files matching patterns
(Get-ChildItem -File -Path $arFilePatterns).FullName
}
# return to previous directory
Set-Location -Path $strPWD
$arSearchResult
Solution 18 - List
may be in your case you could reach this with the following:
mv excluded_dir ..\
ls -R
mv ..\excluded_dir .