5

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
mklement0
  • 382,024
  • 64
  • 607
  • 775
Binkan Salaryman
  • 3,008
  • 1
  • 17
  • 29
  • 2
    you need to receive the job - Get-Job | Receive-Job – Miroslav Vasilev Jan 15 '22 at 20:03
  • 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... – Binkan Salaryman Jan 15 '22 at 20:08
  • 1
    As an aside: [`Write-Host` is typically the wrong tool to use](http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/), 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 answer](https://stackoverflow.com/a/60534138/45375) – mklement0 Jan 15 '22 at 20:11
  • 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. – Binkan Salaryman Jan 15 '22 at 20:33

1 Answers1

5

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775