PowerShell, Get-ChildItem with -Depth output - powershell

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?

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

GitHub Actions // PowerShell // recursive file search applying pylint

I'm looking for a PowerShell version of the following Linux command for CI with GitHub actions:
find . -name "*.py" -not -path "./exclude_dir/*" | xargs pylint
here is where I'm now:
get-childitem -path $pwd -include *.py -recurse -name
at the moment I have no idea how to exclude "exclude_dir" and apply pylint to the selected python files.
Any help would be very appreciated!
Thanks in advance!
Best,
Alexey
While Get-ChildItem does have an -Exclude parameter, it only operates on the file-name part, not on the full path.
Therefore, you must perform the exclusion filtering after the fact, using the negated form of -like, the wildcard matching operator
pylint ((Get-ChildItem -Recurse -Name -Filter *.py) -notlike 'exclude_dir/*')
Note the use of -Filter rather than -Include, which speeds up the operation, because filtering happens at the source rather than being applied by PowerShell after the fact.
However, given that you're seemingly only excluding a single top-level folder, you could try:
pylint (Get-ChildItem -Recurse -Path * -Filter *.py -Exclude exclude_dir)
Note that I've omitted -Name in this case, because it wouldn't work properly in this scenario. As a result, the matching files are implicitly passed as full paths to pylint.
As of PowerShell 7.0, -Name exhibits several problematic behaviors, which are summarized in this answer.

How do I use Get-ChildItem to return files that don't have an extension? [duplicate]

This question already has answers here:
How to select files that have no extension using powershell
(3 answers)
Closed 4 years ago.
I want to get a list of files that don't have a filename extension. Consider the content of my directory to be:
folder
file1
file2.mp4
My goal would be to get file1 only.
Running Get-ChildItem -Exclude *.* -File returned nothing.
Running Get-ChildItem -Exclude *.* returned folder and file1.
Running Get-ChildItem -File returned file1 and file2.mp4.
Any idea if there is any way of using Get-ChildItem to only return file1?
In PSv3+, but doesn't work in PowerShell Core v6.x, fixed in v7 (see this GitHub issue):
Get-ChildItem -File -Filter *.
-File limits output to just files (as opposed to directories).
-Filter *. selects only those files that have no extension.
-Filter is generally preferable to -Include / -Exclude for performance reasons, because it filters at the source, rather than returning all objects and letting PowerShell do the filtering.
In PSv2, where the -File switch isn't available, you need an additional Where-Object call to limit the results to files, as TheIncorrigible1 points out:
Get-ChildItem -Filter *. | Where-Object { -not $_.PSIsContainer }
Slower PowerShell Core solution:
Get-ChildItem -File | Where-Object -Not Extension
Optional background information:
That a -Filter argument is processed by the underlying provider, not by PowerShell, means that its behavior may differ from PowerShell's, which is indeed the case here: the FileSystem provider uses the Windows API's wildcard-expression matching, which has fewer features than PowerShell's as well as some historical quirks; also, it is limited to a single wildcard expression, whereas -Include / -Exclude support multiple ones (separated with ,).
Here, however, -Filter offers something that PowerShell's wildcard matching doesn't: using *. to match files / directories without extension.
-Include / -Exclude generally offer functional advantages at the expense of performance, but they have their own limitations and quirks:
*. isn't supported to match items without extension in PowerShell's wildcard language, and there is no wildcard-based way to achieve this that I know of.
-Include / -Exclude operate on the last component of the specified or implied path, so if you're implicitly targeting the current directory, they apply to that directory path, not to the individual files inside.
Specifying -Recurse changes that, but that searches the entire directory subtree.
While you should be able to add -Depth 0 to limit matches to the immediate child items while still being able to apply -Include / -Exclude, this is broken as of Windows PowerShell v5.1: The -Depth argument is ignored in this case.
This problem has been fixed in PowerShell Core, however.
In short: -Include / -Exclude offer no solution here.

Get-ChildItem Exclude and File parameters don't work together

I can't figure out why these two parameters of the Get-ChildItem cmdlet don't work together. To make my question as clear as possible, look at the following example. From the Powershell ISE command pane:
Type 'dir' --> All files and sub-folders in the current directory are displayed.
Type 'dir -File' --> Original list minus sub-folders is displayed.
Type 'dir -Exclude "*.txt"' --> Original list minus .txt files is displayed.
Type 'dir -File -Exclude "*.txt"' --> NOTHING is displayed.
I would expect the original list minus sub-folders and .txt files. But regardless of what argument I use for '-Exclude', I get no items listed.
I have looked at the Get-ChildItem -full documentation, and the related articles here (Stack Overflow) and at other reliable resources, and still don't understand why this fails. Even the classic "-Include '*.txt' -Exclude 'A*'" example fails when you add "-File". How can I use -File and -Exclude together?
Whilst dir is an alias for Get-ChildItem, I find it best to use the full cmdlets when providing answers.
To use proper PowerShell cmdlets it would be best for you to use the following:
Get-ChildItem * -Exclude "*.txt" -File
What you see above is the PowerShell cmdlet to get all items in the path specified (using the * assumes you want all items from the current location)
You can also use -Path and provide the location of the path to where you want to get the items as well, such as:
Get-ChildItem -Path "C:\Path\Folder" -Exclude "*.txt" -File

Different result from same command in powershell 3.0

Given that Get-ChildItem -Path *.exe will show all the executables in the current directory, why doesn't Get-ChildItem -File -Include *.exe return the same result? Both commands are executed in the same directory, first command (with -Path) returns a list of executables but the second command (with -File) doesn't. (gci -File will list everything including the exe)
Get-ChildItem -File | gm #=> FileInfo
Get-ChildItem *.* | gm #=> DirectoryInfo and FileInfo
All the commands bellow return objects of type FileInfo
Get-ChildItem -File
Get-ChildItem *.* -Include *.exe
Get-ChildItem -Path *.exe
But mixing -File and -Include/-Exclude returns nothing, even though the -include is looking for a filetype:
Get-ChildItem -File -Include *.exe #=> Returns nothing
What am I missing here?
From TechNet:
The Include parameter is effective only when the command includes the
Recurse parameter or the path leads to the contents of a directory,
such as C:\Windows*, where the wildcard character specifies the
contents of the C:\Windows directory.
In other words, when you use the Include parameter, it does not automatically consider all files and directories unless you use the Path or the Recurse parameters. Notice, that when just using the Path parameter, you must include a wildcard to force it to consider the file and directory results underneath that path. I cannot think of why this is.
To get your examples to work, you would use one of the following (I'm dropping the File parameter because it seems redundant):
Get-ChildItem -Path * -Include *.exe
Get-ChildItem -Include *.exe -Recurse
The gist of my answer to your question is an opinion tho - from what I've seen the Include parameter should be removed - or its behavior repaired to match the default behavior of the Get-ChildItem cmdlet when used without parameters. There may be a good explanation to why it works this way, but I'm unaware of this.
If you drop the Include parameter from your examples, the behavior/results make more sense (to me):
Get-ChildItem -Path *.exe
In this case, we would only need the Exclude parameter to effectively cover all filtering requirements. Something like:
Get-ChildItem -Path *.exe -Exclude *system*

Confused with -Include parameter of the Get-ChildItem cmdlet

From documentation:
-Include
Retrieves only the specified items.
The value of this parameter qualifies
the Path parameter. Enter a path
element or pattern, such as "*.txt".
Wildcards are permitted.
The Include parameter is effective only when the command
includes the Recurse parameter or the
path leads to the contents of a
directory, such as C:\Windows*, where
the wildcard character specifies the
contents of the C:\Windows directory.
My first understanding was:
c:\test\a.txt
c:\test\b.txt
So to get 'a.txt' and 'b.txt' I can write:
gci -Path "c:\test\*" -Include "*.txt"
And this works. But now consider such hierarchy:
c:\test\a.txt
c:\test\b.txt
c:\test\c.txt\c.txt
The same command returns:
a.txt, b.txt, c.txt
The actual logic seems to be:
-Include used to match all entities specified by -Path. If matched element
is a file - return it. If matched
element is a folder, look inside and
return matching first level children.
Also, the documentation say:
The Include parameter is effective only when the command
includes the Recurse parameter or the
path leads to the contents of a
directory...
This is wrong as well. E.g.
gci -Path "c:\test" -Include "*.txt"
It returns nothing, while without -Include I get folder content. So -Include is definitely "effective". What really happens here? The -Path specify the "c:\test", and the -Include tries to match this path. As "*.txt" does not match "test", so nothing returned. But look at this:
gci -Path "c:\test" -Include "*t"
It returns a.txt, b.txt and c.txt as "*t" matched "test" and matched all child items.
After all, even knowing how Include works now, I don't understand when to use it. Why do I need it look to inside subfolders? Why should it be so complex?
You're confusing the use of -include. The -include flag is applied to the path, not the contents of the path. Without the use of the recursive flag, the only path that is in question is the path you specify. This is why the last example you gave works, the path c:\test has a t in the path and hence matches "*t".
You can verify this by trying the following
gci -path "c:\test" -in *e*
This will still produce all of the children in the directory yet it matches none of their names.
The reason that -include is more effective with the recurse parameter is that you end up applying the wildcard against every path in the hierarchy.
Try the -filter parameter (it has support for only one extension):
dir -filter *.txt
Tacking on to JaredPar's answer, in order to do pattern matching with Get-ChildItem, you can use common shell wildcards.
For example:
get-childitem "c:\test\t?st.txt"
where the "?" is a wildcard matching any one character or
get-childitem "c:\test\*.txt"
which will match any file name ending in ".txt".
This should get you the "simpler" behavior you were looking for.
I just asked a similar question and got three quick replies concerning the Get-Help for Get-ChildItem.
The answer is in the full description
of the command (Get-Help Get-ChildItem
-full):
The Include parameter is effective only when the command includes the
Recurse parameter or the path leads to
the contents of a directory, such as
C:\Windows*, where the wildcard
character specifies the contents of
the C:\Windows directory.
So the following would work without
recurse.
PS C:\foo> Get-childitem -path
"c:\foo*" -Include *.txt
From Stack Overflow question PowerShell Scripting - Get-ChildItem.
I hope this helps :-)
Including \* at the end of the path should work around the issue
PS C:\logfiles> Get-ChildItem .\* -include *.log
This should return .log files from the current working directory (C:\logfiles)
Alex's example above indicates that a directory with the name foo.log would also be returned. When I tried it, it wasn't but it's 6 years later and that could be from PS updates.
However, you can use the child item Mode to exclude directories I think.
PS C:\logfiles> Get-Childitem .\* -include *.log | where-object {$_.mode -notmatch "d"}
This should exclude anything with the 'directory' mode set.
get-childitem -include only works with -recursive or a wildcard in the path. I consider this a bug [Thought it was different in PS 6].