PowerShell: Why I can't redirect stdout and stderr of external methods sometimes?
tl;dr:
If you want to capture output from in-process code that produces output via the
[Console]
API, you must use explicit redirections via[Console]::SetOut()
and[Console]::SetError()
, the technique you mention in the question.See below for why that is necessary.
PowerShell only allows capturing / redirecting the standard stdout
and stderr
streams of external (console) programs, inside which, in the case of .NET-based programs, Console.WriteLine()
and Console.Out.WriteLine()
write to stdout
, and Console.Error.WriteLine()
writes to stderr
.
When running in a console window, PowerShell passes an external program's stdout and stderr streams through to the console (screen) by default; by contrast, the ISE sends stderr output to PowerShell's error stream[1].
>
or 1>
redirects an external program's stdout (either to a file or to $null
to suppress it), 2>
redirects stderr[2].
Additionally, assigning output from an external program to a variable captures its stdout output, and sending an external program's output through the pipeline redirects its stdout output to PowerShell's success output stream.
By contrast, you're using the [Console]
type's output methods in-process, where no such capturing is possible, because such method calls simply output to the same console that PowerShell itself runs in, without PowerShell knowing about it.[3]
You can cut out the middleman to verify this behavior:
PS> [Console]::WriteLine('hi') *> $null # Try to suppress ALL output streams.hi # !! Still prints to the console - PowerShell streams were bypassed.
The only way to (temporarily) redirect [Console]
output in-process is to explicitly call .SetOut()
and .SetError()
, as mentioned in the question.
The reason that 2> $NULL
in &{ [System.Net.Dns]::Resolve('bla') } 2> $NULL
does work is that the method throws an exception, which PowerShell outputs to its error stream (stream number 2
), whose output 2> $NULL
effectively suppresses.
Note that, because an exception is thrown, 2> $NULL
is only effective because the method call is enclosed in & { ... }
; otherwise, the exception would terminate the redirection itself too.
However, with respect to in-process [Console]
behavior with no exceptions involved, whether & { ... }
is involved or not makes no difference.
Therefore, in order for your custom C# method to integrate with PowerShell's streams - short of using PowerShell APIs directly in your C# code - do the following:
use
return
for what should go to PowerShell's success stream (stream number1
)throw an exception for what should go to PowerShell's error stream (stream number
2
), but note that an unhandled exception will by default abort the enclosing statement as a whole.
Alternatively, compile your C# code to an external program, say, godemo.exe
:
# With an external executable, redirections work as expected.godemo.exe 1> $null 2> $null
[1] This divergent behavior in the ISE is problematic; it is discussed in this GitHub issue.
[2] If $ErrorActionPreference = 'Stop'
happens to be in effect, any 2>
redirection unexpectedly causes a script-terminating error; this problematic behavior is discussed in this GitHub issue.
[3] The methods write to the current console's stdout
and stderr
streams, which, in the absence of an external redirection, print to the screen.