如何将输出从 PowerShell 中的外部进程捕获到变量中? [英] How do I capture the output into a variable from an external process in PowerShell?

查看:45
本文介绍了如何将输出从 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 to Out-String, but that invariably adds a trailing newline):
      $cmdOutput = (<command>) -join [Environment]::NewLine

      stderr2>&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 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 使用命令 调用 cmd.exe 并在 之后退出 已完成.

      • cmd /c invokes cmd.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 to cmd.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 for cmd.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 by 2>&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 的情况下你会得到相同的输出顺序换句话说:当输出到控制台时,捕获的输出不反映外部命令生成 std​​out 和 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 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.)

      或者,过滤错误记录并使用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 the Native 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) or sh -c (Unix), save to a file there, then read that file in PowerShell. See this answer for more information.

      这篇关于如何将输出从 PowerShell 中的外部进程捕获到变量中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆