1

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.

JM1
  • 1,595
  • 5
  • 19
  • 41
  • 2
    You don't need `-not`, you need `-notlike`: `Get-ChildItem -Recurse |Where-Object { $_.Name -like '*.pdf' -and $_.Name -notlike '*Sold*'}` – Mathias R. Jessen Nov 22 '21 at 16:15

2 Answers2

4
  • -Filter only accepts a single pattern.

  • Use -Include and -Exclude instead[1], 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.

[1] Note that you may even combine -Filter with -Include / -Exclude, in which case the -Filter argument is applied first, at the source. Because of how -Include and -Exclude currently work, this is only works meaningfully if the input path ends in \* or -Recurse is used - see this answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    It's probably worth noting that combining `-Recurse` and `-Include`/`-Exclude` will incur [a serious performance penalty](https://stackoverflow.com/questions/52293871/powershell-performance-get-childitem-include-vs-get-childitem-where-object/52294304#52294304) to the point where it's worse than just piping all file system items to `Where-Object` :) – Mathias R. Jessen Nov 22 '21 at 16:25
  • 1
    Thanks, @MathiasR.Jessen - please see my update. I had no idea how slow `-Include` and `-Exclude` actually are. While they are _inherently, of necessity_ slower than `-Filter`, there is _no_ justification for performing worse than an equivalent command that involves an additional `Where-Object` call for post-filtering. There are several issues in the repo re `-Include` and `-Exclude` already, but I'll see if I can find one that calls out this specific problem - if you happen to know of one, do let me know. – mklement0 Nov 22 '21 at 16:56
  • Wow, thank you both. I'm not sure if I've ever seen such a good answer template @mklemento. The 'What you tried' section should be included much more often in answers as it helps specifically fill in the knowledge gap the questioner has so well. Great job and thank you for the help! – JM1 Nov 22 '21 at 16:59
  • @Mathias. I've found the relevant issue (also added to the answer): https://github.com/PowerShell/PowerShell/issues/8662. Turns out that I created it myself, in 2019 - and you commented :) – mklement0 Nov 22 '21 at 17:02
  • 1
    @mklement0 LGTM! I'm afraid I haven't kept on top related issues. I tend to favor `-Filter` + `... |Where-Object` for exclusions whenever possible, (instead of moving both include and exclude logic to `Where-Object`) – Mathias R. Jessen Nov 22 '21 at 17:03
  • 1
    @Mathias :) Re combining `-Filter` with `Where-Object`: Makes perfect sense (the answer already shows an example). – mklement0 Nov 22 '21 at 17:05
0

With -not, you would need parentheses because of operator precedence (without parentheses "-not $_.name" becomes "False"):

get-childitem | where {$_.name -like '*.pdf' -and -not ($_.name -like '*Sold*')} 

You could also get into wmi for some speed (doesn't cover c:\users?):

Get-WmiObject CIM_DataFile -filter 'Drive="C:" and Extension="pdf" and 
  not name like "%Sold%" and path like "\\%"'
js2010
  • 23,033
  • 6
  • 64
  • 66