PowerShell, Get-ChildItem with -Depth output

Viewed 1277

I'm a bit confused by the -Depth flag for Get-ChildItem. The following works great (finds all files and folders only one deep under "C:\Program Files"):

dir 'C:\Program Files\' -Depth 1

But if I then want to extend it to find only *.txt type files, I cannot find how to do that (following just give weirdly unexpected output where -Depth 1 is ignored and it instead does the equivalent of a -Recurse to all subfolders no matter how deep):

dir 'C:\Program Files\*.txt' -Depth 1
dir 'C:\Program Files\' -Include *.txt -Depth 1
dir 'C:\Program Files\*' -Include *.txt -Depth 1

How do we use -Depth to a specific depth for Get-ChildItem and a required file-pattern?

4 Answers

The behavior you're seeing is a bug in Windows PowerShell, that has since been fixed in PowerShell [Core] 6+ - see this GitHub issue.

Given that Windows PowerShell is no longer actively developed, it is unlikely that the bug will be fixed.

To spell it out, Windows PowerShell ignores -Depth's depth constraint in the following cases:

  • with -Include or -Exclude
  • if the (implied) -Path argument contains wildcard characters.

While recursion is still performed, no depth limit is imposed; in effect, -Depth behaves like -Recurse (alone) in these cases.

Workarounds:

  • For -Include and wildcard-based -Path arguments where the wildcards are limited to the last path component:

    • Use -Filter instead, as shown in Wasif Hasan's answer.
    • Caveat: -Filter is usually preferable anyway for its superior performance, but its wildcard language is less powerful than PowerShell's and has legacy quirks - notably, character sets and ranges ([...]) are not supported and in Windows PowerShell a filter such as *.xls also matches *.xlsx files, for instance - see this answer.
  • For -Exclude:

    • Use only -Depth and perform filtering after the fact with a Where-Object call; e.g.,
      Get-ChildItem -File 'C:\Program Files\' -Depth 1 | Where-Object Name -NotLike *.txt
  • [Probably rarely needed] For wildcard-based -Path arguments with wildcard characters (also) in a component other than the last one (e.g., C:\foo*\bar)

    • Use -Recurse and perform filtering after the fact with Where-Object; in this case, you'll also have to weed out too-deep paths by counting the number of their components.

The issue gets solved when you use Filter instead of Include. Filter parameter will return file in correct pattern with depth. (TESTED)

dir 'C:\Program Files\' -Filter *.txt  -Depth 1

In older versions of PowerShell there was no depth, in that case the above can also be

Get-ChildItem -Path "C:\DIRECTORY\*","C:\DIRECTORY\*\*"

If it is pure for filenames then

(Get-ChildItem -Path "c:\program files" -file -Depth 3 -Force -erroraction SilentlyContinue).FullName

Is identical to the ancient kind of tricks, i.e.

(cmd.exe /c dir "c:\program files" /b /a-d /s)|foreach {if ($_.split("\").length -le 5){$_}}

It's amazing that PowerShell is even faster than the above line! I remember that a few years ago that was not the case, but I just tested it and it was 3-4 times faster

To further clarify the answer by Wasif Hasan As I was going through the official documentation for the Get-ChildItem, it is stated there

When using the -Include parameter, if you do not include an asterisk in the path the command returns no output.

Which means that the Depth will be ignored automatically as the behavior required for the Include is recursive. Further some details of the -Include reveals these points.

If the Recurse parameter is added to the command, the trailing asterisk (*) in the Path parameter is optional. The Recurse parameter gets items from the Path directory and its subdirectories. For example, -Path C:\Test\ -Recurse -Include *.txt

So the behavior you are looking for is in the Filter flag for the Get-ChildItem which do not requires any wild cards

For me the Depth flag with any other flag that accepts wild cards in the path do not make sense as the purpose of the Depth flag is to restrict the depth of search in the Items where as specifying a wild card excludes that particular purpose. You can try this by simply using this command and you will see that the Depth parameter is not effective if you specify a wild card in the path for example

Get-ChildItem -Path C:\DIRECTORY\* -Depth 1

and

Get-ChildItem -Path C:\DIRECTORY\* -Depth 2

are going to return the same results.

Hope this helps clarify some issues

Related