簡體   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