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?
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.
Each object travels the pipeline as far as it can. That's why every cmdlet should write its output directly to the pipeline rather than accumulate it in some internal storage, except if it has to process all its input together like the Sort-Object.
It also has some interesting side effects. Eg the Get-Member cmdlet gives different output if it gets input from the pipeline and different if it gets its input from InputObject parameter.
And, because IT IS PowerShell, it's all documented here:
Get-Help about_pipelines
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With