繁体   English   中英

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

[英]How do I capture the output into a variable from an external process in PowerShell?

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

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

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

注意:问题中的命令使用Start-Process ,这会阻止直接捕获目标程序的输出。 通常,不要使用Start-Process同步执行控制台应用程序 - 只需直接调用它们,就像在任何 shell 中一样。 这样做使应用程序连接到调用控制台的标准流,允许通过简单的赋值$output = netdom ...捕获其输出,如下详述。

从根本上说,从外部程序捕获输出的工作方式与 PowerShell 本地命令相同(您可能需要复习一下如何执行外部程序<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

请注意,如果<command>产生1 个以上的输出对象$cmdOutput接收一个对象数组,在外部程序的情况下,这意味着包含程序输出字符串[1]数组

如果您想确保结果始终是一个数组- 即使只输出一个对象,请将变量类型约束为数组,或将命令包装在@() ,即数组子表达式运算符):

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

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

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

通过调用外部程序- 根据定义,它只在 PowerShell [1] 中返回字符串- 您可以通过使用-join运算符来避免这种情况:

# 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

或者,如果<command>cmdlet高级函数,则可以使用通用参数
-OutVariable / -ov :

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

请注意,使用-OutVariable ,与其他情况不同, $cmdOutput始终是一个集合,即使只输出一个对象。 具体来说,返回一个类似数组的[System.Collections.ArrayList]类型的实例。
有关此差异的讨论,请参阅此 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.

特定于外部程序的注意事项:

  • 外部程序,因为它们在 PowerShell 的类型系统之外运行,所以通过它们的成功流 (stdout)返回字符串 同样,PowerShell 只通过管道向外部程序发送字符串。 [1]

    • 因此,字符编码问题可以发挥作用:
      • 在通过管道外部程序发送数据时,PowerShell 使用存储在$OutVariable首选项变量中的编码; 在 Windows PowerShell 中默认为 ASCII(!),在 PowerShell [Core] 中默认为 UTF-8。

      • 从外部程序接收数据时,PowerShell 使用存储在[Console]::OutputEncoding的编码对数据进行解码,这在两个 PowerShell 版本中都默认为系统的活动OEM代码页。

      • 有关更多信息,请参阅此答案 此答案讨论了仍处于测试阶段(截至撰写本文时)的 Windows 10 功能,该功能允许您将 UTF-8 设置为系统范围内的 ANSI 和 OEM 代码页。

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

  • 如果您希望输出是单个的,可能是多行的string ,请使用-join运算符(您也可以通过管道连接到Out-String ,但这总是会添加一个尾随换行符):
    $cmdOutput = (<command>) -join [Environment]::NewLine

  • 使用2>&1stderr合并到 stdout 中,以便也将其捕获为成功流的一部分,但需要注意

    • 在源代码中执行此操作,请cmd.exe使用以下习语处理重定向(在类 Unix 平台上与sh类似):
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = (cmd /c <command> '2>&1') -join "`r`n" # single string

      • cmd /c使用命令<command>调用cmd.exe并在<command>完成后退出。

      • 请注意2>&1周围的单引号,这可确保将重定向传递给cmd.exe而不是由 PowerShell 解释。

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

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

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

      • stderr输出被捕获作为错误记录[System.Management.Automation.ErrorRecord]而不是字符串,所以输出阵列可以包含字符串混合物(每个字符串代表一个标准输出线)和错误记录(代表stderr的线的每个记录) 请注意,根据2>&1 ,字符串和错误记录都是通过 PowerShell 的成功输出流接收的)。

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

      • 在控制台中,错误记录以red打印,一个默认情况下生成多行显示,格式与 cmdlet 的非终止错误将显示的格式相同; 随后的错误记录也以红色打印,但仅在一行上打印其错误消息

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

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

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

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

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

从 PowerShell 7.1 开始,重新传递参数

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

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

仅针对前一个问题,可能会进行修复(尽管修复将在类 Unix平台上完成),如本答案中所述,其中还详细介绍了所有当前问题和解决方法。

如果安装第三方模块是一个选项,来自Native模块( Install-Module Native ) 的ie功能提供了一个全面的解决方案。


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

你有没有尝试过:

$OutputVariable = (Shell command) | Out-String

如果您还想重定向错误输出,则必须执行以下操作:

$cmdOutput = command 2>&1

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

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

或者试试这个。 它会将输出捕获到变量 $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 文件括起来,而不是参数!

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

错误输出也会被收集。

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

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

我得到的原始结果:

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

这件事对我有用:

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

我得到了以下工作:

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

$result给你需要的

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

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

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

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

我使用以下内容:

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)

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

$output = (command 2>&1)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM