如何将输出从 PowerShell 中的外部进程捕获到变量中? [英] How do I capture the output into a variable from an external process in PowerShell?
问题描述
我想运行一个外部进程并将它的命令输出捕获到 PowerShell 中的一个变量.我目前正在使用这个:
I'd like to run an external process and capture it's command output to a variable in PowerShell. I'm currently using this:
$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait
我已经确认命令正在执行,但我需要将输出捕获到一个变量中.这意味着我不能使用 -RedirectOutput 因为这只会重定向到一个文件.
I've confirmed the command is executing but I need to capture the output into a variable. This means I can't use the -RedirectOutput because this only redirects to a file.
推荐答案
注意:问题中的命令使用了 Start-Process
,这会阻止直接捕获目标程序的输出.通常,不要使用 Start-Process
同步执行控制台应用程序 - 只需调用它们直接,就像在任何外壳中一样.这样做可以使应用程序连接到调用控制台的标准流,允许通过简单的赋值$output = netdom ...
捕获其输出,如下详述.
Note: The command in the question uses Start-Process
, which prevents direct capturing of the target program's output. Generally, do not use Start-Process
to execute console applications synchronously - just invoke them directly, as in any shell. Doing so keeps the application connected to the calling console's standard streams, allowing its output to be captured by simple assignment $output = netdom ...
, as detailed below.
从根本上,从外部程序捕获输出的工作方式与使用 PowerShell 本地命令相同(您可能需要复习如何执行外部程序;
是placeholder 对于以下任何有效命令):
Fundamentally, capturing output from external programs works the same as with PowerShell-native commands (you may want a refresher on how to execute external programs; <command>
is a placeholder for any valid command below):
# IMPORTANT:
# <command> is a *placeholder* for any valid command; e.g.:
# $cmdOutput = Get-Date
# $cmdOutput = attrib.exe +R readonly.txt
$cmdOutput = <command> # captures the command's success stream / stdout output
请注意,如果
产生 1 个以上的输出对象,$cmdOutput
会接收一个 array 对象,在外部程序的情况下,表示一个字符串[1]数组,其中包含程序的输出行.
Note that $cmdOutput
receives an array of objects if <command>
produces more than 1 output object, which in the case of an external program means a string[1] array containing the program's output lines.
如果您想确保结果总是一个数组 - 即使只输出一个对象,输入-constrain 将变量作为数组,或将命令包装在 @()
中,数组子表达式运算符):
If you want to make sure that the result is always an array - even if only one object is output, type-constrain the variable as an array, or wrap the command in @()
, the array-subexpression operator):
[array] $cmdOutput = <command> # or: $cmdOutput = @(<command>)
相比之下,如果您希望 $cmdOutput
始终接收 single - 可能是多行 - string,使用 Out-String
,但请注意一个尾随换行符总是被添加(GitHub 问题 #14444 讨论了这种有问题的行为):
By contrast, if you want $cmdOutput
to always receive a single - potentially multi-line - string, use Out-String
, though note that a trailing newline is invariably added (GitHub issue #14444 discusses this problematic behavior):
# Note: Adds a trailing newline.
$cmdOutput = <command> | Out-String
调用外部程序——根据定义,它只在PowerShell中返回字符串[1]——你可以通过使用-join
运算符 代替:
With calls to external programs - which by definition only ever return strings in PowerShell[1] - you can avoid that by using the -join
operator instead:
# NO trailing newline.
$cmdOutput = (<command>) -join "`n"
注意:为简单起见,上面使用 "`n"
来创建 Unix 风格的 LF-only 换行符,PowerShell 很乐意在所有平台上接受;如果您需要适合平台的换行符(Windows 上的 CRLF,Unix 上的 LF),请改用 [Environment]::NewLine
.
Note: For simplicity, the above uses "`n"
to create Unix-style LF-only newlines, which PowerShell happily accepts on all platforms; if you need platform-appropriate newlines (CRLF on Windows, LF on Unix), use [Environment]::NewLine
instead.
要捕获变量中的输出并打印到屏幕:
To capture output in a variable and print to the screen:
<command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed
或者,如果
是 cmdlet 或 高级 函数,您可以使用 通用参数-OutVariable
/-ov
:
Or, if <command>
is a cmdlet or advanced function, you can use common parameter
-OutVariable
/ -ov
:
<command> -OutVariable cmdOutput # cmdlets and advanced functions only
请注意,对于 -OutVariable
,与其他场景不同的是,$cmdOutput
总是 一个 集合,即使只输出一个对象.具体来说,返回一个类似数组的 [System.Collections.ArrayList]
类型的实例.
有关此差异的讨论,请参阅此 GitHub 问题.
Note that with -OutVariable
, unlike in the other scenarios, $cmdOutput
is always a collection, even if only one object is output. Specifically, an instance of the array-like [System.Collections.ArrayList]
type is returned.
See this GitHub issue for a discussion of this discrepancy.
要捕获多个命令的输出,请使用子表达式 ($(...)
) 或调用脚本块 ({ ... }
) 与 &
或 .
:
To capture the output from multiple commands, use either a subexpression ($(...)
) or call a script block ({ ... }
) with &
or .
:
$cmdOutput = $(<command>; ...) # subexpression
$cmdOutput = & {<command>; ...} # script block with & - creates child scope for vars.
$cmdOutput = . {<command>; ...} # script block with . - no child scope
请注意,一般需要以 &
(调用运算符)作为前缀的单个命令,其名称/路径引用 - 例如, $cmdOutput = &'netdom.exe' ...
- 与外部程序本身无关(它同样适用于 PowerShell 脚本),而是语法要求:默认情况下,PowerShell 在表达式模式下解析以带引号的字符串开头的语句,而需要参数模式来调用命令(cmdlet、外部程序、函数、别名),其中是 &
确保的.
Note that the general need to prefix with &
(the call operator) an individual command whose name/path is quoted - e.g., $cmdOutput = & 'netdom.exe' ...
- is not related to external programs per se (it equally applies to PowerShell scripts), but is a syntax requirement: PowerShell parses a statement that starts with a quoted string in expression mode by default, whereas argument mode is needed to invoke commands (cmdlets, external programs, functions, aliases), which is what &
ensures.
$(...)
和 & 的主要区别{ ... }
/.{ ... }
是前者收集内存中的所有输入然后作为一个整体返回,而后者stream输出,适用于一个-逐一流水线处理.
The key difference between $(...)
and & { ... }
/ . { ... }
is that the former collects all input in memory before returning it as a whole, whereas the latter stream the output, suitable for one-by-one pipeline processing.
重定向从根本上也是一样的(但请参阅下面的警告):
Redirections also work the same, fundamentally (but see caveats below):
$cmdOutput = <command> 2>&1 # redirect error stream (2) to success stream (1)
但是,对于外部命令,以下内容更有可能按预期工作:
However, for external commands the following is more likely to work as expected:
$cmdOutput = cmd /c <command> '2>&1' # Let cmd.exe handle redirection - see below.
特定于外部计划的注意事项:
Considerations specific to external programs:
外部程序,因为它们在 PowerShell 的类型系统之外运行,只通过它们的成功流 (stdout) 返回字符串;类似地,PowerShell 只会通过管道发送字符串到外部程序.[1]
External programs, because they operate outside PowerShell's type system, only ever return strings via their success stream (stdout); similarly, PowerShell only ever sends strings to external programs via the pipeline.[1]
- 字符编码问题因此可以发挥作用:
在通过管道发送数据到外部程序时,PowerShell使用存储在
$OutVariable
首选项变量中的编码;在 Windows PowerShell 中默认为 ASCII(!),在 PowerShell [Core] 中默认为 UTF-8.
- Character-encoding issues can therefore come into play:
On sending data via the pipeline to external programs, PowerShell uses the encoding stored in the
$OutVariable
preference variable; which in Windows PowerShell defaults to ASCII(!) and in PowerShell [Core] to UTF-8.
在从外部程序接收数据时,PowerShell 使用存储在
[Console]::OutputEncoding
中的编码对数据进行解码,这在两个 PowerShell 版本中默认为系统的活动 OEM 代码页.On receiving data from an external program, PowerShell uses the encoding stored in
[Console]::OutputEncoding
to decode the data, which in both PowerShell editions defaults to the system's active OEM code page.有关详细信息,请参阅此答案;此答案 讨论了仍处于测试阶段的(截至撰写本文时)Windows 10 功能,该功能允许您设置 UTF-8作为 ANSI 和 OEM 代码页系统范围.
See this answer for more information; this answer discusses the still-in-beta (as of this writing) Windows 10 feature that allows you to set UTF-8 as both the ANSI and the OEM code page system-wide.
如果输出包含超过 1 行,PowerShell 默认将其拆分为数组字符串.更准确地说,输出行存储在
[System.Object[]]
类型的数组中,其元素是字符串 ([System.String]
).If the output contains more than 1 line, PowerShell by default splits it into an array of strings. More accurately, the output lines are stored in an array of type
[System.Object[]]
whose elements are strings ([System.String]
).如果您希望输出是单行,可能是多行字符串,请使用
-join
操作符(您也可以通过管道连接到Out-String
,但这总是会添加一个尾随换行符):$cmdOutput = (
) -join [Environment]::NewLine If you want the output to be a single, potentially multi-line string, use the
-join
operator (you can alternatively pipe toOut-String
, but that invariably adds a trailing newline):
$cmdOutput = (<command>) -join [Environment]::NewLine
将 stderr 与
2>&1
合并到标准输出中,以便也将其作为成功流的一部分进行捕获,附带注意事项:要在源头做到这一点,让
cmd.exe
处理重定向,使用以下习语(在类 Unix 平台上与sh
类似):
<代码>$cmdOutput = cmd/c <命令>'2>&1' # *array* 字符串(通常)$cmdOutput = (cmd/c
'2>&1') -join "`r`n";# 单个字符串
To do this at the source, let
cmd.exe
handle the redirection, using the following idioms (works analogously withsh
on Unix-like platforms):
$cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
$cmdOutput = (cmd /c <command> '2>&1') -join "`r`n" # single string
cmd/c
使用命令
调用cmd.exe
并在
已完成.之后退出
cmd /c
invokescmd.exe
with command<command>
and exits after<command>
has finished.
注意
2>&1
周围的单引号,它确保重定向被传递给cmd.exe
而不是被 PowerShell 解释.Note the single quotes around
2>&1
, which ensures that the redirection is passed tocmd.exe
rather than being interpreted by PowerShell.注意,涉及
cmd.exe
意味着它的转义字符和扩展环境变量的规则开始发挥作用,默认情况下,除了PowerShell自己的要求;在 PS v3+ 中,您可以使用特殊参数--%
(所谓的停止解析符号)来关闭 PowerShell 对其余参数的解释,cmd.exe
样式的环境变量引用,例如%PATH%
.Note that involving
cmd.exe
means that its rules for escaping characters and expanding environment variables come into play, by default in addition to PowerShell's own requirements; in PS v3+ you can use special parameter--%
(the so-called stop-parsing symbol) to turn off interpretation of the remaining parameters by PowerShell, except forcmd.exe
-style environment-variable references such as%PATH%
.请注意,由于您使用这种方法在源头合并了 stdout 和 stderr,您将无法区分源自 stdout 的行和源自 stderr 的行 在 PowerShell 中;如果您确实需要这种区别,请使用 PowerShell 自己的
2>&1
重定向 - 见下文.Note that since you're merging stdout and stderr at the source with this approach, you won't be able to distinguish between stdout-originated and stderr-originated lines in PowerShell; if you do need this distinction, use PowerShell's own
2>&1
redirection - see below.使用 PowerShell 的
2>&1
重定向来了解哪些行来自哪个流:Use PowerShell's
2>&1
redirection to know which lines came from what stream:Stderr 输出被捕获为错误记录(
[System.Management.Automation.ErrorRecord]
),而不是字符串,所以输出数组可能包含字符串(每个字符串代表一个标准输出行)和错误记录(每个记录代表一个标准错误)的混合行).请注意,根据2>&1
的要求,字符串和错误记录都是通过 PowerShell 的 success 输出流接收的.
Stderr output is captured as error records (
[System.Management.Automation.ErrorRecord]
), not strings, so the output array may contain a mix of strings (each string representing a stdout line) and error records (each record representing a stderr line). Note that, as requested by2>&1
, both the strings and the error records are received through PowerShell's success output stream).
注意:以下仅适用于 Windows PowerShell - 这些问题已在 PowerShell [Core] v6+,虽然下面显示的按对象类型的过滤技术 (
$_ -is [System.Management.Automation.ErrorRecord]
) 在那里也很有用.Note: The following only applies to Windows PowerShell - these problems have been corrected in PowerShell [Core] v6+, though the filtering technique by object type shown below (
$_ -is [System.Management.Automation.ErrorRecord]
) can also be useful there.在控制台中,错误记录以红色打印,第一个默认产生多行显示,与 cmdlet 的非终止错误显示的格式相同;后续错误记录也以红色打印,但仅在单行上打印它们的错误消息.
In the console, the error records print in red, and the 1st one by default produces multi-line display, in the same format that a cmdlet's non-terminating error would display; subsequent error records print in red as well, but only print their error message, on a single line.
当输出到控制台时,字符串通常出现在输出数组中第一,然后是错误记录(在至少在一批 stdout/stderr 行输出同时"),但幸运的是,当您捕获输出时,它被正确交错,使用在没有
2>&1
的情况下你会得到相同的输出顺序换句话说:当输出到控制台时,捕获的输出不反映外部命令生成 stdout 和 stderr 行的顺序.When outputting to the console, the strings typically come first in the output array, followed by the error records (at least among a batch of stdout/stderr lines output "at the same time"), but, fortunately, when you capture the output, it is properly interleaved, using the same output order you would get without
2>&1
; in other words: when outputting to the console, the captured output does NOT reflect the order in which stdout and stderr lines were generated by the external command.如果您使用
Out-String
在单个字符串中捕获整个输出,PowerShell 将添加额外行,因为错误记录的字符串表示包含额外信息,例如位置(At line:...
)和类别(+ CategoryInfo ...
);奇怪的是,这仅适用于第一个错误记录.If you capture the entire output in a single string with
Out-String
, PowerShell will add extra lines, because the string representation of an error record contains extra information such as location (At line:...
) and category (+ CategoryInfo ...
); curiously, this only applies to the first error record.要解决此问题,请将
.ToString()
方法应用于每个输出对象,而不是管道到Out-String
:
<代码>$cmdOutput = <命令>2>&1 |% { $_.ToString() };
在 PS v3+ 中,您可以简化为:
<代码>$cmdOutput = <命令>2>&1 |% ToString
(作为奖励,如果没有捕获输出,即使打印到控制台也会产生正确的交错输出.)
To work around this problem, apply the
.ToString()
method to each output object instead of piping toOut-String
:
$cmdOutput = <command> 2>&1 | % { $_.ToString() }
;
in PS v3+ you can simplify to:
$cmdOutput = <command> 2>&1 | % ToString
(As a bonus, if the output isn't captured, this produces properly interleaved output even when printing to the console.)
或者,过滤错误记录出并使用
Write-Error
将它们发送到PowerShell的错误流(作为奖励,如果未捕获输出,即使打印到控制台也会产生正确的交错输出):Alternatively, filter the error records out and send them to PowerShell's error stream with
Write-Error
(as a bonus, if the output isn't captured, this produces properly interleaved output even when printing to the console):$cmdOutput = <command> 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { Write-Error $_ } else { $_ } }
另一种参数传递,从 PowerShell 7.1 开始:
An aside re argument-passing, as of PowerShell 7.1:
就空字符串 参数和包含嵌入
"
字符的参数而言,向外部程序传递参数被破坏.
Passing arguments to external programs is broken with respect to empty-string arguments and arguments that contain embedded
"
characters.
此外,不满足
msiexec.exe
和批处理文件等可执行文件的(非标准)引用需求.Additionally, the (nonstandard) quoting needs of executables such as
msiexec.exe
and batch files aren't accommodated.仅针对前一个问题,可能会进行修复(尽管修复将在类似 Unix 的平台上完成),如 这个答案,其中还详细介绍了当前的所有问题和解决方法.
For the former problem only, a fix may be coming (though the fix would be complete on Unix-like platforms), as discussed in this answer, which also details all the current problems and workarounds.
如果安装第三方模块是一个选项,ie函数rel="noreferrer">
Native
模块(Install-Module Native
)提供了一个综合解决方案.If installing a third-party module is an option, the
ie
function from theNative
module (Install-Module Native
) offers a comprehensive solution.[1] 从 PowerShell 7.1 开始,PowerShell 在与外部程序通信时只知道字符串.在 PowerShell 管道中通常没有原始字节数据的概念.如果您希望从外部程序返回原始字节数据,您必须将外壳输出到
cmd.exe/c
(Windows) 或sh -c
(Unix),保存到file there,然后在 PowerShell 中读取该文件.有关详细信息,请参阅此答案.[1] As of PowerShell 7.1, PowerShell knows only strings when communicating with external programs. There is generally no concept of raw byte data in a PowerShell pipeline. If you want raw byte data returned from an external program, you must shell out to
cmd.exe /c
(Windows) orsh -c
(Unix), save to a file there, then read that file in PowerShell. See this answer for more information.这篇关于如何将输出从 PowerShell 中的外部进程捕获到变量中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!