I have issues with redirecting output from non .NET assemblies method calls:
In the code below, you see one successful redirection with .NET class System.Net.Dns and two failed redirections.
One with an inline C# type and the other is a VS compiled .dll, which contains only the same content as the $cs_code codeblock.
My only workaround so far is to catch their output with [Console]::SetOut and [Console]::SetError.
But why do they fail and how can I redirect/capture those stream outputs ?
# .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
if ($psISE) { cls }
$cs_code = @"
using System;
static public class demo
{
static public void go()
{
Console.WriteLine("***Console.WriteLine***");
Console.Out.WriteLine("***Console.Out.WriteLine***");
//Console.Out.Flush(); // no effect here
Console.Error.WriteLine("***Console.Error.WriteLine***"); // no output in ISE !
//Console.Error.Flush(); // no effect here
}
}
"@
Add-Type -TypeDefinition $cs_code -Language CSharp
#[Console]::SetOut((New-Object IO.StringWriter)) # this would catch all stdout
#[Console]::SetError((New-Object IO.StringWriter)) # this would catch all stderr
&{ [demo]::go() } 1> $null 2> $NULL # no redirection, why ?
# &{ [System.Net.Dns]::Resolve('bla') } 2> $NULL # works as expected
exit 0
Add-Type -AssemblyName 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' `
-ErrorAction Stop
Add-Type -Path "c:\_ClassLibraryDemo.dll" `
-ErrorAction Stop
&{ [MyLib.Demo]::Go() } 1> $null 2> $null // no effect here
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 number 1
)
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.