PowerShell Receive-Job Output To Variable Or File But Not Screen - powershell

I am trying to get the job details without outputting the data to the screen. However, regardless of what option I try, the job logs always get sent to the console. Any ideas on how to save the logs in a variable or file without outputting that data to console?
Receive-Job -Id $id -Keep -ErrorAction Continue > C:\Temp\Transcript-$VM.txt
$info = Receive-Job -Id $id -Keep -ErrorAction Continue

You state that your job uses Write-Host output and that you're running Windows PowerShell v5.1.
In order to also capture Write-Host output - which in v5+ is sent to the information stream (stream number 6) - use redirection 6>&1:
# Capture both success output and information-stream output
# (Write-Host) output in $info.
$info = Receive-Job -Id $id -Keep -ErrorAction Continue 6>&1
Unfortunately, due to a known bug, you'll still get console output as well (bug is still present in PowerShell Core 7.0.0-preview.5).
Catch-all redirection *>&1 normally routes all streams through the success output stream.
Unfortunately, due to the bug linked to above, the following streams cannot be captured or redirected at all when using background jobs or remoting:
verbose messages (4)
debug messages (5)
The only workaround is to capture the streams inside the job and save them to a file from there, and then access the files from the caller later.
Of course, this requires that you have control over how the jobs are created.
A simplified example:
# Redirect all output streams *inside* the job to a file...
Start-Job {
& {
# The job's commands go here.
# Note that for any *verbose* output to be captured,
# verbose output must explicitly turned on, such as with
# the -Verbose common parameter here.
# You can also set $VerbosePreference = 'Continue', which
# cmdlets (including advanced functions/scripts) will honor.
'success'; write-verbose -Verbose 'verbose'; write-host 'host'
} *> $HOME/out.txt
} | Receive-Job -Wait -AutoRemove
# ... then read the resulting file.
Get-Content $HOME/out.txt
Note that I've used a full path as the redirection target, because, unfortunately, in v6- versions of PowerShell script blocks executed in background jobs do not inherit the caller's current location. This will change in PowerShell Core v7.0.

Try placing it in a pipeline, and see if that works:
Receive-Job -Id $id -Keep -ErrorAction Continue | Set-Content 'C:\Temp\Transcript-$VM.txt'

Related

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

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
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)).
See the [System.Management.Automation.JobState] enumeration for the complete list of possible states.
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.

How to capture warnings to a log

I have a script to manage hyper-v virtual machines. When I execute the command to shut down a machine, I need the warning to be saved in the log if it is shut down.
The script I have only captures the errors, I need to get both errors and warnings:
Try {
Stop-VM $Machine -ErrorAction Stop
} Catch {
Write-Host "Error: $_"
Add-Content -Path $Log -Value "`n$_"
}
Thanks!
Assuming that Stop-VM issues non-terminating errors:
Stop-VM $Machine *> output.log
Note: This redirects all of PowerShell's output streams to file output.log, including success output, if any, and it would work with passing an array of VM names in $Machine.
As Abraham Zinala points out, you can selectively capture (some of the) output streams, in variables, using the the common -WarningVariable parameter as well as -ErrorVariable, which you can later send to a file as needed. Note that using these variables still produces the original stream output, but you can silence that with -WarningAction SilentlyContinue and -ErrorAction SilentlyContinue. See the answer linked below for details.
As Santiago Squarzon points out, you could extend your original approach by adding -WarningAction Stop, but the limitation, as
explained in this answer, is that only the first error or warning emitted by the call is then captured, and, perhaps more importantly, the command is terminated at that point - even if multiple VMs were specified.

Do threads still execute using -asjob with wait-job?

Hello all and good afternoon!
I had a quick question regarding -asjob running with invoke-command.
If I run 2 Invoke-Command's using -asjob, does it run simultaneously when I try to receive the ouput? Does this mean wait-job waits till the first job specified is finished running to get the next results?
Write-Host "Searching for PST and OST files. Please be patient!" -BackgroundColor White -ForegroundColor DarkBlue
$pSTlocation = Invoke-Command -ComputerName localhost -ScriptBlock {Get-Childitem "C:\" -Recurse -Filter "*.pst" -ErrorAction SilentlyContinue | % {Write-Host $_.FullName,$_.lastwritetime}} -AsJob
$OSTlocation = Invoke-Command -ComputerName localhost -ScriptBlock {Get-Childitem "C:\Users\me\APpdata" -Recurse -Filter "*.ost" -ErrorAction SilentlyContinue | % {Write-Host $_.FullName,$_.lastwritetime} } -AsJob
$pSTlocation | Wait-Job | Receive-Job
$OSTlocation | Wait-Job | Receive-Job
Also, another question: can i save the output of the jobs to a variable without it showing to the console? Im trying to make it where it checks if theres any return, and if there is output it, but if theres not do something else.
I tried:
$job1 = $pSTlocation | Wait-Job | Receive-Job
if(!$job1){write-host "PST Found: $job1"} else{ "No PST Found"}
$job2 = $OSTlocation | Wait-Job | Receive-Job
if(!$job2){write-host "OST Found: $job2"} else{ "No OST Found"}
No luck, it outputs the following:
Note: This answer does not directly answer the question - see the other answer for that; instead, it shows a reusable idiom for a waiting for multiple jobs to finish in a non-blocking fashion.
The following sample code uses the child-process-based Start-Job cmdlet to create local jobs, but the solution equally works with local thread-based jobs created by Start-ThreadJob as well as jobs based on remotely executing Invoke-Command -ComputerName ... -AsJob commands, as used in the question.
It shows a reusable idiom for a waiting for multiple jobs to finish in a non-blocking fashion that allows for other activity while waiting, along with collecting per-job output in an array.
Here, the output is only collected after each job completes, but note that collecting it piecemeal, as it becomes available, is also an option, using (potentially multiple) Receive-Job calls even before a job finishes.
# Start two jobs, which run in parallel, and store the objects
# representing them in array $jobs.
# Replace the Start-Job calls with your
# Invoke-Command -ComputerName ... -AsJob
# calls.
$jobs = (Start-Job { Get-Date; sleep 1 }),
(Start-Job { Get-Date '1970-01-01'; sleep 2 })
# Initialize a helper array to keep track of which jobs haven't finished yet.
$remainingJobs = $jobs
# Wait iteratively *without blocking* until any job finishes and receive and
# output its output, until all jobs have finished.
# Collect all results in $jobResults.
$jobResults =
while ($remainingJobs) {
# Check if at least 1 job has terminated.
if ($finishedJob = $remainingJobs | Where State -in Completed, Failed, Stopped, Disconnected | Select -First 1) {
# Output the just-finished job's results as part of custom object
# that also contains the original command and the
# specific termination state.
[pscustomobject] #{
Job = $finishedJob.Command
State = $finishedJob.State
Result = $finishedJob | Receive-Job
}
# Remove the just-finished job from the array of remaining ones...
$remainingJobs = #($remainingJobs) -ne $finishedJob
# ... and also as a job managed by PowerShell.
Remove-Job $finishedJob
} else {
# Do other things...
Write-Host . -NoNewline
Start-Sleep -Milliseconds 500
}
}
# Output the jobs' results
$jobResults
Note:
It's tempting to try $remainingJobs | Wait-Job -Any -Timeout 0 to momentarily check for termination of any one job without blocking execution, but as of PowerShell 7.1 this doesn't work as expected: even already completed jobs are never returned - this appears to be bug, discussed in GitHub issue #14675.
If I run 2 Invoke-Command's using -asjob, does it run simultaneously when I try to receive the output?
Yes, PowerShell jobs always run in parallel, whether they're executing remotely, as in your case (with Invoke-Command -AsJob, assuming that localhost in the question is just a placeholder for the actual name of a different computer), or locally (using Start-Job or Start-ThreadJob).
However, by using (separate) Wait-Job calls, you are synchronously waiting for each jobs to finish (in a fixed sequence, too). That is, each Wait-Job calls blocks further execution until the target job terminates.[1]
Note, however, that both jobs continue to execute while you're waiting for the first one to finish.
If, instead of waiting in a blocking fashion, you want to perform other operations while you wait for both jobs to finish, you need a different approach, detailed in the the other answer.
can i save the output of the jobs to a variable without it showing to the console?
Yes, but the problem is that in your remotely executing script block ({ ... }) you're mistakenly using Write-Host in an attempt to output data.
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 answer.
Therefore, your attempt to collect the job's output in a variable failed, because the Write-Host output bypassed the success output stream that variable assignments capture and went straight to the host (console):
# Because the job's script block uses Write-Host, its output goes to the *console*,
# and nothing is captured in $job1
$job1 = $pSTlocation | Wait-Job | Receive-Job
(Incidentally, the command could be simplified to
$job1 = $pSTlocation | Receive-Job -Wait).
[1] Note that Wait-Job has an optional -Timeout parameter, which allows you to limit waiting to at most a given number of seconds and return without output if the target job hasn't finished yet. However, as of PowerShell 7.1, -Timeout 0 for non-blocking polling for whether jobs have finished does not work - see GitHub issue #14675.

PowerShell: How 'Receive-Job' pulls output from the job's code block in detail?

Please have a look at this test script and the conclusions I've made about how 'Receive-Job' works in detail.
I have still issues to figure out, how exaclty 'Receive-Job' pulls the streams from the code block.
<# .SYNOPSIS Test the console output and variable capturing of Write- cmdlet calls in a code block used by 'Start-Job'
.NOTES
.NET Version 4.7.2
PSVersion 5.1.16299.431
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.16299.431
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
#>
Set-StrictMode -Version latest
if ($host.Name -inotmatch 'consolehost') { Clear-Host }
$errorBuffer = $null
$warningBuffer = $null
$outBuffer = $null
$infoBuffer = $null
# Start the job
$job = Start-Job -ScriptBlock {
Set-StrictMode -Version latest
PowerShell starts this script block in its own process, like it would start an external executable.
Therfore PowerShell can only map stdout/success and stderr/error from the codeblock to the PowerShell's success (1) and error (2) streams in the script's process.
Those two streams will be passed by Receive-Job and can be redirected in the Receive-Job line as expected.
And those two streams can be stored into variables by Receive-Job on request. (-OutVariable -ErrorVariable)
Additionally, Receive-Job can caputure the PowerShell streams info (stream 6) and warning (stream 3) and can store them in variables, too. (-WarningVariable -InformationVariable)
But storing those streams in the variables is no redirection.
Every call of a Write- cmdlet can display a message on the console, independent to the -variable swiches.
A visible message on the console depends only on the Write- cmdlet's own preferences and possible redirection in the Write- cmdlet call.
# This will, by default, output to the console over stream 6 (info), and always get captured in $infoBuffer.
Write-Host "***WRITE_HOST***" # 6> $null # Supresses the output to the console.
# This will not output to the console over stream 6 (info) by default, but always get captured in $infoBuffer.
$InformationPreference = 'Continue' # Outputs to the console, default is 'SilentlyContinue'.
Write-Information "***INFO***" # 6> $null # Supresses the output to the console for preference 'Continue'.
$InformationPreference = "SilentlyContinue"
# This will not output to the console over stream 5 (debug) by default, and can't get captured in a variable.
$DebugPreference = 'Continue' # Outputs to the console, default is 'SilentlyContinue'.
Write-Debug "***DEBUG***" # 5> $null # Supresses the output to the console for preference 'Continue'.
$DebugPreference = "SilentlyContinue"
# This will not output to the console over stream 4 (verbose), by default, and can't get captured in a variable.
$VerbosePreference = 'Continue' # Outputs to the console, default is 'SilentlyContinue'.
Write-Verbose "***Verbose***" # 4> $null # Supresses the output to the console for preference 'Continue'.
$VerbosePreference = 'SilentlyContinue'
# This will, by default, output to the console over stream 3 (warning), but get captured in $warningBuffer only for
# preference 'Continue'.
#$WarningPreference = 'SilentlyContinue' # Supresses console output AND variable capturing, default is 'Continue'.
Write-Warning "***WARNING***" # 3> $null # Supresses the warning output to the console for preference
#$WarningPreference = 'Continue' # 'Continue'.
# This will output to the console over stream 2 (error), and always get captured in $errorBuffer, if not redirected
# in the code block.
# For 'Receive-Job -ErrorAction Stop' it would raise an execption, the content in $errorBuffer is quite useless then.
Write-Error '***ERROR***' # 2> $null # Supresses the output AND variable capturing, but you can supress/redirect
# this stream in the 'Receive-Job' line without breaking the variable
# capturing: 'Receive-Job ... -ErrorVariable errorBuffer 2> $null'
# These will output to the console over stream 1 (success), and always get captured in $result and $outBuffer, if
# not redirected in the code block.
Write-Output '***OUTPUT***' # 1> $null # Supresses the output AND variable capturing, but you can supress/redirect
Write-Output '***NEXT_OUTPUT***' # this stream in the 'Receive-Job' line without breaking the variable
"***DIRECT_OUT***" # capturing: '$result = Receive-Job ... -OutVariable outBuffer 1> $null'
}
# Wait for the job to finish
Wait-Job -Job $job
try
{
# Working only outside the code block, this is a workaround for catching ALL output.
#$oldOut = [Console]::Out
#$stringWriter = New-Object IO.StringWriter
#[Console]::SetOut($stringWriter)
# Pull the buffers from the code block
$result = Receive-Job <#-ErrorAction Stop#> `
-Job $job `
-ErrorVariable errorBuffer `
-WarningVariable warningBuffer `
-OutVariable outBuffer `
-InformationVariable infoBuffer `
# 1> $null #2> $null # Only the success and error streams can be redirected here, other
# streams are not available.
# Restore the console
#[Console]::SetOut($oldOut)
# Get all catched output
#$outputOfAllWriteFunctions = $stringWriter.ToString()
}
catch
{
Write-Host "EXCEPTION: $_" -ForegroundColor Red
}
finally
{
Write-Host "error: $errorBuffer"
Write-Host "warning: $warningBuffer"
Write-Host "out: $outBuffer"
Write-Host "info: $infoBuffer"
Write-Host "result: $result"
#Write-Host "`noutputOfAllWriteFunctions:`n";Write-Host "$outputOfAllWriteFunctions" -ForegroundColor Cyan
Remove-Job -Job $job
}
My final conclusions:
Because the code block of Start-Job runs in its own process, it can't write to the scripts process console directly.
The code block is wrapped by a capture mechanism, which captures all 6 PS streams in buffers.
A call of Receive-Job uses inter process communication to get all those streams.
Receive-Job passes through stream 1 and 2 and makes them to its own output and therefore avaiable for redirection.
Receive-Job uses Write-Error to write stream 2 to the console, and therfore Receive-Job will raise an exception for parameter -ErrorAction Stop.
Then Write-Error uses Console.Out.WriteLine() to write to the console in red.
Then Receive-Job checks for variable storing and stores stream 1 (success), 2 (error), 3 (warning) and 6 (info).
Finally Receive-Job uses Console.Out.WriteLine() to write stream 1, 3, 4, 5 and 6 with different ForegroundColors to the console.
That's why you can capture ALL those 6 stream outputs with Console.SetOut(), even the error stream output, for which I had expected Console.SetError() would be needed.
But there is an issue in those conclusions:
The output of Write-Host is written to the console by default and its output is added to the information variable.
So Write-Host maybe just write into stream 6.
But the output of Write-Information is not visible on the console by default, but is also added to the information variable.
So Write-Information can't just share the same IPC pipe with Write-Host.
And Write-Warning can write to the console and the variable independently, so only one stream/pipe couldn't be used here, too.
Have a look at my diagram for that issue.
Receive-Job output transport diagram:
You can verify the diagram by redirecting stream 1-6 in the code block and stream 1 or 2 in the script.
|<-------- code block process -------->|<-- IPC -->|<-------------------- script process ------------------->|
Method Preference Stream Stream/Variable Console output
Write-Out * --> 1 --> PIPE 1 --> 1 --> Console.Out.Write(gray)
PIPE 1 --> Out Variable
Write-Error * --> 2 --> PIPE 2 --> 2 --> Console.Out.Write(red)
PIPE 2 --> Error Variable
Write-Warning Continue ----??????---> PIPE 3 --> Warning Variable
Write-Warning Continue --> 3 --> PIPE 4 --> Console.Out.Write(yellow)
Write-Verbose Continue --> 4 --> PIPE 4 --> Console.Out.Write(yellow)
Write-Debug Continue --> 5 --> PIPE 4 --> Console.Out.Write(yellow)
Write-Information Continue --> 6 --> PIPE 6 --> Console.Out.Write(gray)
Write-Information * ----??????---> PIPE 5 --> Information Variable
Write-Host * ----??????---> PIPE 5 --> Information Variable
Write-Host * --> 6 --> PIPE 6 --> Console.Out.Write(gray)
IPC : Inter Process Communication
* : always, independent from Preference or has no own Preference
There is no redirection you can add after Write-Information or Write-Warning to prevent storing in their variables.
If you'd redirect 3 and 6 after the methods, then it would only affect the console output, not the variable storing.
Only when $InformationPreference (not default) or $WarningPreference (default) are set to Continue, they write into stream 6 or 3, whose are always written in gray or yellow color to the console of the script process.
And only Write-Warning needs preference Continue to store in its variable, Write-Informations always writes to its variable.
Question:
How can 'Write-Warning' and 'Write-Information' pass their output to their assigned variables in the script process ?
(They can't use stream 7,8,9, since they don't exists in windows.)
Best practice:
After the call of Job-Start you should Start-Sleep 1-3 seconds to give the code block time to start or fail.
Then use Receive-Job the first time to get the current progress, start debug info, warning or errors.
You should not use Wait-Job, but use your own loop to check for the job's running state and check a timeout by yourself.
In that own wait loop, you call Receive-Job every X seconds to get progress, debug and error information from the code block process.
When the job's state is finished or failed, you call Receive-Job a last time to get the remaining data of all the buffers.
To redirect/capture stream 1 (success) and 2 (error) you can use normal redirection in the Receive-Job line or storing to the variables.
To capture stream 3 (warning) and 6 (info & Write-Host) you have to use the variable storing.
You can't redirect or capture stream 4 (verbose) or 5 (debug) directly, but you could redirect (4>&1 or 5>&1) those streams in the code block to stream 1 (success) to add them to the output variable.
To supress console output of Write-Output or Write-Error, you can just redirect stream 1 or 2 in the Receive-Job line.
You don't have to supress console output of Write-Information, Write-Verbose or Write-Debug, since they don't write to console with their default preferences.
If you want to capture the output of Write-Information in the assigned variable without console output, you have to redirect stream 6: Write-Information <message> 6>$null.
To supress console output of Write-Warning or Write-Host, you have to redirect stream 3 or 6 in their call lines: Write-Warning <message> 3>$null and Write-Host <message> 6>$null.
Be aware:
If you redirect stream success (1) or error (2) in the code block, they will not be tranfered to the script process, not written to the console and not be stored in the output or error variable.
You are a bit hard to follow with your terminology use but I will do my best with my limited experience.
The output of Write-Host is written to the console by default and its output is added to the information variable.
So Write-Host maybe just write into stream 6.
But the output of Write-Information is not visible on the console by default, but is also added to the information variable.
So Write-Information can't just share the same IPC pipe with Write-Host.
First of all, I read somewhere (do not remember so cannot link, sorry) and confirmed for myself that Write-Host and Write-Information do, in fact, use the same stream. However, Write-Host is, essentially, a special case of Write-Information whereby it ignores the preference variable and always writes. So I would expect Write-Information to show up in its respective variable when the respective preference variable is set properly.
And Write-Warning can write to the console and the variable independently, so only one stream/pipe couldn't be used here, too.
This observation is likely a design choice. (I am guessing here) I expect it works similar to the Tee-Object cmdlet so it can, indeed, write to the console and variable despite only being one stream.
$result = 'some string' | Tee-Object -Variable var
Write-Host $result
Write-Host $var
# same string in both variables

Redirect Write-Host statements to a file

I have a PowerShell script that I am debugging and would like to redirect all Write-Host statements to a file. Is there an easy way to do that?
Until PowerShell 4.0, Write-Host sends the objects to the host. It does not return any objects.
Beginning with PowerShell 5.0 and newer, Write-Host is a wrapper for Write-Information, which allows to output to the information stream and redirect it with 6>> file_name.
http://technet.microsoft.com/en-us/library/hh849877.aspx
However, if you have a lot of Write-Host statements, replace them all with Write-Log, which lets you decide whether output to console, file or event log, or all three.
Check also:
Add-Content
redirection operators like >, >>, 2>, 2>, 2>&1
Write-Log
Tee-Object
Start-Transcript.
You can create a proxy function for Write-Host which sends objects to the standard output stream instead of merely printing them. I wrote the below cmdlet for just this purpose. It will create a proxy on the fly which lasts only for the duration of the current pipeline.
A full writeup is on my blog here, but I've included the code below. Use the -Quiet switch to suppress the console write.
Usage:
PS> .\SomeScriptWithWriteHost.ps1 | Select-WriteHost | out-file .\data.log # Pipeline usage
PS> Select-WriteHost { .\SomeScriptWithWriteHost.ps1 } | out-file .\data.log # Scriptblock usage (safer)
function Select-WriteHost
{
[CmdletBinding(DefaultParameterSetName = 'FromPipeline')]
param(
[Parameter(ValueFromPipeline = $true, ParameterSetName = 'FromPipeline')]
[object] $InputObject,
[Parameter(Mandatory = $true, ParameterSetName = 'FromScriptblock', Position = 0)]
[ScriptBlock] $ScriptBlock,
[switch] $Quiet
)
begin
{
function Cleanup
{
# Clear out our proxy version of write-host
remove-item function:\write-host -ea 0
}
function ReplaceWriteHost([switch] $Quiet, [string] $Scope)
{
# Create a proxy for write-host
$metaData = New-Object System.Management.Automation.CommandMetaData (Get-Command 'Microsoft.PowerShell.Utility\Write-Host')
$proxy = [System.Management.Automation.ProxyCommand]::create($metaData)
# Change its behavior
$content = if($quiet)
{
# In quiet mode, whack the entire function body,
# simply pass input directly to the pipeline
$proxy -replace '(?s)\bbegin\b.+', '$Object'
}
else
{
# In noisy mode, pass input to the pipeline, but allow
# real Write-Host to process as well
$proxy -replace '(\$steppablePipeline\.Process)', '$Object; $1'
}
# Load our version into the specified scope
Invoke-Expression "function ${scope}:Write-Host { $content }"
}
Cleanup
# If we are running at the end of a pipeline, we need
# to immediately inject our version into global
# scope, so that everybody else in the pipeline
# uses it. This works great, but it is dangerous
# if we don't clean up properly.
if($pscmdlet.ParameterSetName -eq 'FromPipeline')
{
ReplaceWriteHost -Quiet:$quiet -Scope 'global'
}
}
process
{
# If a scriptblock was passed to us, then we can declare
# our version as local scope and let the runtime take
# it out of scope for us. It is much safer, but it
# won't work in the pipeline scenario.
#
# The scriptblock will inherit our version automatically
# as it's in a child scope.
if($pscmdlet.ParameterSetName -eq 'FromScriptBlock')
{
. ReplaceWriteHost -Quiet:$quiet -Scope 'local'
& $scriptblock
}
else
{
# In a pipeline scenario, just pass input along
$InputObject
}
}
end
{
Cleanup
}
}
You can run your script in a secondary PowerShell shell and capture the output like this:
powershell -File 'Your-Script.ps1' > output.log
That worked for me.
Using redirection will cause Write-Host to hang. This is because Write-Host deals with various formatting issues that are specific to the current terminal being used. If you just want your script to have flexibility to output as normal (default to shell, with capability for >, 2>, etc.), use Write-Output.
Otherwise, if you really want to capture the peculiarities of the current terminal, Start-Transcript is a good place to start. Otherwise you'll have to hand-test or write some complicated test suites.
Try adding a asterisk * before the angle bracket > to redirect all streams:
powershell -File Your-Script.ps1 *> output.log
When stream redirection is requested, if no specific stream is indicated then by default only the Success Stream(1>) is redirected. Write-Host is an alias for Write-Information which writes to the Information Stream (6>). To redirect all streams use *>.
Powershell-7.1 supports redirection of multiple output streams:
Success Stream (#1): PowerShell 2.0 Write-Output
Error Stream (#2): PowerShell 2.0 Write-Error
Warning Stream (#3): PowerShell 3.0 Write-Warning
Verbose Stream (#4): PowerShell 3.0 Write-Verbose
Debug Stream (#5): PowerShell 3.0 Write-Debug
Information Stream (#6): PowerShell 5.0 Write-Information
All Streams (*): PowerShell 3.0
This worked for me in my first PowerShell script that I wrote few days back:
function logMsg($msg)
{
Write-Output $msg
Write-Host $msg
}
Usage in a script:
logMsg("My error message")
logMsg("My info message")
PowerShell script execution call:
ps> .\myFirstScript.ps1 >> testOutputFile.txt
It's not exactly answer to this question, but it might help someone trying to achieve both logging to the console and output to some log file, doing what I reached here :)
Define a function called Write-Host. Have it write to a file. You may have some trouble if some invocations use a weird set of arguments. Also, this will only work for invocations that are not Snapin qualified.
If you have just a few Write-Host statements, you can use the "6>>" redirector operator to a file:
Write-Host "Your message." 6>> file_path_or_file_name
This is the "Example 5: Suppress output from Write-Host" provided by Microsoft, modified accordingly to about_Operators.
I just added Start-Transcript at the top of the script and Stop-Transcript at the bottom.
The output file was intended to be named <folder where script resides>-<datestamp>.rtf, but for some reason the trace file was being put where I did not expect it — the desktop!
You should not use Write-Host if you wish to have the messages in a file. It is for writing to the host only.
Instead you should use a logging module, or Set/Add-Content.
I have found the best way to handle this is to have a logging function that will detect if there is a host UI and act accordingly. When the script is executed in interactive mode it will show the details in the host UI, but when it is run via WinRM or in a non-interactive mode it will fall back on the Write-Output so that you can capture it using the > or *> redirection operators
function Log-Info ($msg, $color = "Blue") {
if($host.UI.RawUI.ForegroundColor -ne $null) {
Write-Host "`n[$([datetime]::Now.ToLongTimeString())] $msg" -ForegroundColor $color -BackgroundColor "Gray"
} else {
Write-Output "`r`n[$([datetime]::Now.ToLongTimeString())] $msg"
}
}
In cases where you want to capture the full output with the Write-Host coloring, you can use the Get-ConsoleAsHtml.ps1 script to export the host's scrolling buffer to an HTML or RTF file.
Use Write-Output instead of Write-Host, and redirect it to a file like this:
Deploy.ps1 > mylog.log or Write-Output "Hello World!" > mylog.log
Try using Write-Output instead of Write-Host.
The output goes down the pipeline, but if this is the end of the pipe, it goes to the console.
> Write-Output "test"
test
> Write-Output "test" > foo.txt
> Get-Content foo.txt
test