Search code examples
powershellpipeline

How does the order of execution of the ForEach-Object cmdlet in a pipeline work?


In PowerShell, it appears that the order of execution of cmdlets in a pipeline aren't executed in an obvious way. Rather than each cmdlet executing then passing the results to the next cmdlet in the pipeline, it seems that individual output objects of a cmdlet are passed to the input of the next cmdlet before the execution of the previous cmdlet complets. The following confirms this behavior:

1..5 | %{ Write-Host $_; $_ } | %{ Write-Host ([char]($_ + 64)) }

prints

1
A
2
B
3
C
4
D
5
E

It appears what's happening is that each execution of the ForEach-Object cmdlet will execute its script block and each subsequent command in its pipeline before iterating.

Is this what actually occurs, and is this behavior documented anywhere? Is this the case for all iterative cmdlets like ForEach-Object (e.g. Where-Object, etc.)?

I know I can wrap pieces in an expression ((1..5 | %{ Write-Host $_; $_ }) | %{ Write-Host ([char]($_ + 64)) }) or assign a piece to a variable and then pipe that to subsequent pipeline commands to avoid this behavior, but is there a way to perform an operation on each element of a collection and then pass that entire collection on to the next command in a pipeline?


Solution

  • This matches my understanding of how pipelines process objects. Objects travel "as far" in the pipeline as possible before the next item is touched. Some cmdlets don't use the "process" block but instead use the "end" block out of necessity (for example, sort-object has to have all of the items to actually perform the sort) so they block the pipeline. Others use write-output (which your second $_ is doing implicitly) and keep the pipeline moving.