我遇到了从非.NET程序集方法调用重定向输出的问题:
在下面的代码中,您看到一个成功的重定向与.NET类System.Net.Dns和两个失败的重定向。
一个使用内联C#类型,另一个是VS编译的.dll,它只包含与$ cs_code代码块相同的内容。
到目前为止,我唯一的解决方法是使用[Console] :: SetOut和[Console] :: SetError来捕获它们的输出。
但为什么它们会失败,我如何重定向/捕获这些流 输出?
# .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
答案 0 :(得分:2)
<强> TL;博士强>:
如果要捕获通过[Console]
API生成输出的进程内代码的输出,必须 使用显式重定向{ {1}}和[Console]::SetOut()
,您在问题中提到的技术。
请参阅下文,了解为何需要这样做。
PowerShell仅允许捕获/重定向外部(控制台)程序的标准[Console]::SetError()
和stdout
流,其中,在此情况下基于.NET的程序,stderr
和Console.WriteLine()
写入Console.Out.WriteLine()
,stdout
写入Console.Error.WriteLine()
。
在控制台窗口中运行时,PowerShell默认将外部程序的stdout和stderr流传递到控制台(屏幕);相反,ISE将stderr输出发送到PowerShell的错误流 [1] 。
stderr
或>
重定向外部程序的stdout(文件或1>
以禁止它),$null
重定向stderr [2] < / SUP>。
此外,将外部程序的输出分配给变量可捕获其stdout输出,并通过管道发送外部程序的输出,将其stdout输出重定向到PowerShell的成功输出流。
相比之下,您正在使用2>
类型的输出方法进程内,其中无法进行此类捕获,因为此类方法调用只是输出到PowerShell本身运行的同一个控制台,没有PowerShell知道它。 [3]
您可以删除中间人以验证此行为:
[Console]
(暂时)重定向PS> [Console]::WriteLine('hi') *> $null # Try to suppress ALL output streams.
hi # !! Still prints to the console - PowerShell streams were bypassed.
输出进程的唯一方法是明确调用.SetOut()
和.SetError()
,如问题中所述。
[Console]
中的2> $NULL
工作的原因是该方法抛出异常,PowerShell输出其错误流(流号&{ [System.Net.Dns]::Resolve('bla') } 2> $NULL
),其输出2
有效抑制。
请注意,由于抛出了异常,2> $NULL
仅有效,因为方法调用包含在2> $NULL
中;否则,异常也会终止重定向。
但是,对于没有涉及例外的进程内& { ... }
行为,是否涉及[Console]
没有任何区别。
因此,为了使您的自定义C#方法与PowerShell的流集成 - 不在您的C#代码中直接使用PowerShell API - 请执行以下操作:
使用& { ... }
了解PowerShell成功流程的内容(流编号return
)
抛出应该转到PowerShell错误流的异常(流号1
),但请注意,默认情况下,未处理的异常会中止整个封闭语句
或者,将您的C#代码编译为外部程序 ,例如2
:
godemo.exe
[1] ISE中的这种不同行为是有问题的;它在this GitHub issue中讨论。
[2]如果# With an external executable, redirections work as expected.
godemo.exe 1> $null 2> $null
恰好有效,任何$ErrorActionPreference = 'Stop'
重定向都会意外地导致脚本终止错误; this GitHub issue中讨论了这种有问题的行为。
[3]这些方法写入当前控制台的2>
和stdout
流,在没有外部重定向的情况下,将其打印到屏幕上