1

I have this strange observation:

I want to run an expression for as many times as there are files.

Yet, the following script always executes only once, no matter how many files are found by Get-ChildItem:

(Get-ChildItem -Filter '*.png'), (Get-ChildItem -Filter '*.jpg') |
  Sort-Object -Property 'Name' |
  ForEach-Object -Begin { $idx = 0 } -Process { ++$idx; $idx; }

If I replace the expression with $_, all rows are returned as expected:

(Get-ChildItem -Filter '*.png'), (Get-ChildItem -Filter '*.jpg') |
  Sort-Object -Property 'Name' |
  ForEach-Object -Begin { $idx = 0 } -Process { $_; }
mklement0
  • 382,024
  • 64
  • 607
  • 775
AxD
  • 2,714
  • 3
  • 31
  • 53
  • 1
    `(...),(...)` creates a nested array with two "inner" arrays. You want `(...;...)`: `(Get-ChildItem -Filter '*.png'; Get-ChildItem -Filter '*.jpg') |...` – Mathias R. Jessen Jan 16 '23 at 21:11
  • I tried your suggestion, but that results in a syntax error: `(Get-ChildItem -Filter '*.png';Get-ChildItem -Filter '*.jpg') … Missing closing ')' in expression.` — Please note my update to my question. – AxD Jan 16 '23 at 21:14
  • Looks like PS is optimizing and since you are not using the loop variable it is exiting the loop. Use you second method. – jdweng Jan 16 '23 at 21:16
  • @AxD Sorry, there's a `@` missing from my previous comment: `@(Get-ChildItem ...; Get-ChildItem ...)` :) – Mathias R. Jessen Jan 16 '23 at 21:16

1 Answers1

3

As Mathias points out, (...), (...) creates a nested array, which is not your intent (the , operator constructs an array from its operands, even if those operands are themselves arrays).

The best way to provide output from multiple commands as pipeline input is to use & (or . , if you need the commands to run directly in the caller's scope) with a script block ({ ... }), in which, as usual you can separate commands with ;:

& { Get-ChildItem -Filter *.png; Get-ChildItem -Filter *.jpg } |
  Sort-Object -Property Name |
  ForEach-Object -Begin { $idx = 0 } -Process { ++$idx; $idx; }

This approach streams the command output, whereas use of $(...) or @(...) (which in this particular case can be used interchangeably) - $(Get-ChildItem -Filter *.png; Get-ChildItem -Filter *.jpg) or @(Get-ChildItem -Filter *.png; Get-ChildItem -Filter *.jpg) - collects all output first and then sends it to the pipeline.


An simplified version of your command that makes do with a single Get-ChildItem call, using the -Path parameter's support for multiple wildcard patterns:

Get-ChildItem -Path *.png, *.jpg |
  Sort-Object -Property Name |
  ForEach-Object -Begin { $idx = 0 } -Process { (++$idx) }
  • -Filter is usually preferable, because it filters at the source, but it only supports one wildcard pattern; while -Path, which makes PowerShell filter the results, is noticeably slower, the overhead from extra Get-ChildItem calls may negate that advantage.

    • There's also -Include / -Exclude, which also filter on the PowerShell side, but, unfortunately, they do not work as one would intuitively expect: see this answer.
  • Also note the use of (...) around ++$idx, which causes the updated value to also be output.

mklement0
  • 382,024
  • 64
  • 607
  • 775