PowerShell:为什么我有时无法重定向外部方法的stdout和stderr?

时间:2018-06-02 17:16:05

标签: powershell redirect stdout stderr

我遇到了从非.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

1 个答案:

答案 0 :(得分:2)

<强> TL;博士

  • 如果要捕获通过[Console] API生成输出的进程内代码的输出,必须 使用显式重定向{ {1}}和[Console]::SetOut(),您在问题中提到的技术。

  • 请参阅下文,了解为何需要这样做。

PowerShell仅允许捕获/重定向外部(控制台)程序的标准[Console]::SetError()stdout,其中,在此情况下基于.NET的程序,stderrConsole.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流,在没有外部重定向的情况下,将其打印到屏幕上