Doug Maurer's helpful answer provides effective solutions.
As for an explanation of the behavior you saw:
The intrinsic .ForEach()
method first collects all success output produced by the successive, per-input-object invocations of the script block ({ ... }
) passed to it, and only after having processed all input objects outputs the collected results to the pipeline, i.e. the success output stream, in the form of a [System.Collections.ObjectModel.Collection[psobject]]
instance.
In other words:
Unlike its cmdlet counterpart, the ForEach-Object
cmdlet, the .ForEach()
method does not emit output produced in its script block instantly to the pipeline.
As with any method, success output is only sent to the pipeline when the method returns.
- Note that non-PowerShell .NET methods only ever produce success output (which is their return value) and only ever communicate failure via exceptions, which become statement-terminating PowerShell errors.
By contrast, the following do take instant effect inside a .ForEach()
call's script block:
Suspending execution temporarily, such as via a Start-Sleep
Forcing instant display (host) output, such as via Out-Host
or Write-Host
.
- Note that to-host output with
Out-Host
prevents capturing the output altogether, whereas Write-Host
output, in PowerShell v5+, can only be captured via the information output stream (number 6
).
Writing to an output stream other than the success output stream, such as via Write-Error
, Write-Warning
or Write-Verbose -Verbose
.
Alternatively, you may use the foreach
statement, which, like the ForEach-Object
cmdlet, also instantly emits output to the success output stream:
# Stand-alone foreach statement: emits each number right away.
foreach ($i in 1..3) { $i; pause }
# In a pipeline, you must call it via & { ... } or . { ... }
# to get streaming behavior.
# $(...), the subexpression operator would NOT stream,
# i.e. it would act like the .ForEach() method.
& { foreach ($i in 1..3) { $i; pause } } | Write-Output