Note:
This answer is about writing to stderr from the perspective of the outside world when a PowerShell script is called from there; while the answer is written from the perspective of the Windows shell, cmd.exe
, it equally applies to Unix shells such as bash
when combined with PowerShell Core.
By contrast, from within Powershell, you should use Write-Error
, as explained in Keith Hill's answer.
Sadly, there is no unified approach that will work from both within PowerShell and from the outside - see this answer of mine for a discussion.
To add to @Chris Sear's great answer:
While $host.ui.WriteErrorLine
should work in all hosts, it doesn't (by default) write to stderr when invoked via cmd.exe
, such as from a batch file.
[Console]::Error.WriteLine
, by contrast, always does.
So if you want to write a PowerShell script that plays nicely in terms of output streams when invoked from cmd.exe
, use the following function, Write-StdErr
, which uses [Console]::Error.WriteLine
in the regular PS / cmd.exe
host (console window), and $host.ui.WriteErrorLine
otherwise:
<#
.SYNOPSIS
Writes text to stderr when running in a regular console window,
to the host''s error stream otherwise.
.DESCRIPTION
Writing to true stderr allows you to write a well-behaved CLI
as a PS script that can be invoked from a batch file, for instance.
Note that PS by default sends ALL its streams to *stdout* when invoked from
cmd.exe.
This function acts similarly to Write-Host in that it simply calls
.ToString() on its input; to get the default output format, invoke
it via a pipeline and precede with Out-String.
#>
function Write-StdErr {
param ([PSObject] $InputObject)
$outFunc = if ($Host.Name -eq 'ConsoleHost') {
[Console]::Error.WriteLine
} else {
$host.ui.WriteErrorLine
}
if ($InputObject) {
[void] $outFunc.Invoke($InputObject.ToString())
} else {
[string[]] $lines = @()
$Input | % { $lines += $_.ToString() }
[void] $outFunc.Invoke($lines -join "`r`n")
}
}
Optional background information: How the PowerShell CLI's output streams are seen by outside callers:
Internally, PowerShell has more than the traditional output streams (stdout and stderr), and their count has increased over time (try Write-Warning "I'll go unheard." 3> $null
as an example, and read more at Get-Help about_Redirection
.
When interfacing with the outside world, PowerShell must map the non-traditional output streams to stdout and stderr.
Strangely, however, PowerShell by default sends all its streams (including Write-Host
and $host.ui.WriteErrorLine()
output) to stdout when invoked from cmd.exe
, even though mapping PowerShell's error stream to stderr would be the logical choice. This behavior has been in effect since (at least) v2 and still applies as of v5.1 (and probably won't change for reasons of backward compatibility - see GitHub issue #7989).
You can verify this with the following command, if you invoke it from cmd.exe
:
powershell -noprofile -command "'out'; Write-Error 'err'; Write-Warning 'warn'; Write-Verbose -Verbose 'verbose'; $DebugPreference='Continue'; write-debug 'debug'; $InformationPreference='Continue'; Write-Information 'info'; Write-Host 'host'; $host.ui.WriteErrorLine('uierr'); [Console]::Error.WriteLine('cerr')" >NUL
The command writes to all PowerShell output streams (when you run on a pre-PowerShell-v5 version, you'll see an additional error message relating to Write-Information
, which was introduced in PowerShell v5) and has cmd.exe
redirect stdout only to NUL
(i.e., suppress stdout output; >NUL
).
You will see no output except cerr
(from [Console]::Error.WriteLine()
, which writes directly to stderr) - all of PowerShell's streams were sent to stdout.
Perhaps even more strangely, it is possible to capture PowerShell's error stream, but only with a redirection:
If you change >NUL
to 2>NUL
above, it is exclusively PowerShell's error stream and $host.ui.WriteErrorLine()
output that will be suppressed; of course, as with any redirection, you can alternatively send it to a file.
(As stated, [Console]::Error.WriteLine()]
always outputs to stderr, whether the latter is redirected or not.)
To give a more focused example (again, run from cmd.exe
):
powershell -noprofile -command "'out'; Write-Error 'err'" 2>NUL
The above only outputs out
- Write-Error
's output is suppressed.
To summarize:
Without any (cmd.exe
) redirection or with only a stdout redirection (>...
or 1>...
), PowerShell sends all its output streams to stdout.
With a stderr redirection (2>...
), PowerShell selectively sends its error stream to stderr (irrespective of whether stdout is also redirected or not).
As a corollary, the following common idiom does not work as expected:
powershell ... >data-output.txt
This will not, as one might expect, send only stdout to file data-output.txt
while printing stderr output to the terminal; instead, you'd have to use
powershell ... >data-output.txt 2>err-output.tmp; type err-output.tmp >&2; del err-output.tmp
It follows that PowerShell is aware of cmd.exe
's redirections and adjusts its behavior intentionally.
(This is also evident from PowerShell producing colored output in the cmd.exe
console while stripping the color codes when output is redirected to a file.)
Write-Error
to cause my script to terminate because I use$ErrorActionPreference = "Stop"
, which I find more valuable. – Airboat