arrayspowershellpipelinepowershell-7.3

How to have index variable increase in ForEach-Object?


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 (jagged) 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) }