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 theWhere-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.
- The upside of using
Without
-Recurse
,-Include
and-Exclude
do not work as expected - useGet-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.
- That is, you effectively passed