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.
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 ofWrite-Host $value
(or useWrite-Output $value
, though that is rarely needed); see this answer – Monophagous