Redirect Write-Host statements to a file
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.
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
}
}
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 forWrite-Information
, which allows to output to the information stream and redirect it with6>> 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
.
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.