如何在PowerShell中从外部进程捕获输出并存储到变量中?
如何在PowerShell中从外部进程捕获输出并存储到变量中?
我想在PowerShell中运行一个外部进程并将其命令输出捕获到一个变量中。我目前正在使用以下命令:
$params = "/verify $pc /domain:hosp.uhhg.org" start-process "netdom.exe" $params -WindowStyle Hidden -Wait
我已确认命令正在执行,但我需要将输出捕获到一个变量中。这意味着我不能使用-RedirectOutput,因为这只能重定向到一个文件。
注:问题中的命令使用了Start-Process
,这会阻止直接捕获目标程序的输出。通常,不要使用Start-Process
来同步执行控制台应用程序 - 直接调用它们,就像在任何Shell中一样。这样做可以使应用程序的输出流连接到PowerShell的流,允许它们的输出通过简单的赋值$output = netdom ...
(并使用2>
获取stderr输出)来捕获,如下所述。
基本上,从外部程序捕获输出的工作方式与使用PowerShell本地命令相同(你可能需要复习一下如何执行外部程序;
是下面任何有效命令的占位符):
# IMPORTANT: #is a *placeholder* for any valid command; e.g.: # $cmdOutput = Get-Date # $cmdOutput = attrib.exe +R readonly.txt $cmdOutput = # captures the command's success stream / stdout output
请注意,如果
产生多个输出对象,$cmdOutput
将接收一个对象数组,在外部程序的情况下,这意味着一个包含程序输出行的string[1]数组。
如果你希望确保结果始终是一个数组,即使只有一个对象输出,请将变量类型限制为数组([object[]]
),或将命令括在@(...)
中,使用数组子表达式运算符:[2]
[array] $cmdOutput =$cmdOutput = @( ) # alternative
相比之下,如果您想让 $cmdOutput
始终接收一个单一的(可能是多行的)字符串,请使用 Out-String
,但请注意,总是会添加一个尾随换行符(GitHub 问题#14444 对这种问题行为进行了讨论):
# Note: Adds a trailing newline. $cmdOutput =| Out-String
通过调用外部程序 - 这种程序在 PowerShell 中定义为仅返回字符串[1] - ,您可以改用 -join
运算符 避免这种情况:
# NO trailing newline. $cmdOutput = () -join "`n"
注意:为了简单起见,上面使用 "`n"
来创建 Unix 风格的 LF-only 换行符,PowerShell 在所有平台上都可以接受;如果您需要平台适当的换行符(Windows 上的 CRLF,Unix 上的 LF),请改用 [Environment]::NewLine
。
要捕获输出到一个变量并打印到屏幕上:
| Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed
或者,如果
是一个 cmdlet 或高级函数,您可以使用常见参数
-OutVariable
/ -ov
:
-OutVariable cmdOutput # cmdlets and advanced functions only
请注意,与其他情况不同,-OutVariable
中的 $cmdOutput
总是一个集合,即使只有一个对象被输出。具体来说,返回的是一个类似于数组的 [System.Collections.ArrayList]
类型的实例。
请参阅 此 GitHub 问题,以了解这种差异的讨论。
为了捕获多个命令的输出,可以使用子表达式($(...)
)或调用带有&
或.
的脚本块({...}
):
$cmdOutput = $(; ...) # subexpression $cmdOutput = & { ; ...} # script block with & - creates child scope for vars. $cmdOutput = . { ; ...} # script block with . - no child scope
请注意,需要在带引号的个别命令(例如$cmdOutput = & 'netdom.exe' ...
)之前加上&
(调用操作符)不仅适用于外部程序(同样适用于PowerShell脚本),这是一个语法要求:PowerShell默认在表达式模式下解析以引号括起来的语句,而调用命令(cmdlets、外部程序、函数、别名)需要参数模式,这就是&
的作用。
$(...)
与{...}
/ .{...}
之间的主要区别在于前者在返回之前将所有输入收集到内存中,然后作为整体返回,而后者将输出流式传输,适合逐个管道处理。
重定向的基本原理也是相同的(但请注意下面的注意事项):
$cmdOutput =2>&1 # redirect error stream (2) to success stream (1)
然而,对于外部命令,以下方式更可能按预期工作:
$cmdOutput = cmd /c'2>&1' # Let cmd.exe handle redirection - see below.
特定于外部程序的注意事项:
-
外部程序由于在PowerShell的类型系统之外操作,只通过其成功流(stdout)返回字符串;同样,PowerShell仅通过管道向外部程序发送字符串([1])。
- 字符编码问题也可能会出现:
-
如果输出包含多行,PowerShell默认将其拆分为字符串数组。更准确地说,输出行逐一流式传输,捕获时存储在类型为
[System.Object[]]
、元素为字符串([System.String]
)的数组中。 -
如果你希望输出为单个、可能有多行的字符串,请使用
-join
运算符(你也可以使用管道符号Out-String
,但这总会添加一个尾随换行符):
$cmdOutput = (
) -join [Environment]::NewLine -
将stderr合并到stdout中使用
2>&1
,以便将其作为成功流的一部分捕获,具有注意事项:
-
要在源代码中执行此操作,让
cmd.exe
处理重定向,使用以下语法(在类 Unix 平台中使用sh
类似):
$cmdOutput = cmd /c
'2>&1' # *string* 数组(通常)
$cmdOutput = (cmd /c
'2>&1') -join "`r`n" # 单个字符串 -
cmd /c
执行
命令,然后在
完成后退出。 -
请注意在
2>&1
周围使用了单引号,这可以确保将重定向传递给cmd.exe
而不是被 PowerShell 解释。 -
请注意参与
cmd.exe
的规则,这意味着默认情况下,它的转义字符和环境变量扩展规则与 PowerShell 自己的要求相结合。在 PS v3+ 中,您可以使用特殊参数--%
(所谓的强制停止解析符号)来关闭 PowerShell 对剩余参数的解释,除了类似于%PATH%
的cmd.exe
-风格环境变量引用。 -
请注意,由于这种方法在源头合并了 stdout 和 stderr,所以在 PowerShell 中无法区分 stdout 生成的行和 stderr 生成的行;如果确实需要这种区分,请使用 PowerShell 自己的
2>&1
重定向 - 请见下文。
-
-
使用 PowerShell 的
2>&1
重定向来确定哪些行来自哪个流:-
标准错误 (stderr) 的输出被捕获为 错误记录 (
[System.Management.Automation.ErrorRecord]
),而不是字符串,因此输出数组可能包含混合的字符串 (每个字符串代表一个 stdout 行) 和错误记录 (每个记录代表一个 stderr 行)。请注意,根据2>&1
的要求,字符串和错误记录都通过 PowerShell 的成功输出流接收)。 -
注意:以下内容仅适用于 Windows PowerShell - 这些问题已在 PowerShell [Core] v6+ 中得到纠正,但是下面展示的按对象类型过滤技术 (
$_ -is [System.Management.Automation.ErrorRecord]
) 在那里也可以很有用。 -
在控制台中,错误记录以红色打印,第一个错误记录默认情况下会产生多行显示,以与 cmdlet 的非终止性错误相同的格式显示;随后的错误记录也会以红色打印,但只打印其错误消息,仅显示一行。
-
在输出到控制台时,字符串通常位于输出数组的前面,后跟错误记录 (至少在输出“同时”产生的 stdout/stderr 行批次中),但幸运的是,当您捕获输出时,它会被正确地交错,使用在没有
2>&1
的情况下得到的相同输出顺序;换句话说:在输出到控制台时,捕获的输出不反映外部命令生成 stdout 和 stderr 行的顺序。
-
-
如果您使用
Out-String
将整个输出捕获到单个字符串中,PowerShell会添加额外的行,因为错误记录的字符串表示包含额外的信息,例如位置 (At line:...
) 和类别 (+CategoryInfo ...
); 奇怪的是,这仅适用于第一个错误记录。-
为解决此问题,请对每个输出对象应用
.ToString()
方法,而不是使用管道传输到Out-String
:
$cmdOutput =
;2>&1 | % { $_.ToString() }
在PS v3+中,您可以简化为:
$cmdOutput =
2>&1 | % ToString
(作为奖励,如果未捕获输出,则即使在打印到控制台时,也会产生正确交替的输出。) -
或者,过滤掉错误记录,并将它们发送到PowerShell的错误流中,使用
Write-Error
(作为奖励,如果未捕获输出,则即使在打印到控制台时也会产生正确交替的输出):
-
$cmdOutput =2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { Write-Error $_ } else { $_ } }
关于参数传递的一个除外情况,自PowerShell 7.2.x起:
-
关于传递参数给外部程序的问题,在处理空字符串参数和包含嵌入
"
字符的参数时存在问题。 -
此外,不适应
msiexec.exe
和批处理文件等可执行文件的(非标准的)引用需求。
只有针对前一个问题,正在考虑解决方法(尽管在类Unix平台上,解决方法已经完善了),可以参考此回答,其中详细介绍了所有当前问题和解决方法。
如果安装第三方模块是一种选择,那么Native模块中的ie
函数(Install-Module Native
)提供了一个综合解决方案。
[1] 截至PowerShell 7.1,PowerShell只支持与外部程序通信的字符串。通常在PowerShell管道中没有原始字节数据的概念。如果你想从外部程序返回原始字节数据,你必须调用cmd.exe /c
(Windows)或sh -c
(Unix)并将其保存到文件中,然后在PowerShell中读取该文件。更多信息,请参考这个答案。
[2] 两种方法之间有细微的区别(你可以将它们结合起来使用),尽管通常不会影响结果。如果命令没有输出,类型约束方法[array]
会将$null
存储在目标变量中,而@(...)
的情况下则是一个空([object[]
)数组。此外,[array]
类型限制意味着将来对同一变量的(非空)赋值也会被强制转换为数组。