【问题标题】:How do I capture the output into a variable from an external process in PowerShell?如何从 PowerShell 中的外部进程将输出捕获到变量中?
【发布时间】:2011-12-27 04:41:53
【问题描述】:

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

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

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

【问题讨论】:

  • 首先:不要使用 Start-Process 同步执行(根据定义为外部)控制台应用程序 - 只需直接调用它们,如任何外壳;也就是说:netdom /verify $pc /domain:hosp.uhhg.org。这样做可以使应用程序连接到调用控制台的标准流,从而允许通过简单的赋值$output = netdom ... 捕获其输出。下面给出的大多数答案都隐含地放弃了Start-Process,转而支持直接执行。
  • @mklement0 除非有人想使用-Credential 参数
  • @CJBS 是的,为了以不同的用户身份运行,必须使用Start-Process - 但只有这样(如果你想运行命令在单独的窗口中)。并且应该意识到在这种情况下不可避免的限制:无法捕获输出,除了 - 非交错 - files 中的 text,通过 -RedirectStandardOutput 和 @ 987654329@.

标签: powershell process


【解决方案1】:

你试过了吗:

$OutputVariable = (Shell command) | Out-String

【讨论】:

  • 我尝试使用“=”将其分配给变量,但我没有尝试先将输出通过管道传输到 Out-String。我会试试看。
  • 我不明白这里发生了什么,无法让它工作。 “Shell”是powershell关键字吗?所以我们实际上并没有使用 Start-Process cmdlet?请您举一个具体的例子(即用一个真实的例子替换“Shell”和/或“command”)。
  • @deadlydog 将Shell Command 替换为您想要运行的任何内容。就是这么简单。
  • @stej,你是对的。我主要是澄清您评论中的代码与答案中的代码具有不同的功能。像我这样的初学者可能会因为这些细微的行为差异而感到厌烦!
  • @Atique 我遇到了同样的问题。事实证明,ffmpeg 有时会写入 stderr 而不是 stdout,例如,如果您使用 -i 选项而不指定输出文件。如其他一些答案中所述,使用2>&1 重定向输出是解决方案。
【解决方案2】:

如果你也想重定向错误输出,你必须这样做:

$cmdOutput = command 2>&1

或者,如果程序名称中有空格:

$cmdOutput = & "command with spaces" 2>&1

【讨论】:

  • 2>&1 是什么意思? '运行名为 2 的命令并将其输出放入名为 1 的运行命令中'?
  • 它的意思是“将标准错误输出(文件描述符2)重定向到标准输出(文件描述符1)要去的地方”。基本上,将正常和错误消息重定向到同一个地方(在这种情况下是控制台,如果 stdout 没有重定向到其他地方——比如文件)。
【解决方案3】:

或者试试这个。它会将输出捕获到变量 $scriptOutput:

& "netdom.exe" $params | Tee-Object -Variable scriptOutput | Out-Null

$scriptOutput

【讨论】:

  • -1,不必要的复杂。 $scriptOutput = & "netdom.exe" $params
  • 删除out-null,这对于同时通过管道连接到shell和变量非常有用。
【解决方案4】:

另一个现实生活中的例子:

$result = & "$env:cust_tls_store\Tools\WDK\x64\devcon.exe" enable $strHwid 2>&1 | Out-String

请注意,此示例包含一个路径(以环境变量开头)。请注意,引号必须围绕路径和 EXE 文件,而不是参数!

注意:不要忘记命令前面的 & 字符,但要在引号之外。

错误输出也被收集。

我花了一段时间才让这个组合发挥作用,所以我想我会分享它。

【讨论】:

    【解决方案5】:

    我尝试了答案,但就我而言,我没有得到原始输出。相反,它被转换为 PowerShell 异常。

    我得到的原始结果:

    $rawOutput = (cmd /c <command> 2`>`&1)
    

    【讨论】:

      【解决方案6】:

      注意:问题中的命令使用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 ; &lt;command&gt; 是一个占位符,用于下面的任何有效命令):

      # 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
      

      请注意,如果&lt;command&gt; 产生超过 1 个输出对象$cmdOutput 会收到一个对象的数组,在这种情况下外部程序表示一个字符串[1]数组,其中包含程序的输出 .

      如果您想确保结果是总是一个数组 - 即使只输出 一个 对象,输入-将变量约束为数组,或者将命令包装在@()array-subexpression operator):

      [array] $cmdOutput = <command> # or: $cmdOutput = @(<command>)
      

      相比之下,如果您希望 $cmdOutput 始终收到 单行 - 可能是多行 - 字符串,请使用 Out-String ,但请注意总是会添加尾随换行符GitHub issue #14444 讨论了这种有问题的行为):

      # Note: Adds a trailing newline.
      $cmdOutput = <command> | Out-String
      

      通过调用外部程序——根据定义,它只会在PowerShell[1]中返回字符串——你可以通过使用-join operator 改为:

      # NO trailing newline.
      $cmdOutput = (<command>) -join "`n"
      

      注意:为简单起见,上面使用 "`n" 创建 Unix 样式的 LF-only 换行符,PowerShell 很乐意在所有平台上接受它;如果您需要适合平台的换行符(Windows 上的 CRLF,Unix 上的 LF),请改用 [Environment]::NewLine


      捕获变量中的输出打印到屏幕

      <command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed
      

      或者,如果&lt;command&gt;是一个cmdlet高级函数,你可以使用common parameter
      -OutVariable / -ov

      <command> -OutVariable cmdOutput   # cmdlets and advanced functions only
      

      请注意,对于-OutVariable,与其他场景不同,$cmdOutput始终是一个集合,即使只有一个对象是输出。具体来说,返回一个类似数组的 [System.Collections.ArrayList] 类型的实例。
      有关此差异的讨论,请参阅 this GitHub issue


      要捕获多个命令的输出,请使用子表达式 ($(...)) 或使用 &amp; 或 @ 调用脚本块 ({ ... }) 987654362@:

      $cmdOutput = $(<command>; ...)  # subexpression
      
      $cmdOutput = & {<command>; ...} # script block with & - creates child scope for vars.
      
      $cmdOutput = . {<command>; ...} # script block with . - no child scope
      

      请注意,一般需要在名称/路径被引用的单个命令前加上&amp;(呼叫操作员)前缀 - 例如,$cmdOutput = &amp; 'netdom.exe' ... -与外部程序本身无关(它同样适用于 PowerShell 脚本),但 语法 要求:PowerShell 解析以 中的引号字符串开头的语句默认情况下是表达式模式,而调用命令(cmdlet、外部程序、函数、别名)需要参数模式,这是&amp;确保的。

      $(...)&amp; { ... } / . { ... } 之间的主要区别在于前者将所有输入收集到内存中然后将其作为一个整体返回,而后者 em> 输出,适合一对一的流水线处理。


      重定向从根本上也同样有效(但请参阅下面的注意事项):

      $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 answerthis answer 讨论了仍处于测试阶段(撰写本文时)的 Windows 10 功能,该功能允许您将 UTF-8 设置为系统范围内的 ANSI 和 OEM 代码页。

      • 如果输出包含多于 1 行,PowerShell 默认将其拆分为 数组 字符串。更准确地说,输出行存储在 [System.Object[]] 类型的数组中,其元素是字符串 ([System.String])。

      • 如果您希望输出是单行,可能是多行字符串,请使用-join 运算符(您也可以管道到Out-String,但总是添加一个尾随换行符):
        $cmdOutput = (&lt;command&gt;) -join [Environment]::NewLine

      • stderr2&gt;&amp;1 合并到标准输出中,以便将其捕获为成功流的一部分,附带警告

        • 要做到这一点在源cmd.exe 处理重定向,使用以下成语(与sh 类似在类 Unix 平台上):
          $cmdOutput = cmd /c &lt;command&gt; '2&gt;&amp;1' # *array* of strings (typically)
          $cmdOutput = (cmd /c &lt;command&gt; '2&gt;&amp;1') -join "`r`n" # single string

          • cmd /c 使用命令&lt;command&gt; 调用cmd.exe 并在&lt;command&gt; 完成后退出。

          • 注意2&gt;&amp;1 周围的单引号,它确保重定向被传递到cmd.exe 而不是被PowerShell 解释。

          • 注意,涉及cmd.exe 意味着 its 用于转义字符和扩展环境变量的规则在默认情况下发挥作用,除了 PowerShell 自身的要求;在 PS v3+ 中,您可以使用特殊参数 --%(所谓的停止解析符号)关闭 PowerShell 对其余参数的解释,cmd.exe 样式的环境变量引用除外比如%PATH%

          • 请注意,由于您使用这种方法在源合并 stdout 和 stderr,您将无法区分源自 stdout 和源自 stderr 的行 在 PowerShell 中;如果您确实需要这种区别,请使用 PowerShell 自己的 2&gt;&amp;1 重定向 - 见下文。

        • 使用 PowerShell 的 2&gt;&amp;1 重定向来了解哪些行来自哪个流

          • Stderr 输出被捕获为 错误记录 ([System.Management.Automation.ErrorRecord]),而不是字符串,因此 输出数组可能包含 mix 字符串(每个字符串代表一个标准输出行)和错误记录(每个记录代表一个标准错误行)。请注意,根据2&gt;&amp;1 的要求,字符串和错误记录都是通过 PowerShell 的 success 输出流接收的)。

          • 注意:以下仅适用于 Windows PowerShell - 这些问题已在 PowerShell [Core] v6+中得到纠正strong>,尽管下面显示的按对象类型过滤技术 ($_ -is [System.Management.Automation.ErrorRecord]) 在那里也很有用。

          • 在控制台中,错误记录以红色打印,而第一个默认产生多行显示,同理cmdlet 的非终止错误将显示的格式; 后续错误记录也以红色打印,但仅在单行上打印其错误消息

          • 当输出到控制台时,字符串通常在输出数组中排在第一个,然后是错误记录(至少在一个一批 stdout/stderr 行“同时”输出),但幸运的是,当您捕获输出时,它会正确交错,使用与您相同的输出顺序没有2&gt;&amp;1就会得到;换句话说:输出到控制台时,捕获的输出不反映外部命令生成stdout和stderr行的顺序。

          • 如果您使用Out-String单个字符串中捕获整个输出,PowerShell 将添加额外的行,因为错误记录的字符串表示包含额外的信息,例如位置(At line:...)和类别(+ CategoryInfo ...);奇怪的是,这只适用于 first 错误记录。

            • 要解决此问题,请将.ToString() 方法应用于每个输出对象,而不是通过管道连接到Out-String:
              $cmdOutput = &lt;command&gt; 2&gt;&amp;1 | % { $_.ToString() };
              在 PS v3+ 中,您可以简化为:
              $cmdOutput = &lt;command&gt; 2&gt;&amp;1 | % ToString
              (作为奖励,如果没有捕获输出,即使在打印到控制台时也会产生正确的交错输出。)

            • 或者,过滤错误记录 out 并使用 Write-Error 将它们发送到 PowerShell 的错误流(作为奖励,如果未捕获输出,这会产生即使在打印到控制台时也能正确交错输出):

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

      关于 参数传递,从 PowerShell 7.1 开始

      • 对于 empty-string 参数和包含 嵌入 " 字符的参数,将参数传递给外部程序会被破坏。

      • 此外,不满足诸如msiexec.exe 和批处理文件等可执行文件的(非标准)引用需求。

      this answer 中所述,可能即将修复前一个问题(尽管该修复将在类似 Unix 的平台上完成),其中还详细说明了所有当前问题和解决方法。

      如果安装第三方模块是一个选项,Native module (Install-Module Native) 中的ie 函数提供了一个全面 解决方案。 p>


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

      【讨论】:

      • 这最终对我有用,因为我采用了我的可执行路径和我的参数,将它们扔进一个字符串并将其视为我的
      • @Dan:当 PowerShell 本身解释 &lt;command&gt; 时,您不得将可执行文件及其参数组合在一个字符串中;通过cmd /c 调用你可以这样做,这取决于情况是否有意义。你指的是哪个场景,你能举一个最小的例子吗?
      • 作品:$command = "c:\mycommand.exe" + $Args ..... $output = cmd /c $command '2>&1'
      • @Dan:是的,这行得通,尽管您不需要使用+ 运算符的中间变量和显式构造字符串;以下也适用:cmd /c c:\mycommand.exe $Args '2&gt;&amp;1' - 在这种情况下,PowerShell 负责将 $Args 的元素作为空格分隔的字符串传递,该功能称为 splatting
      • 终于找到了适用于 PS6.1+ 的正确答案。酱汁中的秘密确实是'2&gt;&amp;1' 部分,而不是像许多脚本那样包含在() 中。
      【解决方案7】:

      如果您想要做的只是捕获命令的输出,那么这将很有效。

      我用它来更改系统时间,因为[timezoneinfo]::local 总是产生相同的信息,即使您对系统进行了更改。这是我可以验证和记录时区更改的唯一方法:

      $NewTime = (powershell.exe -command [timezoneinfo]::local)
      $NewTime | Tee-Object -FilePath $strLFpath\$strLFName -Append
      

      意味着我必须打开一个新的 PowerShell 会话来重新加载系统变量。

      【讨论】:

        【解决方案8】:

        这件事对我有用:

        $scriptOutput = (cmd /s /c $FilePath $ArgumentList)
        

        【讨论】:

          【解决方案9】:

          我得到了以下工作:

          $Command1="C:\\ProgramData\Amazon\Tools\ebsnvme-id.exe"
          $result = & invoke-Expression $Command1 | Out-String
          

          $result 为您提供所需的

          【解决方案10】:

          我使用以下内容:

          Function GetProgramOutput([string]$exe, [string]$arguments)
          {
              $process = New-Object -TypeName System.Diagnostics.Process
              $process.StartInfo.FileName = $exe
              $process.StartInfo.Arguments = $arguments
              
              $process.StartInfo.UseShellExecute = $false
              $process.StartInfo.RedirectStandardOutput = $true
              $process.StartInfo.RedirectStandardError = $true
              $process.Start()
              
              $output = $process.StandardOutput.ReadToEnd()   
              $err = $process.StandardError.ReadToEnd()
              
              $process.WaitForExit()
              
              $output
              $err
          }
              
          $exe = "C:\Program Files\7-Zip\7z.exe"
          $arguments = "i"
              
          $runResult = (GetProgramOutput $exe $arguments)
          $stdout = $runResult[-2]
          $stderr = $runResult[-1]
              
          [System.Console]::WriteLine("Standard out: " + $stdout)
          [System.Console]::WriteLine("Standard error: " + $stderr)
          

          【讨论】:

            【解决方案11】:

            对我来说有什么诀窍,并且在使用外部命令以及标准错误标准输出流都可能是运行命令的结果时会起作用(或它们的混合),如下:

            $output = (command 2>&1)
            

            【讨论】:

              猜你喜欢
              • 2023-03-03
              • 2010-09-15
              • 1970-01-01
              • 2015-03-07
              • 2021-11-10
              • 1970-01-01
              • 2019-04-10
              • 1970-01-01
              相关资源
              最近更新 更多