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

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

Related

Searching for only folders that contain a certain folder using powershell

I am trying to use powershell to update some programs for my company. I am writing a script to do so (as instructed). When I install the new version of the program on the machines, it also requires me to 'upgrade' existing folders to match the new version of the software.
I need to find all of the folders that contain a certain hidden folder(let the name of said folder be .blah). I am trying to use the get-childitem command, with -path [drives to check] -Recurse -Directory -Force -EA SilentlyContinue. However, I am not sure how to filter correctly to only find folders that contain the .blah folder inside of it.
Help would be very much appreciated.
Combine your Get-ChildItem call with a Where-Object call that tests for a child directory of a given name using Test-Path:
# Note: "." refers to the *current* directory
# Adjust as needed.
Get-ChildItem -LiteralPath . -Recurse -Directory -Force -ErrorAction Ignore |
Where-Object {
Test-Path -ItemType Container -LiteralPath "$($_.FullName)\.blah"
}
The Get-ChildItem call outputs all directories (-Directory) in the entire directory subtree (-Recurse), including hidden ones (-Force), ignoring any errors (such as from lack of permissions, -ErrorAction Ignore).
The Where-Object call calls Test-Path to look for a .blah child directory (-ItemType Container) in the directory at hand ($_).
With a -LiteralPath argument, Test-Path finds the specified path if it exists, irrespective of whether the target file or directory is hidden.
By contrast, with a wildcard-based -Path argument, hidden items are not found, and given that, as of PowerShell 7.2.5, Test-Path has no -Force switch, there is no way to force their inclusion; this gap in functionality is the subject of GitHub issue #6501.
Note: In PowerShell (Core) 7+, you could simplify "$($_.FullName)\.blah" to "$_\.blah", because the [System.IO.DirectoryInfo] and [System.IO.FileInfo] instances output by Get-ChildItem and Get-Item there consistently stringify to their full path (.FullName) property, unlike in WindowsPowerShell, where they situationally stringify by their file/directory name only - see this answer.

Why get-childitem with wildcards are not easy to use? [duplicate]

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

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.

How does the Get-ChildItem -Exclude Parameter work?

How does the Get-ChildItem -Exclude parameter work? What rules does it follow?
The Get-Help for Get-ChildItem isn't detailed at all:
Omits the specified items. The value of this parameter qualifies the
Path parameter. Enter a path element or pattern, such as "*.txt".
Wildcards are permitted.
And on Stackoverflow and elsewhere the general consensus seems to be it's too difficult to use and we should all just pipe the output of Get-ChildItem to Where-Object instead.
While I'm willing to use Where-Object I'm curious as to the rules -Exclude follows.
For example, I have a folder with the following sub-folders:
HsacFixtures
HsacFixturesBuild
RestFixture
RestFixtureBuild
If I execute the following command:
Get-ChildItem $rootFolderPath -Exclude HsacFixturesBuild -Directory
it returns the results expected:
HsacFixtures
RestFixture
RestFixtureBuild
However, if I add a -Recurse parameter:
Get-ChildItem $rootFolderPath -Exclude HsacFixturesBuild -Directory -Recurse
Then it returns sub-folders in the HsacFixturesBuild folder.
I've also tried HsacFixturesBuild\ and HsacFixturesBuild\*, which have the same results.
So does -Exclude only apply to immediate children, and not to grand-children or deeper sub-folders?
Exclude omits child objects based on the Name,
For files, gets the name of the file. For directories, gets the name
of the last directory in the hierarchy if a hierarchy exists.
Otherwise, the Name property gets the name of the directory
not the FullName,
which gets the full path of the directory or file
So even though the object is a grandchild in a recursive call, the exclude only looks at the object's Name, not the FullName so the exclude wont affect omission unless the child objects share a common substring of the name that happens to be part of the exclude parameter
Source Get-ChildItem
Example 3: Get all child items using an inclusion and exclusion
This command lists the .txt files in the Logs subdirectory, except for
those whose names start with the letter A. It uses the wildcard
character (*) to indicate the contents of the Logs subdirectory, not
the directory container. Because the command does not include the
Recurse parameter, the command does not include the content of
directory automatically; you need to specify it.
Windows PowerShell
PS C:\> Get-ChildItem –Path "C:\Windows\Logs\*" -Include "*.txt" -Exclude "A*"
With respect to Get-ChildItem,-exclude parameter works on the objects name
It could be understood better from the following example:
Consider the following folder structure
First we use -exclude parameter without recurse.
As we could see in the above image, based on objects name , folder was excluded
Now we add recurse parameter to the above statement as follows
Now we see could see that sub-folders of folder is still present, because the exclusion was applied at objects name
Hope this HElps.

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