PowerShell 流式输出
PowerShell Streaming Output
我想在 PowerShell 中捕获一些流输出。例如
cmd /c "echo hi && foo"
此命令应先打印 hi 然后再打印 bomb。我知道我可以使用 -ErrorVariable:
Invoke-Command { cmd /c "echo hi && foo" } -ErrorVariable ev
但是有一个问题:在长 运行 命令的情况下,我想流式传输输出,而不是捕获它并且只在命令末尾获得 stderr/stdout 输出
理想情况下,我希望能够拆分 stderr 和 stdout 并通过管道传输到两个不同的流 - 并将 stdout 通过管道返回给调用者,但要准备好在出现错误时抛出 stderr。像
$stdErr
Invoke-Command "cmd" "/c `"echo hi && foo`"" `
-OutStream (Get-Command Write-Output) `
-ErrorAction {
$stdErr += "`n$_"
Write-Error $_
}
if ($lastexitcode -ne 0) { throw $stdErr}
我能得到的最接近的是使用管道,但这并不能让我区分 stdout 和 stderr,所以我最终抛出了整个输出流
function Invoke-Cmd {
<#
.SYNOPSIS
Executes a command using cmd /c, throws on errors.
#>
param([string]$Cmd)
)
$out = New-Object System.Text.StringBuilder
# I need the 2>&1 to capture stderr at all
cmd /c $Cmd '2>&1' |% {
$out.AppendLine($_) | Out-Null
$_
}
if ($lastexitcode -ne 0) {
# I really just want to include the error stream here
throw "An error occurred running the command:`n$($out.ToString())"
}
}
常用用法:
Invoke-Cmd "GitVersion.exe" | ConvertFrom-Json
请注意,仅使用 ScriptBlock 的类似版本(并检查 [ErrorRecord] 的输出流是不可接受的,因为有许多程序 "don't like" 直接从 PowerShell 进程执行
.NET System.Diagnostics.Process API 允许我这样做...但我无法从流处理程序内部流式传输输出(因为线程和阻塞 - 虽然我想我可以使用 while 循环和 stream/clear 收集的输出)
注意:下面的更新示例 应该 现在可以跨 PowerShell 主机工作。 GitHub 问题 Inconsistent handling of native command stderr 已经打开以跟踪前面示例中的差异。但是请注意,由于它取决于未记录的行为,因此该行为将来可能会发生变化。在必须耐用的解决方案中使用它之前,请考虑到这一点。
您在使用管道方面走在正确的轨道上,您可能几乎不需要 Invoke-Command。 Powershell 确实区分标准输出和标准错误。例如试试这个:
cmd /c "echo hi && foo" | set-variable output
stdout
被传送到设置变量,而标准错误仍然出现在您的屏幕上。如果你想隐藏和捕获 stderr
输出,试试这个:
cmd /c "echo hi && foo" 2>$null | set-variable output
2>$null
部分是一个未记录的技巧,导致错误输出作为 ErrorRecord
.
附加到 PowerShell $Error
变量
这是一个显示标准输出的示例,同时用 catch
块捕获 stderr
:
function test-cmd {
[CmdletBinding()]
param()
$ErrorActionPreference = "stop"
try {
cmd /c foo 2>$null
} catch {
$errorMessage = $_.TargetObject
Write-warning "`"cmd /c foo`" stderr: $errorMessage"
Format-list -InputObject $_ -Force | Out-String | Write-Debug
}
}
test-cmd
生成消息:
WARNING: "cmd /c foo" stderr: 'foo' is not recognized as an internal or external command
如果您在启用调试输出的情况下调用,您还会看到抛出的错误记录的详细信息:
DEBUG:
Exception : System.Management.Automation.RemoteException: 'foo' is not recognized as an internal or external command,
TargetObject : 'foo' is not recognized as an internal or external command,
CategoryInfo : NotSpecified: ('foo' is not re...ternal command,:String) [], RemoteException
FullyQualifiedErrorId : NativeCommandError
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at test-cmd, <No file>: line 7
at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}
PSMessageDetails :
设置$ErrorActionPreference="stop"
导致PowerShell在子进程写入stderr时抛出异常,这听起来像是你想要的核心。这个 2>$null
技巧使 cmdlet 和外部命令的行为非常相似。
- 描述的行为适用于 运行 PowerShell 常规 console/terminal windows 不涉及远程处理。使用远程处理和在 ISE 中,行为与 PSv5.1 不同 - 见底部。
- 2>$null
行为 relies on - 2>$null
secretly still writing to PowerShell's error stream and therefore, with $ErrorActionPreference
Stop
in effect, aborting the script as soon as an external utility writes anything to stderr - has been classified as a bug 并且可能会消失。
当 PowerShell 调用诸如 cmd
之类的外部实用程序时,其 stderr 输出默认直接通过。也就是说,stderr 输出直接打印到控制台,而不包含在捕获的输出中(无论是通过分配给变量还是重定向到文件)。
虽然您可以使用 2>&1
作为 cmd
命令行的一部分 ,但您将无法区分PowerShell 中的 stdout 和 stderr 输出。
相比之下,如果您使用 2>&1
作为 PowerShell 重定向,您可以根据输入对象的类型:
- 一个
[string]
实例是一个stdout行
- A
[System.Management.Automation.ErrorRecord]
实例是 stderr 行。
以下函数 Invoke-CommandLine
利用了这一点:
请注意,cmd /c
部分不是内置的,因此您可以按如下方式调用它,例如:
Invoke-CommandLine 'cmd /c "echo hi && foo"'
通过调用 cmd
命令行和直接调用外部实用程序(例如 git.exe
,但请注意,只有通过 cmd
调用才允许通过运算符 &
、&&
和 ||
使用 多个 命令,并且只有cmd
解释%...%
风格的环境变量引用,除非你使用--%
,停止解析符号。
Invoke-CommandLine
输出接收到的 stdout 和 stderr 行,因此您可以在管道中使用该函数。
如所写,stderr 行在接收时使用 Write-Error
写入 PowerShell 的错误流,带有 单个通用 异常在外部命令终止后被抛出,它应该报告一个非零 $LASTEXITCODE
.
适配函数很简单:
- 在收到第一行 stderr 后采取行动。
- 到收集单个变量中的所有stderr行
- and/or,终止后,如果收到 any stderr 输入,即使
$LASTEXITCODE
报告 0
.
Invoke-CommandLine
使用 Invoke-Expression
,所以通常的警告适用:确保你知道你传递的是什么命令行,因为它会按原样执行,不不管它包含什么。
function Invoke-CommandLine {
<#
.SYNOPSIS
Executes an external utility with stderr output sent to PowerShell's error '
stream, and an exception thrown if the utility reports a nonzero exit code.
#>
param([parameter(Mandatory)][string] $CommandLine)
# Note that using . { ... } is required around the Invoke-Expression
# call to ensure that the 2>&1 redirection works as intended.
. { Invoke-Expression $CommandLine } 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) { # stderr line
Write-Error $_ # send stderr line to PowerShell's error stream
} else { # stdout line
$_ # pass stdout line through
}
}
# If the command line signaled failure, throw an exception.
if ($LASTEXITCODE) {
Throw "Command failed with exit code ${LASTEXITCODE}: $CommandLine"
}
}
可选阅读:对外部实用程序的调用如何适应 PowerShell 的错误处理
当前版本:Windows PowerShell v5.1,PowerShell Core v6-beta.2
首选项变量的值 $ErrorActionPreference
仅控制对在 PowerShell cmdlet/function 调用中发生 的错误和 .NET 异常的反应或表达式.
Try
/ Catch
用于捕获 PowerShell 的终止错误 和 .NET异常.
在不涉及远程处理的常规控制台 window 中 ,cmd
等外部实用程序当前 从不生成任一个错误 - 他们所做的只是报告一个退出代码,PowerShell 在 自动变量 $LASTEXITCODE
中反映,自动变量 $?
在退出代码非零时反映 $False
.
注意:在控制台主机以外的主机中行为根本不同这一事实 - 包括 Windows ISE 和涉及远程处理时 - 是有问题的:调用外部实用程序导致 stderr 输出被视为已报告 非终止错误;具体来说:
- 每个 stderr 输出行都作为错误记录输出,并记录在自动
$Error
集合中。
- 在 addition 到
$?
中设置为 $false
且退出代码为非零,任何 stderr 输出的存在 also 将其设置为 $False
.
- 这种行为是有问题的,因为 stderr 输出本身并不一定表示 错误 - 只有 非零退出代码 是。
- Burt has created an issue in the PowerShell GitHub repository to discuss this inconsistency.
默认情况下,外部实用程序生成的标准错误输出直接传递到控制台 - 它们 不是 由 PowerShell 变量赋值或(成功流)输出重定向捕获。
如上所述,可以更改为:
2>&1
作为命令行的一部分传递给 cmd
发送标准输出和 stderr combined 到 PowerShell 的成功流,作为 strings,无法区分给定行是 stdout 还是 stderr 行。
2>&1
作为 PowerShell 重定向 也将标准错误行发送到 PowerShell 的成功流,但是 你可以通过数据类型 来区分源自 stdout 和 stderr 的行:[string]
类型的行是源自 stdout 的行,而 [System.Management.Automation.ErrorRecord]
类型的行line 是一个 stderr 起源的。
我想在 PowerShell 中捕获一些流输出。例如
cmd /c "echo hi && foo"
此命令应先打印 hi 然后再打印 bomb。我知道我可以使用 -ErrorVariable:
Invoke-Command { cmd /c "echo hi && foo" } -ErrorVariable ev
但是有一个问题:在长 运行 命令的情况下,我想流式传输输出,而不是捕获它并且只在命令末尾获得 stderr/stdout 输出
理想情况下,我希望能够拆分 stderr 和 stdout 并通过管道传输到两个不同的流 - 并将 stdout 通过管道返回给调用者,但要准备好在出现错误时抛出 stderr。像
$stdErr
Invoke-Command "cmd" "/c `"echo hi && foo`"" `
-OutStream (Get-Command Write-Output) `
-ErrorAction {
$stdErr += "`n$_"
Write-Error $_
}
if ($lastexitcode -ne 0) { throw $stdErr}
我能得到的最接近的是使用管道,但这并不能让我区分 stdout 和 stderr,所以我最终抛出了整个输出流
function Invoke-Cmd {
<#
.SYNOPSIS
Executes a command using cmd /c, throws on errors.
#>
param([string]$Cmd)
)
$out = New-Object System.Text.StringBuilder
# I need the 2>&1 to capture stderr at all
cmd /c $Cmd '2>&1' |% {
$out.AppendLine($_) | Out-Null
$_
}
if ($lastexitcode -ne 0) {
# I really just want to include the error stream here
throw "An error occurred running the command:`n$($out.ToString())"
}
}
常用用法:
Invoke-Cmd "GitVersion.exe" | ConvertFrom-Json
请注意,仅使用 ScriptBlock 的类似版本(并检查 [ErrorRecord] 的输出流是不可接受的,因为有许多程序 "don't like" 直接从 PowerShell 进程执行
.NET System.Diagnostics.Process API 允许我这样做...但我无法从流处理程序内部流式传输输出(因为线程和阻塞 - 虽然我想我可以使用 while 循环和 stream/clear 收集的输出)
注意:下面的更新示例 应该 现在可以跨 PowerShell 主机工作。 GitHub 问题 Inconsistent handling of native command stderr 已经打开以跟踪前面示例中的差异。但是请注意,由于它取决于未记录的行为,因此该行为将来可能会发生变化。在必须耐用的解决方案中使用它之前,请考虑到这一点。
您在使用管道方面走在正确的轨道上,您可能几乎不需要 Invoke-Command。 Powershell 确实区分标准输出和标准错误。例如试试这个:
cmd /c "echo hi && foo" | set-variable output
stdout
被传送到设置变量,而标准错误仍然出现在您的屏幕上。如果你想隐藏和捕获 stderr
输出,试试这个:
cmd /c "echo hi && foo" 2>$null | set-variable output
2>$null
部分是一个未记录的技巧,导致错误输出作为 ErrorRecord
.
$Error
变量
这是一个显示标准输出的示例,同时用 catch
块捕获 stderr
:
function test-cmd {
[CmdletBinding()]
param()
$ErrorActionPreference = "stop"
try {
cmd /c foo 2>$null
} catch {
$errorMessage = $_.TargetObject
Write-warning "`"cmd /c foo`" stderr: $errorMessage"
Format-list -InputObject $_ -Force | Out-String | Write-Debug
}
}
test-cmd
生成消息:
WARNING: "cmd /c foo" stderr: 'foo' is not recognized as an internal or external command
如果您在启用调试输出的情况下调用,您还会看到抛出的错误记录的详细信息:
DEBUG:
Exception : System.Management.Automation.RemoteException: 'foo' is not recognized as an internal or external command,
TargetObject : 'foo' is not recognized as an internal or external command,
CategoryInfo : NotSpecified: ('foo' is not re...ternal command,:String) [], RemoteException
FullyQualifiedErrorId : NativeCommandError
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at test-cmd, <No file>: line 7
at <ScriptBlock>, <No file>: line 1
PipelineIterationInfo : {}
PSMessageDetails :
设置$ErrorActionPreference="stop"
导致PowerShell在子进程写入stderr时抛出异常,这听起来像是你想要的核心。这个 2>$null
技巧使 cmdlet 和外部命令的行为非常相似。
- 描述的行为适用于 运行 PowerShell 常规 console/terminal windows 不涉及远程处理。使用远程处理和在 ISE 中,行为与 PSv5.1 不同 - 见底部。
- 2>$null
行为 2>$null
secretly still writing to PowerShell's error stream and therefore, with $ErrorActionPreference
Stop
in effect, aborting the script as soon as an external utility writes anything to stderr - has been classified as a bug 并且可能会消失。
当 PowerShell 调用诸如
cmd
之类的外部实用程序时,其 stderr 输出默认直接通过。也就是说,stderr 输出直接打印到控制台,而不包含在捕获的输出中(无论是通过分配给变量还是重定向到文件)。虽然您可以使用
2>&1
作为cmd
命令行的一部分 ,但您将无法区分PowerShell 中的 stdout 和 stderr 输出。相比之下,如果您使用
2>&1
作为 PowerShell 重定向,您可以根据输入对象的类型:- 一个
[string]
实例是一个stdout行 - A
[System.Management.Automation.ErrorRecord]
实例是 stderr 行。
- 一个
以下函数 Invoke-CommandLine
利用了这一点:
请注意,
cmd /c
部分不是内置的,因此您可以按如下方式调用它,例如:Invoke-CommandLine 'cmd /c "echo hi && foo"'
通过调用
cmd
命令行和直接调用外部实用程序(例如git.exe
,但请注意,只有通过cmd
调用才允许通过运算符&
、&&
和||
使用 多个 命令,并且只有cmd
解释%...%
风格的环境变量引用,除非你使用--%
,停止解析符号。Invoke-CommandLine
输出接收到的 stdout 和 stderr 行,因此您可以在管道中使用该函数。如所写,stderr 行在接收时使用
Write-Error
写入 PowerShell 的错误流,带有 单个通用 异常在外部命令终止后被抛出,它应该报告一个非零$LASTEXITCODE
.适配函数很简单:
- 在收到第一行 stderr 后采取行动。
- 到收集单个变量中的所有stderr行
- and/or,终止后,如果收到 any stderr 输入,即使
$LASTEXITCODE
报告0
.
Invoke-CommandLine
使用Invoke-Expression
,所以通常的警告适用:确保你知道你传递的是什么命令行,因为它会按原样执行,不不管它包含什么。
function Invoke-CommandLine {
<#
.SYNOPSIS
Executes an external utility with stderr output sent to PowerShell's error '
stream, and an exception thrown if the utility reports a nonzero exit code.
#>
param([parameter(Mandatory)][string] $CommandLine)
# Note that using . { ... } is required around the Invoke-Expression
# call to ensure that the 2>&1 redirection works as intended.
. { Invoke-Expression $CommandLine } 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) { # stderr line
Write-Error $_ # send stderr line to PowerShell's error stream
} else { # stdout line
$_ # pass stdout line through
}
}
# If the command line signaled failure, throw an exception.
if ($LASTEXITCODE) {
Throw "Command failed with exit code ${LASTEXITCODE}: $CommandLine"
}
}
可选阅读:对外部实用程序的调用如何适应 PowerShell 的错误处理
当前版本:Windows PowerShell v5.1,PowerShell Core v6-beta.2
首选项变量的值
$ErrorActionPreference
仅控制对在 PowerShell cmdlet/function 调用中发生 的错误和 .NET 异常的反应或表达式.Try
/Catch
用于捕获 PowerShell 的终止错误 和 .NET异常.在不涉及远程处理的常规控制台 window 中 ,
cmd
等外部实用程序当前 从不生成任一个错误 - 他们所做的只是报告一个退出代码,PowerShell 在 自动变量$LASTEXITCODE
中反映,自动变量$?
在退出代码非零时反映$False
.注意:在控制台主机以外的主机中行为根本不同这一事实 - 包括 Windows ISE 和涉及远程处理时 - 是有问题的:调用外部实用程序导致 stderr 输出被视为已报告 非终止错误;具体来说:
- 每个 stderr 输出行都作为错误记录输出,并记录在自动
$Error
集合中。 - 在 addition 到
$?
中设置为$false
且退出代码为非零,任何 stderr 输出的存在 also 将其设置为$False
. - 这种行为是有问题的,因为 stderr 输出本身并不一定表示 错误 - 只有 非零退出代码 是。
- Burt has created an issue in the PowerShell GitHub repository to discuss this inconsistency.
- 每个 stderr 输出行都作为错误记录输出,并记录在自动
默认情况下,外部实用程序生成的标准错误输出直接传递到控制台 - 它们 不是 由 PowerShell 变量赋值或(成功流)输出重定向捕获。
如上所述,可以更改为:
2>&1
作为命令行的一部分传递给cmd
发送标准输出和 stderr combined 到 PowerShell 的成功流,作为 strings,无法区分给定行是 stdout 还是 stderr 行。2>&1
作为 PowerShell 重定向 也将标准错误行发送到 PowerShell 的成功流,但是 你可以通过数据类型 来区分源自 stdout 和 stderr 的行:[string]
类型的行是源自 stdout 的行,而[System.Management.Automation.ErrorRecord]
类型的行line 是一个 stderr 起源的。