How to have index variable increase in ForEach-Object?

Advertisements

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 { $_; }

>Solution :

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)collect all output first and then send 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 at a time; while -Path, which makes PowerShell filter the results is noticeably slower, the overhead from an extra Get-ChildItem call 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.

Leave a ReplyCancel reply