Home > Net >  PowerShell - How to use multiple filter's with -not? (See example.)
PowerShell - How to use multiple filter's with -not? (See example.)

Time:11-23

I'm trying to move .pdf's based on text that could be present anywhere in the file name and it's not working. It executes without error, but it doesn't move the files.

All the .pdf's with 'Sold' in the filename go to one place and all the ones without 'sold' in the file name go to a different place.

get-childitem -Recurse -path $sourcePathPDF  {-filter '*.pdf' -and '*Sold*'} | move-item -Destination $destinationPathSoldPDF 
get-childitem -Recurse -path $sourcePathPDF  {-filter '*.pdf' -not '*Sold*'} | move-item -Destination $destinationPathPDF 

Am I filtering/using the -not incorrectly?

Thanks for the help.

CodePudding user response:

  • -Filter only accepts a single pattern.

  • Use -Include and -Exclude instead, both of which accept multiple patterns.

# Two inclusion patterns
Get-ChildItem -Recurse -Path $sourcePathPDF -Include *.pdf, *sold*

# One inclusion, one exclusion pattern.
Get-ChildItem -Recurse -Path $sourcePathPDF -Include *.pdf -Exclude *Sold*

Note:

  • As of PowerShell 7.2, -Include and -Exclude are hampered by performance problems, as Mathias R. Jessen notes, due to their inefficient implementation (see GitHub issue #8662), so the Where-Object-based solution discussed below may be called for not just for more complex matching logic, but for performance alone.

  • -Include and -Exclude use PowerShell's wildcard expressions, which have more features and lack the legacy quirks of the platform-native patterns that -Filter supports - see this answer for more information.

    • The upside of using -Filter is that its much faster than -Include / -Exclude, because -Filter filters _at the source, whereas -Include / -Exclude filters are applied after the fact, by PowerShell, after all items have been retrieved first.
  • Without -Recurse, -Include and -Exclude do not work as expected - use Get-Item * -Include/-Exclude ... instead - see this answer.


For more sophisticated pattern matching and/or better performance, pipe to a Where-Object call in whose script block you can use the wildcard-based -like operator, as Mathias recommends; alternatively, for even more flexible matching, you can use the regex-based -match operator. To adapt Mathias' -like-based Where-Object solution from the comment:

Get-ChildItem -Recurse  -Path $sourcePathPDF |
  Where-Object { $_.Name -like '*.pdf' -and $_.Name -notlike '*Sold*' }

For (currently) best performance you can pre-filter with -Filter:

Get-ChildItem -Recurse -Path $sourcePathPDF -Filter *.pdf |
  Where-Object { $_.Name -notlike '*Sold*' }

As for what you tried:

  • -Filter, -Include, and -Exclude are string-typed - there is no support for passing script blocks ({ ... } with arbitrary expression to them.

    • While pipeline-binding parameters may accept script blocks, in order to supply parameter values dynamically, based on the input object at hand (a feature known as delay-bind script blocks), neither of these parameters support such binding, so passing script blocks doesn't apply.
  • What actually happened in your attempt is that the stringified version of script block {-filter '*.pdf' -and '*Sold*'} - which is its verbatim content (excluding { and }) - was positionally bound to the -Filter parameter.

    • That is, you effectively passed -Filter "-filter '*.pdf' -and '*Sold*'", which predictably didn't match any files, because verbatim -filter '*.pdf' -and '*Sold*' was used as the pattern.
  • Related