To complement David Martin's helpful answer:
Avoiding Write-Host
(which is often the wrong tool to use) in favor of Write-Output
, i.e. outputting to the success output stream - rather than printing to the display with Write-Host
[1] - solves your immediate problem.
However, you could have avoided the problem by using &
, the call operator instead of the .Invoke()
method:
# Invokes the script block and *streams* its output.
& $test_block
- Using
&
is generally preferable, not just to avoid .Invoke()
's collect-all-success-output-stream-first behavior - see next section.
As an aside:
This answer describes common problem with similar symptoms (success output (pipeline output) appearing out of order relative to other streams), although it is technically unrelated and also occurs with &
:
# !! The Write-Host output prints FIRST, due to implicit, asynchronous
# !! table formatting of the [pscustomobject] instance.
& { [pscustomobject] @{ Foo = 'Bar' }; Write-Host 'Why do I print first?' }
Why &
, not .Invoke()
, should be used to invoke script blocks ({ ... }
):
Script blocks ({ ... }
) are normally invoked with &
, the call operator, in argument (parsing) mode (like cmdlets and external programs), not via their .Invoke()
method, which allows for more familiar syntax; e.g.:
& $test_block
rather than $test_block.Invoke()
- with arguments:
& $test_block arg1 ...
rather than $test_block.Invoke(arg1, ...)
Perhaps more importantly, using this operator-based invocation syntax (& { ... } ...
or . { ... } ...
) has the following advantages:
It preserves normal streaming semantics, meaning that success output (too) is emitted from the script block as it is being produced, whereas .Invoke()
collects all success output first, in a [System.Collections.ObjectModel.Collection[psobject]]
instance, which it then returns - by contrast, the Write-Host
output goes straight to the display in both cases.
As a beneficial side effect, your specific output-ordering problem goes away, but note that in PSv5+ there can generally still be an output-ordering problem, although its cause is unrelated:
Implicit tabular output (implied Format-Table
) for output types without predefined format data is asynchronous in an effort to determine suitable column widths (your specific code happens to only use cmdlets with predefined format data).
See this answer for more information; a simple repro:
[pscustomobject] @{ foo = 1 }; Write-Host 'Should print after, but prints first.'
It allows you to pass named arguments (e.g. -Foo Bar
), whereas .Invoke()
supports only positional (unnamed) ones (e.g. Bar
).
It preserves normal semantics for script-terminating errors:
# OK: & throw aborts the entire script; 'after' never prints.
& { throw 'fatal' }; 'after'
# !! .Invoke() "eats" the script-terminating error and
# !! effectively converts it to a *statement*-terminating one.
# !! Therefore, execution continues, and 'after' prints.
{ throw 'fatal' }.Invoke(); 'after'
Additionally, using operator-based invocation gives you the option to use .
, the dot-sourcing operator, in lieu of &
, so as to run a script block directly in the caller's scope, whereas an .Invoke()
method call only runs in a child scope (as &
does):
# Dot-sourcing the script block runs it in the caller's scope.
. { $foo='bar' }; $foo # -> 'bar'
Use of .Invoke()
is best limited to PowerShell SDK projects (which are typically C#-based, where use of PowerShell operators isn't an option).
[1] Technically, since PowerShell v5 Write-Host
outputs to the information stream (stream number 6
), which, however, prints to the display by default, while getting ignored in the pipeline and redirections. See this answer for a juxtaposition of Write-Host
and Write-Output
, and why the latter is typically note even needed.