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


158

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

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

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


3
首先,最重要的是:不要使用它Start-Process来同步执行(定义上是外部的)控制台应用程序-就像在任何shell中一样,直接调用它们即可;即:netdom /verify $pc /domain:hosp.uhhg.org。这样做可以使应用程序保持与调用控制台的标准流连接,从而允许通过简单的分配捕获其输出$output = netdom ...。下面给出的大多数答案都隐含地放弃Start-Process了直接执行。
mklement0

@ mklement0,除非可能要使用-Credential参数
CJBS

@CJBS是的,为了以不同的用户身份运行,Start-Process必须使用,但是只有这样(如果要在单独的窗口中运行命令),则必须如此。人们应当意识到在这种情况下,不可避免的局限性:没有能力捕获输出,除非-非交错- 文本文件,通过-RedirectStandardOutput-RedirectStandardError
mklement0

Answers:


161

你有没有尝试过:

$OutputVariable = (Shell command) | Out-String


我尝试使用“ =”将其分配给变量,但没有尝试先将输出通过管道传递给Out-String。我会尝试的。
亚当·贝特拉姆,

10
我不明白这里发生了什么,无法使其正常工作。“ Shell”是powershell关键字吗?因此,我们实际上不使用Start-Process cmdlet吗?您能否举一个具体的例子(即用一个真实的例子代替“ Shell”和/或“ command”)。
致命的狗狗

@deadlydog用Shell Command您想要运行的任何内容替换。就这么简单。
JNK 2013年

1
@stej,你是对的。我主要是在澄清您注释中的代码与答案中的代码具有不同的功能。像我这样的初学者可能会被此类行为的细微差别所吸引!
2013年

1
@Atique我遇到了同样的问题。事实证明,例如,如果您使用-i选项而不指定输出文件,则ffmpeg有时会写入stderr而不是stdout 。2>&1解决方案是使用其他一些答案中所述的方法重定向输出。
jmbpiano

159

注意:问题中的命令使用Start-Process,这会阻止直接捕获目标程序的输出。通常, 不要用于Start-Process同步执行控制台应用程序-就像在任何shell中一样,直接调用它们即可。这样可以使应用程序保持与调用控制台的标准流连接,从而允许通过简单的分配捕获其输出$output = netdom ...,如下所述。

从根本上讲,从外部实用程序捕获输出与使用PowerShell本地命令的工作原理相同(您可能需要重新了解如何执行外部工具):

$cmdOutput = <command>   # captures the command's success stream / stdout

请注意,如果产生一个以上的输出对象,则$cmdOutput接收一个对象数组,这对于外部程序而言是指包含程序输出的字符串数组。 如果您希望始终收到一个单行(可能是多行)字符串,请使用<command>
$cmdOutput
$cmdOutput = <command> | Out-String


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

<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)返回字符串

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

  • 如果您希望输出是单个(可能是多行)字符串,请通过以下管道传递给Out-String
    $cmdOutput = <command> | Out-String

  • 使用重定向stderr到stdout2>&1,以便也将其捕获为成功流的一部分,附带警告

    • 为了使2>&1合并输出和错误的来源cmd.exe处理重定向,使用下列成语:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /ccmd.exe使用command 调用<command>并在<command>完成后退出。
      • 注意周围的单引号2>&1,以确保将重定向传递给cmd.exePowerShell而不是由PowerShell解释。
      • 请注意,默认情况下,除PowerShell自身的要求外,包含cmd.exe是指转义字符和扩展环境变量的规则起作用;在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的成功输出流接收的。

      • 在控制台中,错误记录以红色打印,默认情况下,一个记录将以多行显示的形式显示cmdlet的非终止错误;后续错误记录也以红色打印,但仅将错误消息打印在一行上

      • 当输出到控制台时,字符串通常第一输出数组中,随后由错误记录(在一个批次的stdout / stderr的线输出中至少“同时”),但是,幸运的是,当你捕获输出,使用与您不用的输出顺序相同的输出顺序进行适当的交错2>&1 ; 换句话说:当输出到控制台时,捕获的输出不反映外部命令生成stdout和stderr行的顺序。

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

        • 要变通解决此问题,请将该.ToString()方法应用于每个输出对象,而不是对Out-String:进行管道传递
          $cmdOutput = <command> 2>&1 | % { $_.ToString() }
          在PS v3 +中,您可以简化为:(
          $cmdOutput = <command> 2>&1 | % ToString
          此外,如果未捕获输出,即使在打印到控制台时,也会产生正确的交错输出。)
      • 或者,过滤错误记录并将其发送到PowerShell的错误流中Write-Error(此外,如果未捕获输出,即使在打印到控制台时,也会产生正确的交错输出):

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

在我选择了可​​执行文件的路径和参数之后,这最终为我工作了,将它们扔进一个字符串并将其视为我的<command>。

2
@Dan:当PowerShell本身解释时<command>,您不能将可执行文件及其参数组合在单个字符串中;通过cmd /c您进行调用可能会这样做,并且取决于情况是否有意义。您指的是哪种情况,能否举一个最小的例子?
mklement0

作品:$命令= “C:\ mycommand.exe” + $参数数量..... $输出= CMD / C $命令'2>&1'

1
@Dan:是的,尽管您不需要中间变量和使用+运算符对字符串进行显式构造,这是可行的;以下内容也可以工作:cmd /c c:\mycommand.exe $Args '2>&1'- $Args在这种情况下,PowerShell负责将元素作为空格分隔的字符串进行传递,这是一种称为splatting的功能。
mklement0

最后,一个正确的答案可以在PS6.1 +中使用。酱汁中的秘密确实是其中的'2>&1'一部分,并且没有()像许多脚本所包含的那样包含其中。
not2qubit

24

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

$cmdOutput = command 2>&1

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

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

4
2>&1是什么意思?'名为2的运行命令并将其输出放入名为1的运行命令中?
理查德

7
这意味着“将标准错误输出(文件描述符2)重定向到标准输出(文件描述符1)要移到的位置”。基本上,将正常消息和错误消息重定向到同一位置(在这种情况下,如果未将stdout重定向到其他地方(例如文件),则为控制台)。
Giovanni Tirloni 2014年

11

或尝试一下。它将捕获输出到变量$ scriptOutput中:

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

$scriptOutput

7
-1,不必要地复杂。$scriptOutput = & "netdom.exe" $params
CharlesB 2013年

8
删除零位,这对于同时管道输送到外壳和变量非常有用。
ferventcoder

10

另一个真实的例子:

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

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

注意:别忘了&命令前面字符,但要放在引号之外。

错误输出也会被收集。

我花了一些时间才能使此组合生效,所以我认为我会分享。


8

我尝试了答案,但就我而言,没有得到原始输出。而是将其转换为PowerShell异常。

我得到的原始结果:

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

2

我有以下工作:

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

$ result使您有需要



0

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.