简体   繁体   English

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

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

I'd like to run an external process and capture it's command output to a variable in PowerShell.我想运行一个外部进程并将它的命令输出捕获到 PowerShell 中的变量。 I'm currently using this:我目前正在使用这个:

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

I've confirmed the command is executing but I need to capture the output into a variable.我已经确认命令正在执行,但我需要将输出捕获到一个变量中。 This means I can't use the -RedirectOutput because this only redirects to a file.这意味着我不能使用 -RedirectOutput 因为这只会重定向到一个文件。

Note: The command in the question uses Start-Process , which prevents direct capturing of the target program's output.注意:问题中的命令使用Start-Process ,这会阻止直接捕获目标程序的输出。 Generally, do not use Start-Process to execute console applications synchronously - just invoke them directly , as in any shell.通常,不要使用Start-Process同步执行控制台应用程序 - 只需直接调用它们,就像在任何 shell 中一样。 Doing so keeps the application connected to the calling console's standard streams, allowing its output to be captured by simple assignment $output = netdom ... , as detailed below.这样做使应用程序连接到调用控制台的标准流,允许通过简单的赋值$output = netdom ...捕获其输出,如下详述。

Fundamentally , capturing output from external programs works the same as with PowerShell-native commands (you may want a refresher on how to execute external programs ; <command> is a placeholder for any valid command below):从根本上说,从外部程序捕获输出的工作方式与 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

Note that $cmdOutput receives an array of objects if <command> produces more than 1 output object , which in the case of an external program means a string [1] array containing the program's output lines .请注意,如果<command>产生1 个以上的输出对象$cmdOutput接收一个对象数组,在外部程序的情况下,这意味着包含程序输出字符串[1]数组

If you want to make sure that the result is always an array - even if only one object is output, type-constrain the variable as an array, or wrap the command in @() , the array-subexpression operator ):如果您想确保结果始终是一个数组- 即使只输出一个对象,请将变量类型约束为数组,或将命令包装在@() ,即数组子表达式运算符):

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

By contrast, if you want $cmdOutput to always receive a single - potentially multi-line - string , use Out-String , though note that a trailing newline is invariably added ( GitHub issue #14444 discusses this problematic behavior):相比之下,如果您希望$cmdOutput始终接收单行(可能是多行) string ,请使用Out-String ,但请注意,总是添加尾随换行符GitHub 问题 #14444讨论了这种有问题的行为):

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

With calls to external programs - which by definition only ever return strings in PowerShell [1] - you can avoid that by using the -join operator instead:通过调用外部程序- 根据定义,它只在 PowerShell [1] 中返回字符串- 您可以通过使用-join运算符来避免这种情况:

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

Note: For simplicity, the above uses "`n" to create Unix-style LF-only newlines, which PowerShell happily accepts on all platforms;注意:为简单起见,上面使用"`n"来创建 Unix 风格的 LF-only 换行符,PowerShell 很乐意在所有平台上接受; if you need platform-appropriate newlines (CRLF on Windows, LF on Unix), use [Environment]::NewLine instead.如果您需要适合平台的换行符(Windows 上为 CRLF,Unix 上为 LF),请改用[Environment]::NewLine


To capture output in a variable and print to the screen :捕获变量中的输出打印到屏幕

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

Or, if <command> is a cmdlet or advanced function, you can use common parameter或者,如果<command>cmdlet高级函数,则可以使用通用参数
-OutVariable / -ov : -OutVariable / -ov :

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

Note that with -OutVariable , unlike in the other scenarios, $cmdOutput is always a collection , even if only one object is output.请注意,使用-OutVariable ,与其他情况不同, $cmdOutput始终是一个集合,即使只输出一个对象。 Specifically, an instance of the array-like [System.Collections.ArrayList] type is returned.具体来说,返回一个类似数组的[System.Collections.ArrayList]类型的实例。
See this GitHub issue for a discussion of this discrepancy.有关此差异的讨论,请参阅此 GitHub 问题


To capture the output from multiple commands , use either a subexpression ( $(...) ) or call a script block ( { ... } ) with & or .要捕获多个命令的输出,请使用子表达式 ( $(...) ) 或使用&或 调用脚本块 ( { ... } ) . :

$cmdOutput = $(<command>; ...)  # subexpression

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

$cmdOutput = . {<command>; ...} # script block with . - no child scope

Note that the general need to prefix with & (the call operator) an individual command whose name/path is quoted - eg, $cmdOutput = & 'netdom.exe' ... - is not related to external programs per se (it equally applies to PowerShell scripts), but is a syntax requirement : PowerShell parses a statement that starts with a quoted string in expression mode by default, whereas argument mode is needed to invoke commands (cmdlets, external programs, functions, aliases), which is what & ensures.请注意,一般需要以& (调用运算符)作为前缀的单个命令的名称/路径被引用- 例如, $cmdOutput = & 'netdom.exe' ... - 与外部程序本身无关(它同样适用于 PowerShell 脚本),但有一个语法要求:PowerShell 默认在表达式模式下解析以带引号的字符串开头的语句,而调用命令(cmdlet、外部程序、函数、别名)需要参数模式,这就是&确保。

The key difference between $(...) and & { ... } / . { ... } $(...)& { ... } / 之间的主要区别. { ... } . { ... } is that the former collects all input in memory before returning it as a whole, whereas the latter stream the output, suitable for one-by-one pipeline processing. . { ... }是前者将所有输入收集在内存中,然后作为一个整体返回,而后者将输出流式处理,适用于一对一的流水线处理。


Redirections also work the same, fundamentally (but see caveats below):重定向从根本上也是一样的(但请参阅下面的警告):

$cmdOutput = <command> 2>&1 # redirect error stream (2) to success stream (1)

However, for external commands the following is more likely to work as expected:但是,对于外部命令,以下更有可能按预期工作:

$cmdOutput = cmd /c <command> '2>&1' # Let cmd.exe handle redirection - see below.

Considerations specific to external programs:特定于外部程序的注意事项:

  • External programs , because they operate outside PowerShell's type system, only ever return strings via their success stream (stdout);外部程序,因为它们在 PowerShell 的类型系统之外运行,所以通过它们的成功流 (stdout)返回字符串 similarly, PowerShell only ever sends strings to external programs via the pipeline.同样,PowerShell 只通过管道向外部程序发送字符串。 [1] [1]

    • Character-encoding issues can therefore come into play:因此,字符编码问题可以发挥作用:
      • On sending data via the pipeline to external programs, PowerShell uses the encoding stored in the $OutVariable preference variable;在通过管道外部程序发送数据时,PowerShell 使用存储在$OutVariable首选项变量中的编码; which in Windows PowerShell defaults to ASCII(!) and in PowerShell [Core] to UTF-8.在 Windows PowerShell 中默认为 ASCII(!),在 PowerShell [Core] 中默认为 UTF-8。

      • On receiving data from an external program, PowerShell uses the encoding stored in [Console]::OutputEncoding to decode the data, which in both PowerShell editions defaults to the system's active OEM code page.从外部程序接收数据时,PowerShell 使用存储在[Console]::OutputEncoding的编码对数据进行解码,这在两个 PowerShell 版本中都默认为系统的活动OEM代码页。

      • See this answer for more information;有关更多信息,请参阅此答案 this answer discusses the still-in-beta (as of this writing) Windows 10 feature that allows you to set UTF-8 as both the ANSI and the OEM code page system-wide.此答案讨论了仍处于测试阶段(截至撰写本文时)的 Windows 10 功能,该功能允许您将 UTF-8 设置为系统范围内的 ANSI 和 OEM 代码页。

  • If the output contains more than 1 line , PowerShell by default splits it into an array of strings .如果输出包含多于 1 行,PowerShell 默认将其拆分为字符串数组 More accurately, the output lines are stored in an array of type [System.Object[]] whose elements are strings ( [System.String] ).更准确地说,输出行存储在类型为[System.Object[]]的数组中,其元素是字符串 ( [System.String] )。

  • If you want the output to be a single , potentially multi-line string , use the -join operator (you can alternatively pipe to Out-String , but that invariably adds a trailing newline):如果您希望输出是单个的,可能是多行的string ,请使用-join运算符(您也可以通过管道连接到Out-String ,但这总是会添加一个尾随换行符):
    $cmdOutput = (<command>) -join [Environment]::NewLine

  • Merging stderr into stdout with 2>&1 , so as to also capture it as part of the success stream, comes with caveats :使用2>&1stderr合并到 stdout 中,以便也将其捕获为成功流的一部分,但需要注意

    • To do this at the source , let cmd.exe handle the redirection , using the following idioms (works analogously with sh on Unix-like platforms):在源代码中执行此操作,请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 invokes cmd.exe with command <command> and exits after <command> has finished. cmd /c使用命令<command>调用cmd.exe并在<command>完成后退出。

      • Note the single quotes around 2>&1 , which ensures that the redirection is passed to cmd.exe rather than being interpreted by PowerShell.请注意2>&1周围的单引号,这可确保将重定向传递给cmd.exe而不是由 PowerShell 解释。

      • Note that involving cmd.exe means that its rules for escaping characters and expanding environment variables come into play, by default in addition to PowerShell's own requirements;注意,涉及cmd.exe意味着的转义字符和扩展环境变量的规则发挥作用,默认情况下,除了 PowerShell 自己的要求; in PS v3+ you can use special parameter --% (the so-called stop-parsing symbol ) to turn off interpretation of the remaining parameters by PowerShell, except for cmd.exe -style environment-variable references such as %PATH% .在 PS v3+ 中,您可以使用特殊参数--% (所谓的停止解析符号)来关闭 PowerShell 对其余参数的解释,除了cmd.exe风格的环境变量引用,例如%PATH%

      • Note that since you're merging stdout and stderr at the source with this approach, you won't be able to distinguish between stdout-originated and stderr-originated lines in PowerShell;请注意,由于您使用这种方法在源代码处合并 stdout 和 stderr ,因此您将无法在 PowerShell 中区分源自 stdout 的行和源自 stderr 的行 if you do need this distinction, use PowerShell's own 2>&1 redirection - see below.如果您确实需要这种区别,请使用 PowerShell 自己的2>&1重定向 - 见下文。

    • Use PowerShell's 2>&1 redirection to know which lines came from what stream :使用PowerShell 的2>&1重定向来了解哪些行来自哪个流

      • Stderr output is captured as error records ( [System.Management.Automation.ErrorRecord] ), not strings, so the output array may contain a mix of strings (each string representing a stdout line) and error records (each record representing a stderr line) . stderr输出被捕获作为错误记录[System.Management.Automation.ErrorRecord]而不是字符串,所以输出阵列可以包含字符串混合物(每个字符串代表一个标准输出线)和错误记录(代表stderr的线的每个记录) Note that, as requested by 2>&1 , both the strings and the error records are received through PowerShell's success output stream).请注意,根据2>&1 ,字符串和错误记录都是通过 PowerShell 的成功输出流接收的)。

      • Note: The following only applies to Windows PowerShell - these problems have been corrected in PowerShell [Core] v6+ , though the filtering technique by object type shown below ( $_ -is [System.Management.Automation.ErrorRecord] ) can also be useful there.注意:以下仅适用于Windows PowerShell - 这些问题已在PowerShell [Core] v6+ 中得到纠正,尽管下面显示的按对象类型过滤技术 ( $_ -is [System.Management.Automation.ErrorRecord] ) 也很有用那里。

      • In the console, the error records print in red , and the 1st one by default produces multi-line display, in the same format that a cmdlet's non-terminating error would display;在控制台中,错误记录以red打印,一个默认情况下生成多行显示,格式与 cmdlet 的非终止错误将显示的格式相同; subsequent error records print in red as well, but only print their error message , on a single line .随后的错误记录也以红色打印,但仅在一行上打印其错误消息

      • When outputting to the console , the strings typically come first in the output array, followed by the error records (at least among a batch of stdout/stderr lines output "at the same time"), but, fortunately, when you capture the output, it is properly interleaved , using the same output order you would get without 2>&1 ;输出到控制台时,字符串通常首先出现在输出数组中,然后是错误记录(至少在“同时”输出的一批 stdout/stderr 行中),但幸运的是,当您捕获输出时,它是正确交错的,使用与没有2>&1相同的输出顺序; in other words: when outputting to the console , the captured output does NOT reflect the order in which stdout and stderr lines were generated by the external command.换句话说:当输出到控制台时,捕获的输出不反映外部命令生成 std​​out 和 stderr 行的顺序。

      • If you capture the entire output in a single string with Out-String , PowerShell will add extra lines , because the string representation of an error record contains extra information such as location ( At line:... ) and category ( + CategoryInfo ... );如果使用Out-String单个字符串中捕获整个输出PowerShell 将添加额外的行,因为错误记录的字符串表示包含额外的信息,例如位置( At line:... )和类别( + CategoryInfo ... ); curiously, this only applies to the first error record.奇怪的是,这仅适用于第一个错误记录。

        • To work around this problem, apply the .ToString() method to each output object instead of piping to Out-String :要解决此问题,请将.ToString()方法应用于每个输出对象,而不是管道到Out-String
          $cmdOutput = <command> 2>&1 | % { $_.ToString() } $cmdOutput = <command> 2>&1 | % { $_.ToString() } ; $cmdOutput = <command> 2>&1 | % { $_.ToString() } ;
          in PS v3+ you can simplify to:在 PS v3+ 中,您可以简化为:
          $cmdOutput = <command> 2>&1 | % ToString
          (As a bonus, if the output isn't captured, this produces properly interleaved output even when printing to the console.) (作为奖励,如果未捕获输出,即使在打印到控制台时也会产生正确的交错输出。)

        • Alternatively, filter the error records out and send them to PowerShell's error stream with Write-Error (as a bonus, if the output isn't captured, this produces properly interleaved output even when printing to the console):或者,过滤错误记录并使用Write-Error将它们发送到 PowerShell 的错误流(作为奖励,如果未捕获输出,即使打印到控制台也会产生正确的交错输出):

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

An aside re argument-passing , as of PowerShell 7.1 :从 PowerShell 7.1 开始,重新传递参数

  • Passing arguments to external programs is broken with respect to empty-string arguments and arguments that contain embedded " characters.对于空字符串参数和包含嵌入的"字符的参数,将参数传递给外部程序会被破坏。

  • Additionally, the (nonstandard) quoting needs of executables such as msiexec.exe and batch files aren't accommodated.此外,不满足诸如msiexec.exe和批处理文件之类的可执行文件的(非标准)引用需求。

For the former problem only, a fix may be coming (though the fix would be complete on Unix -like platforms), as discussed in this answer , which also details all the current problems and workarounds.仅针对前一个问题,可能会进行修复(尽管修复将在类 Unix平台上完成),如本答案中所述,其中还详细介绍了所有当前问题和解决方法。

If installing a third-party module is an option, the ie function from the Native module ( Install-Module Native ) offers a comprehensive solution.如果安装第三方模块是一个选项,来自Native模块( Install-Module Native ) 的ie功能提供了一个全面的解决方案。


[1] As of PowerShell 7.1, PowerShell knows only strings when communicating with external programs . [1]从 PowerShell 7.1 开始,PowerShell 在与外部程序通信时知道字符串 There is generally no concept of raw byte data in a PowerShell pipeline. PowerShell 管道中通常没有原始字节数据的概念。 If you want raw byte data returned from an external program, you must shell out to cmd.exe /c (Windows) or sh -c (Unix), save to a file there , then read that file in PowerShell.如果您想要从外部程序返回原始字节数据,您必须将 shell 输出到cmd.exe /c (Windows) 或sh -c (Unix),保存到那里的文件,然后在 PowerShell 中读取该文件。 See this answer for more information.有关更多信息,请参阅此答案

你有没有尝试过:

$OutputVariable = (Shell command) | Out-String

If you want to redirect the error output as well, you have to do:如果您还想重定向错误输出,则必须执行以下操作:

$cmdOutput = command 2>&1

Or, if the program name has spaces in it:或者,如果程序名称中有空格:

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

Or try this.或者试试这个。 It will capture output into variable $scriptOutput:它会将输出捕获到变量 $scriptOutput 中:

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

$scriptOutput

Another real-life example:另一个现实生活中的例子:

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

Notice that this example includes a path (which begins with an environment variable).请注意,此示例包含一个路径(以环境变量开头)。 Notice that the quotes must surround the path and the EXE file, but not the parameters!请注意,引号必须将路径和 EXE 文件括起来,而不是参数!

Note: Don't forget the & character in front of the command, but outside of the quotes.注意:不要忘记命令前面的&字符,但在引号之外。

The error output is also collected.错误输出也会被收集。

It took me a while to get this combination working, so I thought that I would share it.我花了一段时间才让这个组合起作用,所以我想我会分享它。

I tried the answers, but in my case I did not get the raw output.我尝试了答案,但就我而言,我没有得到原始输出。 Instead it was converted to a PowerShell exception.相反,它被转换为 PowerShell 异常。

The raw result I got with:我得到的原始结果:

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

这件事对我有用:

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

I got the following to work:我得到了以下工作:

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

$result gives you the needful $result给你需要的

If all you are trying to do is capture the output from a command, then this will work well.如果您要做的只是捕获命令的输出,那么这将很有效。

I use it for changing system time, as [timezoneinfo]::local always produces the same information, even after you have made changes to the system.我用它来改变系统时间,因为[timezoneinfo]::local总是产生相同的信息,即使你对系统进行了更改之后也是如此。 This is the only way I can validate and log the change in time zone:这是我可以验证和记录时区更改的唯一方法:

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

Meaning that I have to open a new PowerShell session to reload the system variables.这意味着我必须打开一个新的PowerShell会话来重新加载系统变量。

I use the following:我使用以下内容:

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