PowerShell:为什么我有时无法重定向外部方法的标准输出和标准错误?
PowerShell: Why I can't redirect stdout and stderr of external methods sometimes?
我在重定向非 .NET 程序集方法调用的输出时遇到问题:
在下面的代码中,您会看到一次成功的 .NET 重定向 class System.Net.Dns 和两次失败的重定向。
一个是内联C#类型,另一个是VS编译的.dll,只包含与$cs_code代码块相同的内容。
到目前为止,我唯一的解决方法是使用 [Console]::SetOut 和 [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:
如果您想从 进程中 代码中捕获通过 [Console]
API 生成输出的输出,您 必须 通过 [Console]::SetOut()
和 [Console]::SetError()
使用显式重定向,您在问题中提到的技术。
请参阅下文了解为什么需要这样做。
PowerShell 仅允许捕获/重定向 外部(控制台)程序的标准 stdout
和 stderr
流,其中,对于基于 .NET 的程序,Console.WriteLine()
和 Console.Out.WriteLine()
写入 stdout
,Console.Error.WriteLine()
写入 stderr
。
当 运行 在控制台 window 中时,PowerShell 默认将外部程序的 stdout 和 stderr 流传递到控制台(屏幕);相比之下,ISE 将 stderr 输出发送到 PowerShell 的错误流[1].
>
或 1>
重定向外部程序的标准输出(到文件或 $null
以抑制它),2>
重定向 stderr [2].
此外,将外部程序的输出分配给 variable 会捕获其标准输出,并通过 pipeline 发送外部程序的输出会将其标准输出重定向到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()
and .SetError()
,如问题中所述。
2> $NULL
in &{ [System.Net.Dns]::Resolve('bla') } 2> $NULL
确实 工作的原因是该方法抛出一个 异常 ,它PowerShell 输出到它的错误流(流编号2
),其输出2> $NULL
有效地抑制了。
请注意,因为抛出了 异常 ,所以 2> $NULL
仅在方法调用包含在 & { ... }
中时才有效;否则,异常也会终止重定向本身。
但是,对于不涉及异常的进程中[Console]
行为,是否涉及& { ... }
没有区别。
因此,为了让您的自定义 C# 方法与 PowerShell 的流集成 - 除了直接在您的 C# 代码中使用 PowerShell APIs - 执行以下操作:
将 return
用于应该进入 PowerShell 成功流的内容(流编号 1
)
抛出应该进入 PowerShell 错误流的异常 (流编号 2
),但请注意,默认情况下未处理的异常将中止整个封闭语句。
或者,将您的 C# 代码编译成外部程序,比如说,godemo.exe
:
# With an external executable, redirections work as expected.
godemo.exe 1> $null 2> $null
[1] ISE 中的这种发散行为是有问题的; this GitHub issue.
中对此进行了讨论
[2] 如果 $ErrorActionPreference = 'Stop'
恰好生效,任何 2>
重定向都会意外导致脚本终止错误; this GitHub issue.
中讨论了这种有问题的行为
[3] 方法写入当前控制台的 stdout
和 stderr
流,在没有 external 重定向,打印到屏幕。
我在重定向非 .NET 程序集方法调用的输出时遇到问题:
在下面的代码中,您会看到一次成功的 .NET 重定向 class System.Net.Dns 和两次失败的重定向。
一个是内联C#类型,另一个是VS编译的.dll,只包含与$cs_code代码块相同的内容。
到目前为止,我唯一的解决方法是使用 [Console]::SetOut 和 [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:
如果您想从 进程中 代码中捕获通过
[Console]
API 生成输出的输出,您 必须 通过[Console]::SetOut()
和[Console]::SetError()
使用显式重定向,您在问题中提到的技术。请参阅下文了解为什么需要这样做。
PowerShell 仅允许捕获/重定向 外部(控制台)程序的标准 stdout
和 stderr
流,其中,对于基于 .NET 的程序,Console.WriteLine()
和 Console.Out.WriteLine()
写入 stdout
,Console.Error.WriteLine()
写入 stderr
。
当 运行 在控制台 window 中时,PowerShell 默认将外部程序的 stdout 和 stderr 流传递到控制台(屏幕);相比之下,ISE 将 stderr 输出发送到 PowerShell 的错误流[1].
>
或 1>
重定向外部程序的标准输出(到文件或 $null
以抑制它),2>
重定向 stderr [2].
此外,将外部程序的输出分配给 variable 会捕获其标准输出,并通过 pipeline 发送外部程序的输出会将其标准输出重定向到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()
and .SetError()
,如问题中所述。
2> $NULL
in &{ [System.Net.Dns]::Resolve('bla') } 2> $NULL
确实 工作的原因是该方法抛出一个 异常 ,它PowerShell 输出到它的错误流(流编号2
),其输出2> $NULL
有效地抑制了。
请注意,因为抛出了 异常 ,所以 2> $NULL
仅在方法调用包含在 & { ... }
中时才有效;否则,异常也会终止重定向本身。
但是,对于不涉及异常的进程中[Console]
行为,是否涉及& { ... }
没有区别。
因此,为了让您的自定义 C# 方法与 PowerShell 的流集成 - 除了直接在您的 C# 代码中使用 PowerShell APIs - 执行以下操作:
将
return
用于应该进入 PowerShell 成功流的内容(流编号1
)抛出应该进入 PowerShell 错误流的异常 (流编号
2
),但请注意,默认情况下未处理的异常将中止整个封闭语句。
或者,将您的 C# 代码编译成外部程序,比如说,godemo.exe
:
# With an external executable, redirections work as expected.
godemo.exe 1> $null 2> $null
[1] ISE 中的这种发散行为是有问题的; this GitHub issue.
中对此进行了讨论[2] 如果 $ErrorActionPreference = 'Stop'
恰好生效,任何 2>
重定向都会意外导致脚本终止错误; this GitHub issue.
[3] 方法写入当前控制台的 stdout
和 stderr
流,在没有 external 重定向,打印到屏幕。