Elifcan Mehekli
Elifcan Mehekli

Reputation: 55

Suppress receive-job console output when the job uses write-host

I'm starting a job and I need to receive the output before the job finishes. It uses write-host instead of write-output, I know when I use write-output this shouldn't be a problem but assume I need to use write-host.

So as an example I have a job

$job = Start-Job -Name $taskId -ScriptBlock { Write-host "This is the job output, there is a keyword"; start-sleep 2 }

I need to retrieve the console output and check for a keyword there, so I use receive-job

$result = Receive-Job -Job $job -Keep -ErrorAction SilentlyContinue *>&1

I use *>&1 to be able to capture every output. But I need to suppress the console output and only assign the output to the variable. Unfortunately I cannot suppress the console log. Using Out-Null also does not populate the variable.

Is there a way to do this?

Thanks

Upvotes: 2

Views: 84

Answers (3)

mklement0
mklement0

Reputation: 439777

  • What you're seeing is a long-standing bug, still present as of PowerShell 7.5.x, reported a long time ago in GitHub issue #3354 and GitHub issue #9585; in a nutshell:

    • In background jobs (but not thread jobs - see next point), PowerShell remoting calls and mini-shells (from-PowerShell CLI calls that use a script block to specify code to execute), (unless silenced at the source) output streams 3 (warning stream, 4 (verbose stream), 5 (debug stream), and, except in mini-shells, 6 (information stream, to which Write-Host prints) cannot be suppressed (silenced) with >, because they are locally replicated as host output, which is invariably printed.

      • There are also limitations on which streams can be captured and how - see GitHub issue #9585 for details.
    • As you've observed, the problem also surfaces when you use*>&1, i.e. a redirection that routes all other streams through stream 1, the success output stream.

  • An easy way to bypass the bug is to use Start-ThreadJob rather than Start-Job; use of Start-ThreadJob is generally preferable anyway, because it creates comparatively lightweight, much faster thread-based jobs (whereas Start-Job uses child processes) that support full type fidelity.[1]

    • Start-ThreadJob comes with PowerShell (Core) 7, and in Windows PowerShell can be installed on demand, using, e.g., Install-Module ThreadJob -Scope CurrentUser.

    • See the bottom section for a Start-ThreadJob solution.

  • Otherwise, the only workarounds are:

    • Use *>&1 redirections from inside your job, as shown in sirtao's helpful answer.

    • If modifying the job script block is not an option, the only way to avoid the bug is to refrain from using Receive-Job:

      • Santiago's helpful answer offers a solution for reading (non-redirected) Write-Host / Write-Information output, specifically, via the .Information collection on the child jobs.

      • If you need the output from other streams too, apply the technique analogously to the properties representing them, i.e. .Output, .Error, .Warning, and .Debug (e.g., $job.ChildJobs[0].Output); note, however, that you'll need to access them separately and individually.


Start-ThreadJob-based solution:

# Note the use of Start-ThreadJob instead of Start-Job.
$job = Start-ThreadJob -ScriptBlock { 
  Write-host "This is the job output, there is a keyword"
}

# Wait for the job to complete and retrieve all output via 
# the success output stream (*>&1)
# Thanks to Start-ThreadJob, *no* to-host output is now printed.
$allOutput = $job | Receive-Job -Wait -AutoRemoveJob *>&1

# Print the captured output.
"`$allOutput value: [$allOutput]"

[1] See the bottom section of this answer more information.

Upvotes: 2

Santiago Squarzon
Santiago Squarzon

Reputation: 60838

You may be able to capture the host output from the Job by inspecting the .Information property of the child job. In this case you can also use Receive-Job however the Job host output will still be console output.

$job = Start-Job -Name $taskId -ScriptBlock {
    Write-Host 'This is the job output, there is a keyword'
    Start-Sleep 2
}
$job | Wait-Job | Out-Null
$hostOutput = $job.ChildJobs | ForEach-Object { $_.Information.ReadAll() }

Upvotes: 2

sirtao
sirtao

Reputation: 2880

You need to redirect the write-host inside the scriptblock.
To do that, you need to redirect the InformationStream(6) to the SuccessStream(1).

# this prints nothing.   
$job = Start-Job -Name $taskId -ScriptBlock { Write-host "This is the job output, there is a keyword" 6>&1; start-sleep 2 }

# this, too, prints nothing.   
$result = Receive-Job -Job $job -Keep

# this prints "This is the job output, there is a keyword"
$result

Mind you: this means $result is a PSObject in the SuccessStream.
Any decoration like colors would be lost.

Is this just playing to understand how Write-Host works\its limits or there is some specific need?

Upvotes: 1

Related Questions