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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With