我想运行一个外部进程,并将它的命令输出捕获到PowerShell中的一个变量。我目前正在使用这个:
$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait
我已经确认该命令正在执行,但我需要将输出捕获到一个变量中。这意味着我不能使用-RedirectOutput,因为它只重定向到一个文件。
我想运行一个外部进程,并将它的命令输出捕获到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…(stderr输出为2>),如下所示。
从根本上说,从外部程序捕获输出与powershell本机命令的工作原理相同(您可能需要重新了解如何执行外部程序;<命令>是下面任何有效命令的占位符):
# 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
注意,如果<命令>产生多个输出对象,$cmdOutput将接收一个对象数组,在外部程序的情况下,这意味着包含程序输出行的字符串[1]数组。
如果希望确保结果始终是一个数组——即使只输出一个对象,也可以将变量类型约束为数组([object[]]),或者将命令括在@(…)中,数组子表达式操作符:[2]
[array] $cmdOutput = <command>
$cmdOutput = @(<command>) # alternative
相比之下,如果你想要$cmdOutput总是接收单个-可能是多行-字符串,使用Out-String,尽管注意,尾随换行符总是被添加(GitHub issue #14444讨论了这个有问题的行为):
# Note: Adds a trailing newline.
$cmdOutput = <command> | Out-String
对于外部程序的调用——根据定义,在PowerShell[1]中只返回字符串——您可以通过使用-join操作符来避免这种情况:
# NO trailing newline.
$cmdOutput = (<command>) -join "`n"
注意:为了简单起见,上面使用“n”来创建unix风格的仅限lf的换行符,PowerShell乐意在所有平台上接受这种换行符;如果您需要适合平台的换行符(Windows上的CRLF, Unix上的LF),请使用[Environment]::NewLine代替。
获取变量的输出并打印到屏幕上:
<command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed
或者,如果<命令>是cmdlet或高级函数,则可以使用公共参数 -OutVariable / -ov:
<command> -OutVariable cmdOutput # cmdlets and advanced functions only
注意,使用-OutVariable时,与其他场景不同,$cmdOutput始终是一个集合,即使只输出一个对象。具体来说,是类数组[System.Collections. xml]的实例。类型返回。 关于这一差异的讨论,请参阅GitHub问题。
要从多个命令中捕获输出,可以使用子表达式($(…))或调用脚本块({…})与&或。
$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、外部程序、函数、别名)则需要参数模式,这就是&所确保的。
$(…)和&{…} /。{…}是前者在返回之前将所有输入收集到内存中,而后者则将输出作为流,适合逐个管道处理。
重定向从根本上也是一样的(但请参阅下面的警告):
$cmdOutput = <command> 2>&1 # redirect error stream (2) to success stream (1)
然而,对于外部命令,以下命令更有可能按预期工作:
$cmdOutput = cmd /c <command> '2>&1' # Let cmd.exe handle redirection - see below.
外部程序的具体考虑事项:
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] 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. 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. 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. If the output contains more than 1 line, PowerShell by default splits it into an array of strings. More accurately, the output lines are streamed one by one, and, when captured, stored in an array of type [System.Object[]] whose elements are strings ([System.String]). If you want the output to be a single, potentially multi-line string, use the -join operator (you can alternatively pipe to Out-String, but that invariably adds a trailing newline): $cmdOutput = (<command>) -join [Environment]::NewLine Merging stderr into stdout with 2>&1, so as to also capture it as part of the success stream, comes with caveats: To do this at the source, let cmd.exe handle the redirection, using the following idioms (works analogously with sh 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 invokes cmd.exe with command <command> and exits after <command> has finished. Note the single quotes around 2>&1, which ensures that the redirection is passed to cmd.exe rather than being interpreted by PowerShell. 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 for cmd.exe-style environment-variable references such as %PATH%. 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. Use PowerShell's 2>&1 redirection to know which lines came from what stream: 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 by 2>&1, both the strings and the error records are received through PowerShell's success output stream). 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. 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. 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. 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. To work around this problem, apply the .ToString() method to each output object instead of piping to Out-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.) 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.2.x开始:
对于空字符串参数和包含嵌入式“字符”的参数,向外部程序传递参数是不正确的。 此外,msiexec.exe和批处理文件等可执行文件的(非标准)引用需求也不满足。
仅针对前一个问题,可能会出现修复(尽管修复将在类unix平台上完成),如本回答所述,该回答还详细介绍了当前所有问题和解决方法。
如果可以选择安装第三方模块,则本地模块(Install-Module Native)中的ie功能提供了全面的解决方案。
从PowerShell 7.1开始,PowerShell在与外部程序通信时只知道字符串。PowerShell管道中通常没有原始字节数据的概念。如果你想从外部程序返回原始字节数据,你必须shell到cmd.exe /c (Windows)或sh -c (Unix),保存到一个文件中,然后在PowerShell中读取该文件。更多信息请看这个答案。
这两种方法之间有微妙的区别(你可以结合使用),尽管它们通常并不重要:如果命令没有输出,[array]类型约束方法导致$null被存储在目标变量中,而在@(…)的情况下,它是一个空([object[])数组。此外,[array]类型约束意味着对同一个变量的未来(非空)赋值也会被强制到数组中。
其他回答
或者试试这个。它将输出捕获到变量$scriptOutput:
& "netdom.exe" $params | Tee-Object -Variable scriptOutput | Out-Null
$scriptOutput
另一个现实生活中的例子:
$result = & "$env:cust_tls_store\Tools\WDK\x64\devcon.exe" enable $strHwid 2>&1 | Out-String
注意,这个示例包括一个路径(以一个环境变量开始)。注意,引号必须包含路径和EXE文件,而不是参数!
注意:不要忘记在命令前面,但在引号之外的&字符。
错误输出也会被收集。
我花了一段时间来让这个组合工作,所以我想我会分享它。
对我来说,当使用外部命令时,当标准错误和标准输出流都可能是运行命令(或它们的混合)的结果时,我的诀窍是:
$output = (command 2>&1)
如果你想重定向错误输出,你必须做:
$cmdOutput = command 2>&1
或者,如果程序名中有空格:
$cmdOutput = & "command with spaces" 2>&1
你有没有试过:
$OutputVariable = (Shell命令)| Out-String