77

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   
Tunaki
  • 132,869
  • 46
  • 340
  • 423
peterjfrancis
  • 781
  • 1
  • 5
  • 4
  • peterjfrancis, according to one of your comments, the answer provided by @CB was the correct method for excluding paths with folders containing 'ARCHIVE' in the name. CB's answer should be marked as correct. – jaylweb Jul 08 '16 at 15:16

18 Answers18

74

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.

guillem
  • 2,768
  • 2
  • 30
  • 44
  • I ended with double foreach to process files and folders Get-ChildItem -Exclude folder1,folder2 | foreach { Get-ChildItem -Path $_ -Include *.a -Exclude *.b -Recurse | foreach { echo $_ } } . Is there anything better ? – NN_ May 20 '19 at 18:17
  • This IS the correct answer to the question and should be accepted. The only one that actually prevents Get-ChildItem from making expensive and long recursion. Other answers allow the unnecessary recursion to occur and filter after the fact. Thanks! – TugboatCaptain Nov 30 '21 at 14:55
  • 2
    If you add a `-Filter` on the second `Get-ChildItem` it won't filter files that were at the root directory of the first `Get-ChildItem` call and you end up with unexpected files – Silex Feb 23 '22 at 15:40
  • 2
    @Silex good point `-Filter` will not work with this approach – guillem Feb 23 '22 at 18:38
54

I'd do it like this:

Get-ChildItem -Path $folder -r  | 
? { $_.PsIsContainer -and $_.FullName -notmatch 'archive' }

Note that -notmatch accepts a Regular Expression:

https://learn.microsoft.com/powershell/module/microsoft.powershell.core/where-object#parameters

Zombo
  • 1
  • 62
  • 391
  • 407
CB.
  • 58,865
  • 9
  • 159
  • 159
  • This works, but isn't it better to do the excluding based on name in `gci` (filter left, format right)? – alroc Mar 08 '13 at 13:38
  • 1
    in terms of performance I think so, but IMO I've more control using a regex in what to exclude.. but it's just a personal preference.. – CB. Mar 08 '13 at 13:45
  • 8
    Maybe this is a bug. It seems the option '-Exclude' only apply to a folder or file, not the path. – Bohr Jul 30 '13 at 08:55
  • The best solution for me. I like the shorter version of: `gci -r | ? fullname -notm 'archive'` – Rasmond Dec 13 '20 at 20:24
  • In this answer, is ? equivalent to Where-Object¿ ... and if so, is there a reason to not use Where-Object¿ – George 2.0 Hope Feb 24 '21 at 10:29
  • 1
    @George2.0Hope if you type `alias` in PowerShell, you'll get a printout of all its current alias, which shows `? -> Where-Object`. So, yes, `?` is an alia for `Where-Object`. Not sure why they used the alia and not the full name, given they used `Get-ChildItem` instead of `gci`. – Jonathan E. Landrum Oct 19 '21 at 12:42
  • I feel like at least ten times I've done this stupid circle: *I need to exclude some directories. -Exclude isn't quite doing what I want. The docs aren't exactly clear. General consensus is fuck it, use Where-Object and filter on FullName.* – Nathan Chappell Jan 19 '23 at 08:13
  • This is very inefficient. It only filters out the directories AFTER traversing them. – Francisco Aguilera Apr 02 '23 at 21:17
52

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:

  1. Remove the Exclude parameter from Get-ChildItem.
  2. 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.

VertigoRay
  • 5,935
  • 6
  • 39
  • 48
  • 2
    I don't know why I got down-voted. Maybe because the first portion is the same answer as @CB? [I tried to just edit his answer](https://stackoverflow.com/review/suggested-edits/11627206), but the edit was rejected with the following reason: "This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer." – VertigoRay Mar 15 '16 at 18:41
  • I believe the answer provided by @CB will work through all versions of PowerShell and answered the OPs question. The POSH 3.0+ example would have been better suited as a brief comment on CB's post. – jaylweb Jul 08 '16 at 15:33
  • 1
    @jaylweb I never said it wouldn't work ... I just said CB's answer wasn't as efficient in POSH 3.0+. I also wanted to add the bit about multiple excludes, even though the OP's multiple excludes where essentially the same `ilike` string, but maybe he didn't want to exclude "ArcHive", Thus he should be using `clike` ... I've added a note. All of those changes would have made for a long, poorly formatted comment. As I stated already, it didn't get approved as an edit ... so a new answer was born. Thanks for the feedback! – VertigoRay Jul 08 '16 at 19:26
  • 8
    Worth an upvote as the only answer that explains what's going on: That Exclude only operates on the leaf level of a path (ie the last filename or directory name in the path) and not on the path as a whole. – Simon Elms Nov 02 '16 at 20:32
  • I'm not sure that using "break" in a pipeline achieves what you want (to go to next item in pipeline). Instead, I think you need to use "return" – Peter McEvoy Aug 28 '17 at 10:04
  • 1
    @PeterMcEvoy I think you misread the code. The `break` is breaking: `foreach ($exclude in $Excludes) {` not the pipeline. After the foreach loop, I output the current pipeline item; `if ($allowed) ...`.The results of this code have been thoroughly tested and I'm using in production. However, if you happen to find an edge case that breaks it, please let me know. – VertigoRay Aug 28 '17 at 17:45
15

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 
alroc
  • 27,574
  • 6
  • 51
  • 97
  • 2
    `$archive = ("*archive*","*Archive*","*ARCHIVE*")` and `$archive = "*archive*","*Archive*","*ARCHIVE*"` and `$archive = @("*archive*","*Archive*","*ARCHIVE*")` are equivalent. – CB. Mar 08 '13 at 13:38
  • True. But the OP didn't have `*archive*` (in any incarnation) in quotes, so they weren't being treated properly as strings. I like wrapping my arrays in `()` for clarity. – alroc Mar 08 '13 at 13:40
  • Thanks alroc , that was fast ! Your code works fine , except its missing folders with ....\_Archive\.... in them. I've added _Archive to the $archive array but its still not excluding them – peterjfrancis Mar 08 '13 at 14:04
  • I can't reproduce that. Matching `*_something*` in an exclude pattern works for me. What *exactly* does `$archive` look like? – alroc Mar 08 '13 at 14:10
  • $archive = ("*archive*","*Archive*","*ARCHIVE*","*_Archive*"); I've just noticed its also missing Archive as well Here is an example of two of the folders its missing T:\Drawings\Design\FTA0001\Archive\sw T:\Drawings\Design\980-CAS11535\_Archive\Iss.3 – peterjfrancis Mar 08 '13 at 14:32
  • What version of PowerShell are you running? [This](http://www.vistax64.com/powershell/127913-exclude-directories-get-childitem.html) reads like `-exclude` is broken on 2.0 & older; I tested with 3.0. – alroc Mar 08 '13 at 14:57
  • I've only got 1.0 (I can't install 2 or higher on XP x64) – peterjfrancis Mar 08 '13 at 15:17
  • 1
    Then I think you need to use @C.B.'s answer. – alroc Mar 08 '13 at 15:52
  • 3
    Finally solved it ! `Get-ChildItem -Path $folder -Recurse | ? { $_.psiscontainer -and $_.fullname -notmatch 'archive' } | select-Object -expandproperty FullName | out-file $search_pro` That seems to work Thanks for your help @alroc & @C.B. – peterjfrancis Mar 08 '13 at 16:15
  • 1
    You can use the `-Directory` parameter to get directories instead of checking if your resulting object is a container. – jrsconfitto Mar 19 '14 at 13:45
  • 11
    [**`-exclude` only applies to the name of an item**](http://superuser.com/a/663229/180163) so this will *not* work. As suggested in the accepted answer, you'll need to pipe the results into `Where` (aliased `?`) in order to filter on `FullName`. @alroc - it might be a good day to earn the [disciplined](http://stackoverflow.com/help/badges/37/disciplined) badge. – KyleMit Apr 17 '15 at 13:15
  • Could you explain the `*` in the string array? Is that a wildcard? – Kellen Stuart Oct 17 '16 at 20:41
  • @KolobCanyon yes, it's to match on any string containing the word "archive". – alroc Oct 18 '16 at 01:38
  • This doesn't work. – js2010 Nov 02 '22 at 18:44
10

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.

wonea
  • 4,783
  • 17
  • 86
  • 139
lil
  • 109
  • 1
  • 2
  • As the parameter is Name and not FullName, this doesn't work on recursive case where you need to exclude something else than the leaf name. – Timo Riikonen Mar 08 '21 at 13:48
5

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

Nahuel Taibo
  • 459
  • 6
  • 3
4

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'
js2010
  • 23,033
  • 6
  • 64
  • 66
3

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).

Simon Elms
  • 17,832
  • 21
  • 87
  • 103
  • 1
    The problem with this is that Get-ChildItem has already spent XX amount of time recursing the directories, and only after it has finished then you filter. If the point is to avoid expense searches, which is almost always the case, then this won't help. – TugboatCaptain Nov 30 '21 at 14:50
3

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 | ....
Rex Henderson
  • 412
  • 2
  • 7
  • The part `fullname -inotmatch '\\obj\\|\\bin\\'` is really worth mentioning if you are looking for files inside folders (which is not exactly the question above, but was my case)! – tomwaitforitmy Nov 18 '22 at 09:42
2

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
PollusB
  • 1,726
  • 2
  • 22
  • 31
1

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
}
Nathan Goings
  • 1,145
  • 1
  • 15
  • 33
1

I wanted a solution that didn't involve looping over every single item and doing ifs. 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)
    }
}
Luiz Felipe
  • 1,123
  • 8
  • 14
1

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"}
  • 2
    Hi and welcome to Stack Overflow! Please take the [tour](https://stackoverflow.com/tour). Thanks for answering but can you also add an explanation on how your code solves the issue? Check the [help center](https://stackoverflow.com/editing-help) for info on how to format code. – Tyler2P Dec 04 '20 at 10:18
0
#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
Jason Brower
  • 27
  • 1
  • 4
0
$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
jangix
  • 49
  • 7
0

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
}
Joe
  • 1
0

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

Rainer
  • 1
  • 2
  • Jason Brower's answer should be upvoted sky-high for its efficiency, but I can neither comment on others' posts nor vote yet – Rainer Jan 15 '22 at 02:53
-4

may be in your case you could reach this with the following:

    mv excluded_dir ..\
    ls -R 
    mv ..\excluded_dir .
udito
  • 56
  • 3
  • 3
    Moving around folders is a dangerous way to solve an unrelated problem. This could have serious side effects, if the move does not move hidden/system files, or the script is terminated (by the user or by a crash) between the two move operations. – runemoennike Jul 15 '19 at 10:52