Why does Write-Host not work when run in a powershell job?
Asked Answered
D

1

6

Sorry if I'm being a dumb powershell noob, but what's wrong with jobs apparently being unable to write to the terminal? And how can I fix that?

# test.ps1
function myjob {
    Write-Host "Hello, World!" # doesn't show
}
Start-Job -Name MyJob -ScriptBlock ${function:myjob}
Wait-Job MyJob
Remove-Job MyJob
Decoupage answered 15/1, 2022 at 19:46 Comment(4)
you need to receive the job - Get-Job | Receive-JobKelsey
I've tried that, but all it does is yield a value returned by the job. That's definitly not what I want - my job routines are a bit more complex than the tiny example code I provided for the question...Decoupage
As an aside: Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answerMonophagous
Hmm, weird. But yes it's working with Receive-Job added, even though the script block isn't supposed to return a value. And yes, my intent is to only show it on the console, so I'm fine with that.Decoupage
M
6

It sounds like you're trying to use Write-Host to directly, synchronously write to the console (terminal) from a background job.

However, PowerShell jobs do not allow direct access to the caller's console. Any output - even to the PowerShell host (which in foreground use is the console, if run in one) is routed through PowerShell's system of output streams (see the conceptual about_Redirection help topic).

Therefore, you always need the Receive-Job cmdlet in order to receive output from a PowerShell job.

The following example receives the job output synchronously, i.e. it blocks execution until the job finishes (-Wait) and then removes it (-AutoRemoveJob); see the bottom section for an asynchronous (polling, non-blocking) approach.

$null = Start-Job -Name MyJob -ScriptBlock { Write-Host "Hello, World!" } 
Receive-Job -Wait -AutoRemoveJob -Name  MyJob

Caveat re use of Write-Host in jobs:

  • In foreground use, Write-Host output - even though primarily designed to go to the host (console) - can be redirected or captured via the information stream (whose number is 6, available in PSv5+); e.g.:

    # OK - no output
    Write-Host 'silence me' 6>$null
    
  • Write-Host output received via a (child-process-based) background job, however, can not be redirected or captured, as of PowerShell 7.2.1:

    # !! `silence me` still prints. 
    Start-Job { Write-Host 'silence me' } | Receive-Job -Wait -AutoRemoveJob 6>$null
    
    • By contrast, it can be redirected/captured when using a (generally preferable) thread-based background job (as opposed to a child-process-based background job), via Start-ThreadJob:

      # OK - no output
      Start-ThreadJob { Write-Host 'silence me' } | Receive-Job -Wait -AutoRemoveJob 6>$null
      

Waiting for a job to complete in a non-blocking fashion, passing job output through as it becomes available:

# Start a simple job that writes a "." to the host once a second,
# for 5 seconds
$job = Start-Job $job -ScriptBlock { 
         1..5| ForEach-Object { Write-Host -NoNewLine .; Start-Sleep 1 } 
       } 

"Waiting for job $($job.Id) to terminate while passing its output through..."

do {
  $job | Receive-Job  # See if job output is available (non-blocking) and pass it through
  Start-Sleep 1       # Do other things or sleep a little.
} while (($job | Get-Job).State -in 'NotStarted', 'Running')

"`nJob terminated with state '$($job.State)'."

$job | Remove-Job     # Clean up.
  • Note: In this simple case, the expected termination states are Completed (either no or only non-terminating errors occurred) or Failed (a script-terminating error was generated with throw (and not caught inside the job)).

  • The job object returned by Start-Job - rather than a self-chosen name via the -Name parameter - is used to interact with the job. This eliminates the ambiguity of possibly multiple jobs being present with a given -Name, all of which would be targeted.

Monophagous answered 15/1, 2022 at 22:43 Comment(2)
Thanks man, you're awesome! I think I can manage the rest from now on :)Decoupage
Glad to hear it, @BinkanSalaryman; my pleasure.Monophagous

© 2022 - 2024 — McMap. All rights reserved.