15

I have a list in my C: directory that has many files. If I try to run an -Exclude on it, I get no returns. Same with -Include. If I use -Filter, it returns what I expected to get back. Am I not understanding what it should be doing?

Here is an example of what I am running and getting nothing:

Get-ChildItem -Path C: -Exclude "*.txt"

I get nothing back. If I run

Get-Childitem -filter "*.txt"

I get this back:

  Mode                LastWriteTime         Length Name                                                                               
----                -------------         ------ ----                                                                               
-a----        11/7/2007   8:00 AM          17734 eula.1028.txt                                                                      
-a----        11/7/2007   8:00 AM          17734 eula.1031.txt                                                                      
-a----        11/7/2007   8:00 AM          10134 eula.1033.txt                                                                      
-a----        11/7/2007   8:00 AM          17734 eula.1036.txt                                                                      
-a----        11/7/2007   8:00 AM          17734 eula.1040.txt                                                                      
-a----        11/7/2007   8:00 AM            118 eula.1041.txt                                                                      
-a----        11/7/2007   8:00 AM          17734 eula.1042.txt                                                                      
-a----        11/7/2007   8:00 AM          17734 eula.2052.txt                                                                      
-a----        11/7/2007   8:00 AM          17734 eula.3082.txt                                                                      
               7/7/2016   8:50 AM             93 HaxLogs.txt                                                                        
-a----         7/8/2016   8:30 AM              0 Test.txt 
mklement0
  • 382,024
  • 64
  • 607
  • 775
bradrice
  • 1,655
  • 2
  • 24
  • 44
  • For me it works without a problem, but have you tried not using quotation marks for the extension too? Like `Get-ChildItem -Path C: -Exclude *.txt` ? – nyagolova Jul 08 '16 at 14:40

4 Answers4

43

To summarize and complement gravity's and Brian Reynolds's helpful answers:

There are two distinct problems with your approach:

  • Targeting C: probably doesn't (always) do what you want, because C: refers to whatever happens to be the current location (working dir.) on drive C: at the moment.

    • To target the root folder of drive C:, you must use C:\, which I'll assume is what you meant in the remainder of this answer.
  • Using the -Exclude (and also -Include) parameter with neither -Recurse nor a -Path value of * or ending in \* often yields NO results. Unexpected? Indeed - see below for more.

    • Thus, Get-Item -Path C:\* -Exclude *.txt - note the switch from Get-ChildItem to Get-Item and the * after C:\ - is needed to make your command work for the items located directly in C:\ only.

Background information:

Using the provider-native -Filter parameter is generally preferable to -Include, because:

  • it is much faster than -Include due to the provider itself performing the filtering at the source, as opposed to letting PowerShell apply the filter later, after all objects have been received.

  • it doesn't require you to switch to Get-Item and append \* to the -Path parameter value.

    • Get-ChildItem -Path C:\ -Filter *.txt works fine for matching all *.txt files in the root directory of C:, for instance.

That said, there are caveats:

  • The wildcard pattern language supported by -Filter has fewer features than PowerShell's - notably, it doesn't support character sets/ranges such as [0-9] - may unexpectedly match short (8.3) filenames, and has other legacy quirks - see this well-researched answer for the gory details.

  • -Filter supports only a single pattern, whereas -Include supports multiple ones (an array of patterns).

Unfortunately, -Filter is always a positive (inclusionary) filter and therefore cannot be used to provide the functionality of -Exclude.


The implementation of -Include / -Exclude with Get-ChildItem is unintuitive and has pitfalls:

Side note: if you only use one -Include pattern (as opposed to -Exclude), it's easier to append the pattern directly to the -Path argument; e.g.: Get-ChildItem C:\*.txt

tl;dr:

To get predictable behavior with -Include / -Exclude if you're not also using -Recurse (if you are using -Recurse this workaround is not needed):

# IMPORTANT: Workaround isn't needed if you're using -Recurse.
#   * "\*" was appended to the input path
#   * Get-*ChildItem* was switched to Get-*Item*
Get-Item C:\path\to\* -Include ...
Get-Item C:\path\to\* -Exclude ...

In PowerShell (Core) v7+, if your input paths are literal ones, you can alternatively use Get-ChildItem with -LiteralPath rather than (possibly positionally implied) -Path (use . for the current dir.):

# IMPORTANT: Works in PowerShell (Core) only.
#            Note the use of -LiteralPath.
Get-ChildItem -LiteralPath C:\path\to -Include ...
Get-ChildItem -Literalpath C:\path\to -Exclude ...

  • There's an outright bug in Windows PowerShell where, when -LiteralPath is used, -Include / -Exclude are quietly ignored. This has been fixed in PowerShell (Core) as of (at least) v7.0 (and it's fair to assume that it will not be fixed in Windows PowerShell, which will only receive critical fixes going forward).

  • -Include and -Exclude do not work as one would intuitively expect, which is the subject of GitHub issue #3304:

    • -Include and -Exclude modify the leaf (last) path component of the -Path argument, i.e. file or directory names in the case of file-system paths.

    • That means that the patterns are first applied to the leaf component of the specified folder path itself, before getting applied to the child items, if at all.

    • If the input path doesn't end in \* and -Recurse is not specified, the implications are as follows:

      • -Include: If the input path's last path component does not match the -Include pattern(s), the input path itself is excluded (not included), and the path's child items are never looked at - nothing is output.

      • -Exclude: Analogously, if the input path's last path component does match the -Exclude pattern(s), the input path itself is excluded, and the path's child items are never looked at - nothing is output.

        • Targeting a root directory - e.g., Get-ChildItem -Path C:\ -Exclude Windows) appears to be broken altogether as of v7.0: it either produces no output at all, or fails on Unix-like platforms, both with -Include and -Exclude, irrespective of the patterns used - see GitHub issue #11649.
      • As stated, the problem doesn't surface if -Recurse is used, because that forces descending into the input path's subtree, even if the input path itself is not included / excluded.

  • Unless -Recurse is needed, the only way to get expected behavior is to replace
    Get-ChildItem C:\path\to -Include / -Exclude with
    Get-Item C:\path\to\* -Include / -Exclude - note the use of Get-Item instead of Get-ChildItem, and that \* was appended to the -Path argument.

    • By contrast, if you use Get-ChildItem * in combination with -Exclude and there are directories among the non-excluded items, Get-ChildItem will unexpectedly output their contents instead; this does not happen with -Include and generally doesn't happen with subdirectory matches by a wildcard expression (-Path argument and/or -Filter argument).
  • A summary of the problems as of PowerShell 7.2:

    • GitHub issue #3304 (discussed above): counterintuitive application of -Include / -Exclude patterns only to the input themselves rather than to their children.

    • GitHub issue #11649: Get-ChildItem -Path <rootPath> -Exclude <anyPattern> unexpectedly produces no output (even though nothing should be excluded, given that the exclusion pattern is normally applied to the input paths, such as / or c:\).

    • GitHub issue #9126: -Include / -Exclude unexpectedly follow symlinks when -Recurse is used.

    • GitHub issue #8662: performance problem: -Include / -Exclude are slower(!) than after-the-fact filtering with Where-Object.

    • A related feature request is GitHub issue #15159, which suggests introducing the ability to exclude subfolder subtrees (as opposed to just excluding the items matching the patterns themselves, but not their children), with a new parameter such as -ExcludeSubtree.


Examples: problematic uses of -Include / -Exclude

Note: To make all commands below work as one would intuitively expect, replace Get-ChildItem C:\Windows with Get-Item C:\Windows\* - note the use of a different cmdlet, Get-Item, and the appended \*.

# HAPPENS TO WORK, BUT IS NOT ROBUST:
# Matches all w* items *inside* C:\Windows, but
# ONLY because w* happens to match 'Windows' - the last input
# path component - too.
Get-ChildItem C:\Windows -Include w*

# HAPPENS TO WORK, BUT IS NOT ROBUST:
# Matches all items whose names *don't* start with a-v *inside* C:\Windows, but
# ONLY because [a-v]* happens not to exclude 'Windows' - the last input
# path component - too.
Get-ChildItem C:\Windows -Exclude [a-v]*


# OUTPUTS NOTHING:
# Because t* doesn't match 'Windows', the child items of 
# 'C:\Windows' are not considered.
Get-ChildItem C:\Windows -Include t*

# OUTPUTS NOTHING:
# Because w* matches 'Windows', it is excluded, and
# the child items of 'C:\Windows' are not considered.     
Get-ChildItem C:\Windows -Exclude w*
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 4
    Holy moly that's a thorough incorporation of all of the different hang-ups this had! Way to really dig deep into the reasoning! :) Is there somewhere that you found this, or did you research it on your own? – gravity Jul 11 '16 at 20:17
  • 3
    This is broken beyond all belief. It's literally saying that -Include/-Exclude are known to just be silently ignored; therefore they're unreliable. It would be outright dangerous to write a script to delete child directories with an -Exclude option, because it could delete them. This a huge problem, because the Remove-Item documentation says its -Recurse parameter is also 'known to not work', and suggests using Get-ChildItem to pipe it to the command as a workaround. But Get-ChildItem is also clearly broken! What a mess. – Triynko Sep 24 '21 at 00:12
  • P.S., @Triynko: A similarly potentially destructive problem is discussed in [GitHub issue #5699](https://github.com/PowerShell/PowerShell/issues/5699). – mklement0 Sep 24 '21 at 00:58
  • Small correction, @Triynko: `-Include` / `-Exclude` aren't _ignored_ - they are just not applied in the way one would expect: they are inappropriately applied to the _input_ path(s) (first) rather than (only) to _their child items_, and the resulting, seemingly unpredictable behavior is arguably even worse than just ignoring. As for the warnings in the `Remove-Item` help topic re `-Recurse` / combining `-Recurse` with `-Include`: Let's try to get those clarified / updated / removed: see [Github docs issue #8134](https://github.com/MicrosoftDocs/PowerShell-Docs/issues/8134). – mklement0 Sep 24 '21 at 18:50
  • I was confronted with the unique problem of getting all files and folders with no dot in their name. Apparently, `-Filter *.` was the solution, despite the fact that this parameter is supposed to be inclusionary. Truly dark magic. – AgentRev Nov 17 '21 at 20:07
  • 1
    @AgentRev, yes, that's counterintuitive, but it isn't PowerShell's fault: the `-Filter` argument is generally provider-specific, which for file-system paths means that it's the underlying WinAPI that handles the wildcard matching, which has a lot of legacy quirks, such as the one you mention - see [this answer](https://stackoverflow.com/a/60171419/45375). – mklement0 Nov 17 '21 at 22:49
11
Get-ChildItem -Path "C:\*" -Include "*.txt"

This example, of how -Include should work, will give you the results you were expecting. Note that I provided a wildcard in the path parameter as well, to explicitly define the path as "any file in the root C:" as opposed to "C:" itself.

Source: https://technet.microsoft.com/library/hh849800.aspx

Example 3 from this link, in case it goes defunct (note the wildcard in path here, as well):

C:\> Get-ChildItem –Path "C:\Windows\Logs\*" -Include "*.txt" -Exclude "A*"
gravity
  • 2,175
  • 2
  • 26
  • 34
  • 1
    Thanks. For some reason the wildcard in the path does work. – bradrice Jul 08 '16 at 15:10
  • 4
    This is the kind of thing that drives me mad: when APIs/commands behave in different ways based on input. Of course, that SHOULD happen but what I mean specifically is like in this case. When you pass in a path alone it presumes the wildcard `*`. When you add `-Include` or `-Exclude` it stops making that presumption and the docs don't say this. More importantly, the command is CALLED `Get-ChildItem`. It should be getting CHILD items. The wildcard should be unnecessary in all cases. – Chris76786777 Dec 17 '18 at 17:38
  • 3
    Wow. Apparently adding a wildcard to the path made everything work. that was super confusing – Kellen Stuart Mar 02 '20 at 22:38
2

Using 'C:' with no slash after it is the reason you're not getting the results you want. This is being interpreted as a relative path to the current directory instead of the root of the C drive. (See: Path with no slash after drive letter and colon - what does it point to?)

If you use 'C:\' instead it should work as expected.

Edit: My mistake; I was responding specifically to the examples which only use '-exclude'.

Community
  • 1
  • 1
Brian Reynolds
  • 402
  • 2
  • 5
  • Actually, it does not. Try it yourself. `Get-ChildItem -Path "C:\" -Include "*.txt"` yields no results, whereas, `Get-ChildItem -Path "C:\*" -Include "*.txt"` shows me all of the files extensions with `.txt` – gravity Jul 08 '16 at 20:45
  • Yes, I get no results unless I add the *. after C:\. I'm not sure why because I am at location C: and if I use Get-ChildItem C: or Get-ChildItem C:\ I get all the files back. It appears only -Include and -Exclude do this for me. I'm running PSVersion - 5.0.10586.122 – bradrice Jul 08 '16 at 21:16
  • It is (unfortunately) true that appending \* in the absence of -Recurse is needed with -Include / -Exclude), but this answer makes a good point about `C:` vs. `C:\ ` – mklement0 Jul 11 '16 at 14:18
0

This seems to be a bug (because the string version of the parent properties is empty string?). With subdirectories, it works fine:

get-childitem c:\windows -directory -exclude *.dll

You can specify the root directory in a different way, and get a strange error message:

get-childitem Microsoft.PowerShell.Core\FileSystem::C:\ -exclude *.txt

get-childitem : Cannot process argument because the value of argument "path" is not valid. Change the value of the "path" argument and run the operation again.
At line:1 char:1
+ get-childitem Microsoft.PowerShell.Core\FileSystem::C:\ -exclude wind ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Get-ChildItem], PSArgumentException
    + FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.GetChildItemCommand

Or, in osx:

get-childitem / -directory -exclude *.txt

get-childitem : Cannot process argument because the value of argument "path" is not valid. Change the value of the "path" argument and run the operation again.
At line:1 char:1
+ get-childitem / -directory -exclude var
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [Get-ChildItem], PSArgumentException
+ FullyQualifiedErrorId : Argument,Microsoft.PowerShell.Commands.GetChildItemCommand

This is a workaround for powershell 6 and above:

get-item c:\ | get-childitem -exclude *.txt
js2010
  • 23,033
  • 6
  • 64
  • 66