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

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

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

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

431763 次浏览

你有没有试过:

$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)

注意:问题中的命令使用Start-Process,这阻止了直接捕获目标程序的输出。一般来说,不使用Start-Process来同步执行控制台应用程序-只是调用它们直接,就像在任何shell中一样。这样做可以使应用程序的输出流连接到PowerShell的流,允许通过简单的赋值$output = netdom ...捕获它们的输出(并对stderr输出使用2>),如下所述。

基本上,从外部程序捕获输出,其工作原理与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

注意$cmdOutput 如果<command>产生多个输出对象,则接收对象的数组,在外部程序表示字符串[1]数组,包含程序的输出. c的情况下。

如果你想确保结果是always一个数组——即使只有一个对象输出,type-constrain变量作为数组([object[]]),或附上@(...)中的命令,array-subexpression运营商: [2]

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

相反,如果你想要$cmdOutput总是接收single -潜在多行- string,使用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
或者,如果<command>是一个cmdlet先进的函数,你可以使用公共参数
-OutVariable / -ov
:

<command> -OutVariable cmdOutput   # cmdlets and advanced functions only
注意,在-OutVariable中,与其他情况不同,$cmdOutput总是一个集合,即使只输出一个对象。具体来说,将返回类数组的[System.Collections.ArrayList]类型的实例。
关于此差异的讨论,请参见这个GitHub问题

要获取< em >多个< / em >命令的输出,可以使用子表达式($(...))或使用&.调用脚本块({ ... }):

$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的类型系统之外操作,只返回strings通过它们的成功流(stdout);类似地,PowerShell只通过pipeline.[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])。

  • 如果你希望输出为单线,潜在的多线字符串,使用-join操作符(你也可以选择管道到Out-String,但这总是添加一个换行符):
    $cmdOutput = (<command>) -join [Environment]::NewLine < / p >
  • 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 < / p >

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

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

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

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

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

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

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

      • 在控制台中,错误记录打印在红色的中,而1日在默认情况下产生多行显示,与cmdlet的非终止错误显示的格式相同;后续错误记录也以红色打印,但只打印他们的错误消息,在一行上。

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

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

          要解决这个问题,将.ToString()方法应用于每个输出对象,而不是管道到Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          在PS v3+中,您可以简化为:
          $cmdOutput = <command> 2>&1 | % ToString
          (作为奖励,如果输出没有被捕获,即使打印到控制台,这也会产生正确的交错输出。
        • 或者,过滤错误记录out并用Write-Error将它们发送到PowerShell的错误流(作为奖励,如果输出没有被捕获,即使打印到控制台也会产生正确的交错输出):

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

一个附加的re 参数-传递,从PowerShell 7.2.x开始:

  • 将参数传递给外部程序是关于空字符串参数和包含嵌入式 "字符的参数。

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

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

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


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

这两种方法之间有微妙的区别(你可以结合使用),尽管它们通常并不重要:如果命令有no output[array]类型约束方法会导致$null被存储在目标变量中,而在@(...)的情况下,它是一个空([object[])数组。此外,[array]类型约束意味着对同一变量的未来(非空)赋值也被强制转换为数组。

如果您所要做的只是捕获命令的输出,那么这将工作得很好。

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

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

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

这招对我很管用:

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

我得到了以下工作:

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

美元的结果给你必要的

我使用以下方法:

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)