Home > Net >  How to have index variable increase in ForEach-Object?
How to have index variable increase in ForEach-Object?

Time:01-17

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

CodePudding user response:

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.

  • Related