Search code examples
multithreadingpowershellrunspace

What output exactly is captured by -Streaminghost in Start-ThreadJob?


When running code as a ThreadJob in PowerShell 7.2.11 (also applies to 7.2.13), I've noticed that not all output is affected by the -Streaminghost argument.

Start-ThreadJob {
  Write-Host 'Hello'
} -Streaminghost $Host

will output Hello,

but using Write-Output in the thread will not show the output until I do a Get-Job | Receive-Job...

Start-ThreadJob {
  Write-Output 'Hello'
} -Streaminghost $Host
Start-ThreadJob {
  'Hello'
} -Streaminghost $Host

What gives?


Solution

  • Santiago Squarzon has provided the crucial pointers:

    • The purpose of Start-ThreadJob's -StreamingHost parameter is to directly connect all output streams other than the success and the error output streams to the specified PowerShell host.

    • The success and the error stream outputs are quietly collected by the resulting job and require Receive-Job to be retrieved on demand later.

      • Note: Whether or not you use -StreamingHost, output objects from the other output streams are also collected in the job and therefore potentially reported by Receive-Job, via their respective streams, though the output may not surface; as of PowerShell 7.4.6:

        • Write-Host, Write-Warning and Write-Debug output is surfaced by Receive-Job unconditionally, which is expected only for the former two, given that they produce output unconditionally; it is unclear why Write-Debug, which by default produces no output (produces output only when requested via the common -Debug parameter or when the $DebugPreference preference variable is set to 'Continue'), behaves the same.

        • Also surfacing Write-Verbose and Write-Information output requires passing the relevant common parameters to Receive-Object, -Verbose and -InformationAction Continue and, in the case of Write-Verbose only, requires having turned on verbose output inside the job too (via the common -Verbose parameter or via $VerbosePreference = 'Continue'). This is not necessary for Write-Information which seemingly always produces output, which isn't surfaced by default, however.[1]

          • That Receive-Job in combination with thread jobs (Start-ThreadJob) as well as with ForEach-Object -Parallel doesn't honor the silence / non-silence of streams as set inside the job is problematic - see GitHub issue #24016 for a discussion.
            (By contrast, it does in combination with background (child process-based) jobs (Start-Job), at least with respect to to-host display, but capturing / redirecting stream output has its problems too - see GitHub issue #9585).

    If you truly want to surface all output streams from the thread job instantly, asynchronously, you can use the following technique, which uses redirection *>&1, which merges all outputs streams into the success output stream, and pipes the result to Out-Host for direct-to-host output:

    $job = Start-ThreadJob {
      & {
        Write-Host 'Hello host'
        Write-Output 'Hello success'
        Write-Error 'Hello error'
      } *>&1 | Out-Host
    } -StreamingHost $Host
    

    Note:

    • The above also outputs a (non-terminating) error in addition to host (Write-Host) and success output (Write-Output) to demonstrate that all output surfaces instantly, asynchronously.

    • Due to using Out-Host for direct-to-display printing, you cannot capture any output with this technique.


    [1] The following commands illustrate this difference: Write-Information info 6>&1 prints info, because info was still emitted and therefore redirected to the success output stream - even though the command by itself, without the redirection, is silent. By contrast, Write-Verbose verbose 4>&1 outputs nothing, because Write-Verbose, in the absence of requesting verbose output (e.g., with -Verbose), emits nothing