Why does Write-Host not work when run in a powershell job?
Solution 1:
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 is6
, 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) orFailed
(a script-terminating error was generated withthrow
(and not caught inside the job)).- See the
[System.Management.Automation.JobState]
enumeration for the complete list of possible states.
- See the
-
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.