PowerShell:为什么有时无法重定向外部方法的标准输出和错误输出?

3

我遇到了一些问题,无法重定向非 .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
PowerShell 引擎不知道 Console.OutConsole.Error,当然也不会重定向它们。 - user4003407
这与当前目录的情况非常相似。有PowerShell引擎的当前目录/路径,可以使用Set-Location(别名cd)进行设置,但也有可通过[Environment] :: CurrentDirectory获得的PowerShell进程的当前目录。涉及文件的.NET调用使用进程的当前目录而不是PowerShell引擎的当前目录。 - Mike Zboray
@mikez:不,这里的问题是进程内与外部程序,而不是PowerShell与.NET:在进程内使用[Console] API只需简单地打印到控制台,除非通过.SetOut() / 和 .SetError()方法应用显式重定向,否则同一进程中的其他代码 - 无论是C#还是PowerShell - 都看不到该输出。 - mklement0
1个回答

2

tl;dr:

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

  • 请参见下文,了解为什么需要这样做。


PowerShell仅允许捕获/重定向外部(控制台)程序的标准stdoutstderr,在其中,在基于.NET的程序的情况下,Console.WriteLine()Console.Out.WriteLine()写入stdout,而Console.Error.WriteLine()写入stderr

在控制台窗口中运行时,PowerShell默认情况下将外部程序的stdout和stderr流传递到控制台(屏幕);相比之下,ISE将stderr输出发送到PowerShell的错误流[1]

>1>重定向外部程序的stdout(将其重定向到文件或$null以抑制它),2>重定向stderr[2]
此外,将外部程序的输出分配给变量会捕获其stdout输出,并将外部程序的输出通过管道发送会将其stdout输出重定向到PowerShell的成功输出流。


相比之下,您正在使用[Console]类型的输出方法进程内,因此不可能进行此类捕获,因为这样的方法调用只是将输出输出到PowerShell本身运行的同一控制台,而PowerShell并不知道它。[3]

您可以切掉中间人来验证这种行为:

PS> [Console]::WriteLine('hi')  *> $null # Try to suppress ALL output streams.
hi  # !! Still prints to the console - PowerShell streams were bypassed.

唯一在进程中(暂时)重定向[Console]输出的方法是显式调用.SetOut().SetError(),正如问题所述。
之所以&{ [System.Net.Dns]::Resolve('bla') } 2> $NULL中的2> $NULL有效,是因为该方法会抛出一个异常,PowerShell将其输出到其错误流(流编号2),而2> $NULL的输出有效地被抑制了。
请注意,由于会抛出一个异常,2> $NULL只有在方法调用被包含在& { ... }中时才有效;否则,异常将终止重定向本身。
然而,对于没有涉及异常的进程内[Console]行为,无论是否涉及& { ... }都没有区别。
因此,为使您的自定义C#方法与PowerShell的流集成 - 除了直接在C#代码中使用PowerShell API外 - 请执行以下操作:
  • 使用return来处理应发送到PowerShell的成功流(流编号1

  • 抛出异常来处理应发送到PowerShell的错误流(流编号2),但请注意,未处理的异常将默认中止整个语句。

或者,将您的C#代码编译为一个外部程序,比如godemo.exe
# With an external executable, redirections work as expected.
godemo.exe 1> $null 2> $null 

[1] 在ISE中这种分歧行为是有问题的,详情请参见此GitHub问题

[2] 如果$ErrorActionPreference = 'Stop'生效,任何2>重定向会意外地导致脚本终止错误;这种问题性行为在此GitHub问题中有所讨论。

[3] 这些方法写入当前控制台的stdoutstderr流,在缺少外部重定向的情况下会打印到屏幕上。


我只是使用 &{ } 包装了这些调用,因为它被用作类似问题的解决方法。 - andiDo
@andiDo:只有在涉及异常(例如System.Net.Dns]::Resolve('bla'))时,使用& { ... }才会有区别;对于[Console]::WriteLine()等调用,这并不重要。请参见我的更新。 - mklement0
1
@mklement0 鉴于将所有stderr输出视为错误是没有意义的,我不能说我对PowerShell的这一部分实现感到满意,并且我没有意图争论stderr应该如何处理,但这并不改变ISE一贯通过Out-Default传递所有成功和错误输出以在ISE控制台上显示结果的事实,而控制台主机则会产生异常,并有时将stdoutstderr直接补丁到控制台,绕过Out-Default并放弃解释stderr作为ErrorRecord的能力,从而产生差异。 - user4003407
1
我不理解你对于交互式控制台应用程序的观点。如果你总是重定向输出(如果你想要将所有内容传递给 Out-Default),那么控制台应用程序将无法看到真正的控制台句柄在它们的 stdoutstderr 上,因此你需要在某些情况下进行例外处理,并直接将它们补丁到控制台中,以使需要这种句柄的应用程序正常工作。注意:你不能始终将 stdoutstderr 直接补丁到控制台,因为你不总是有本地控制台可供补丁(图形主机、远程、作业)。 - user4003407
好的信息,谢谢@PetSerAl。关于stderr:没有好的解决方案,因为PowerShell的错误流只是用于错误,而stderr-尽管它的名称是这样-并不是,因此将非错误stderr输出格式化为PowerShell错误(多行,红色)是无益且令人困惑的(撇开有关PowerShell默认错误格式的争论-请参见https://github.com/PowerShell/PowerShell/issues/3647)。 - mklement0
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接