powershell - 如何将输出从 PowerShell 中的外部进程捕获到变量中?

标签 powershell process

我想运行一个外部进程并将它的命令输出捕获到 PowerShell 中的变量。我目前正在使用这个:

$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait

我已经确认命令正在执行,但我需要将输出捕获到一个变量中。这意味着我不能使用 -RedirectOutput 因为这只会重定向到一个文件。

最佳答案

注意:问题中的命令使用 Start-Process ,这会阻止直接捕获目标程序的输出。通常, do not use Start-Process to execute console applications synchronously - just invoke them directly ,就像在任何 shell 中一样。这样做使应用程序连接到调用控制台的标准流,允许通过简单的赋值 $output = netdom ... 捕获其输出,如下详述。
从根本上说,从外部程序捕获输出的工作方式与 PowerShell 本地命令 相同(您可能需要复习 how to execute external programs<command> 是以下任何有效命令的占位符):

# 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 个输出对象 ,则 <command> 接收一个对象数组,在 外部程序的情况下,这意味着包含程序输出行 _679204 的 string[1] 数组
如果你想确保结果是 总是一个数组 - 即使只输出一个对象,将变量类型约束为一个数组,或者将命令包装在 @() 中,即 array-subexpression operator ):
[array] $cmdOutput = <command> # or: $cmdOutput = @(<command>)
相比之下,如果您希望 $cmdOutput 始终接收 单行 - 可能是多行 - 字符串 ,请使用 Out-String ,但请注意 总是添加尾随换行符 _0x104567 讨论此问题的行为
# Note: Adds a trailing newline.
$cmdOutput = <command> | Out-String
通过调用外部程序——根据定义,它只在 PowerShell[1] 中返回字符串——你可以通过使用 GitHub issue #14444 来避免这种情况:
# NO trailing newline.
$cmdOutput = (<command>) -join "`n"
注意:为简单起见,上面使用 -join 来创建 Unix 风格的 LF-only 换行符,PowerShell 很乐意在所有平台上接受;如果您需要适合平台的换行符(Windows 上的 CRLF,Unix 上的 LF),请改用 "`n"

在变量 中捕获 输出并打印到屏幕 :
<command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed
或者,如果 [Environment]::NewLine 是 cmdlet 或高级函数,则可以使用 <command> operator :
<command> -OutVariable cmdOutput   # cmdlets and advanced functions only
请注意,对于 -OutVariable ,与其他场景不同,-ov 始终是一个集合,即使只输出一个对象。具体来说,返回一个类似数组的 -OutVariable 类型的实例。
有关此差异的讨论,请参阅 common parameter
$cmdOutput / [System.Collections.ArrayList]


要捕获 多个命令 的输出,请使用子表达式 ($(...)) 或使用 { ... }& 调用脚本块 (.):
$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.

特定于外部程序的注意事项:
  • 外部程序 ,因为它们在 PowerShell 的类型系统之外运行, 只通过它们的成功流(stdout)返回字符串 ;同样,PowerShell 只通过管道向外部程序发送字符串。 [1]
  • 字符编码问题 因此可以发挥作用:
  • 在通过管道向外部程序发送数据时,PowerShell 使用存储在 $OutVariable 首选项变量中的编码;在 Windows PowerShell 中默认为 ASCII(!),在 PowerShell [Core] 中默认为 UTF-8。
  • 从外部程序接收数据时,PowerShell 使用存储在 [Console]::OutputEncoding 中的编码对数据进行解码,这在两个 PowerShell 版本中都默认为系统的事件 OEM 代码页。
  • 参见 this GitHub issue 了解更多信息; this answer 讨论了仍处于测试阶段(截至撰写本文时)的 Windows 10 功能,该功能允许您将 UTF-8 设置为系统范围内的 ANSI 和 OEM 代码页。


  • 如果输出包含 多于 1 行 ,PowerShell 默认将其拆分为 字符串数组 。更准确地说,输出行存储在 [System.Object[]] 类型的数组中,其元素是字符串 ( [System.String] )。
  • 如果您 希望输出是单个的、可能是多行的字符串 ,请使用 -join 运算符(您也可以通过管道连接到 Out-String ,但总是会添加一个尾随换行符):$cmdOutput = (<command>) -join [Environment]::NewLine
  • 将 stderr 与 2>&1 合并到标准输出中,以便也将其捕获为成功流的一部分,附带 警告 _0x10456792
  • 要在源代码中执行此操作 , cmd.exe 处理重定向 ,在 Unixtcode-like 平台上使用以下习语:sh$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 并在 <command> 完成后退出。
  • 注意 <command> 周围的单引号,它确保重定向被传递给 2>&1 而不是被 PowerShell 解释。
  • 注意,涉及cmd.exe意味着它的转义字符和扩展环境变量的规则起作用了,默认情况下,除了PowerShell本身的要求;在 PS v3+ 中,您可以使用特殊参数 cmd.exe(所谓的停止解析符号)来关闭 PowerShell 对其余参数的解释,除了 --% 风格的环境变量引用,例如 cmd.exe
  • 请注意,由于您使用这种方法在源头合并 stdout 和 stderr,因此 您将无法在 PowerShell 中区分源自 stdout 和源自 stderr 的行 ;如果您确实需要这种区别,请使用 PowerShell 自己的 %PATH% 重定向 - 见下文。

  • 使用 PowerShell 的 2>&1 重定向来了解哪些行来自 流:
  • Stderr 输出被捕获为 错误记录 ( 2>&1 ),而不是字符串,所以 _0791061 字符串可能包含一个字符串,每行 _079106 记录每行 stdout204 条 stdout204 记录,每行都包含一个 stdout25 字符串,代表 stdout204 行.请注意,根据 [System.Management.Automation.ErrorRecord] 的要求,字符串和错误记录都是通过 PowerShell 的成功输出流接收的)。
  • 注意:以下 仅适用于 Windows PowerShell - 这些 问题已在 PowerShell [Core] v6+ 中得到纠正,尽管下面的过滤技术也可以显示有用的 ji 类型对象(通过 ji )。
  • 在控制台中,错误记录以红色打印,第一个默认生成多行显示,格式与cmdlet的非终止错误显示的格式相同;随后的错误记录也以红色打印,但仅在一行上打印其错误消息。
  • 输出到控制台时,字符串通常首先出现在输出数组中,然后是错误记录(至少在“同时”输出的一批 stdout/stderr 行中),但幸运的是,捕获时为 输出,它正确交错 ,使​​用与没有 2>&1 时相同的输出顺序;换句话说: 输出到控制台时,捕获的输出不反射(reflect)外部命令生成 std​​out 和 stderr 行的顺序。
  • 如果你用 $_ -is [System.Management.Automation.ErrorRecord] 在单个字符串中捕获整个输出, PowerShell 将添加额外的行 ,因为包含这样的字符串 2>&1 错误代码类别(代码)和错误代码表示奇怪的是,这仅适用于第一个错误记录。
  • 要解决此问题,请将 Out-String 方法应用于每个输出对象,而不是通过管道传输到 At line:... :+ CategoryInfo ... ;
    在 PS v3+ 中,您可以简化为:.ToString()(作为奖励,如果未捕获输出,即使在打印到控制台时也会产生正确的交错输出。)
  • 或者, 过滤掉错误记录,并使用 Out-String 将它们发送到 PowerShell 的错误流(作为奖励,如果没有捕获输出,即使打印到控制台也会产生正确的交错输出):




  • $cmdOutput = <command> 2>&1 | ForEach-Object {
      if ($_ -is [System.Management.Automation.ErrorRecord]) {
        Write-Error $_
      } else {
        $_
      }
    }
    

    从 PowerShell 7.1 开始,旁白重新传递参数 :
  • 就空字符串参数和包含嵌入 $cmdOutput = <command> 2>&1 | % { $_.ToString() } 字符的参数而言,将参数传递给外部程序被破坏。
  • 此外,不满足 $cmdOutput = <command> 2>&1 | % ToString 和批处理文件等可执行文件的(非标准)引用需求。

  • 仅针对前一个问题,可能会进行修复(尽管修复将在类 Unix 平台上完成),如 this answer 中所述,其中还详细介绍了所有当前问题和解决方法。
    如果可以选择安装第三方模块,则 this answer ( Write-Error ) 中的 " 函数提供了一个全面的解决方案。

    [1] 从 PowerShell 7.1 开始,PowerShell 在与外部程序 通信时只知道字符串。 PowerShell 管道中通常没有原始字节数据的概念。如果您想要从外部程序返回原始字节数据,您必须转至 msiexec.exe (Windows) 或 ie (Unix),保存到那里的文件,然后在 PowerShell 中读取该文件。有关更多信息,请参阅 Native module

    关于powershell - 如何将输出从 PowerShell 中的外部进程捕获到变量中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8097354/

    相关文章:

    powershell - 在 Powershell 中从列表中交互式选择

    c# - 调试 PowerShell 脚本

    macos - mac 上进程的内存快照?

    Powershell 脚本 Web 客户端超时(1 :40) calling an SSRS report

    Azure Runbook 命令

    azure - 如何从 Azure 命令 "results"的 'az group list in powershell' 数组中只选择一个属性?

    子进程不会因kill(pid_t, SIGTERM)而死亡

    c# - 检查我的应用程序是否有权读取进程信息以及读取/写入文件和目录

    multithreading - 为什么进程调度不叫线程调度?

    bash - 为什么进程替换并不总是适用于 bash 中的 while 循环?