PowerShell で外部プロセスからの出力を変数にキャプチャするにはどうすればよいですか? 質問する

PowerShell で外部プロセスからの出力を変数にキャプチャするにはどうすればよいですか? 質問する

外部プロセスを実行し、そのコマンド出力を PowerShell の変数にキャプチャしたいと思います。現在これを使用しています:

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

コマンドが実行されていることを確認しましたが、出力を変数にキャプチャする必要があります。つまり、これはファイルにリダイレクトするだけなので、-RedirectOutput は使用できません。

ベストアンサー1

注: 質問のコマンドは を使用しているためStart-Process、対象プログラムの出力を直接キャプチャすることはできません。一般的に、コンソールアプリケーションを同期的に実行するために使用しないでください。直接Start-Process呼び出すだけです。$output = netdom ...、他のシェルと同様に。これにより、アプリケーションの出力ストリームが PowerShell のストリームに接続されたままになり、以下に詳述するように、単純な割り当て( stderr2>出力の場合は を使用)によって出力をキャプチャできるようになります。

基本的に、外部プログラムからの出力のキャプチャは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

が1つ以上の出力オブジェクトを生成する場合、はオブジェクトの配列$cmdOutput を受け取る<command>ことに注意してください。外部プログラムの場合、これはプログラムの出力行を含む文字列[1]配列を意味します

結果が常に配列になるようにしたい場合(たとえ1つのオブジェクトしか出力されない場合でも) 、変数を配列( )として型制約する[object[]]か、コマンドを で囲みます@(...)配列部分式演算子: [2]

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

対照的に、$cmdOutput常に単一の(複数行の可能性がある)文字列を受け取りたい場合は、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 のみの改行を作成します。PowerShell はこれをすべてのプラットフォームで問題なく受け入れます。プラットフォームに適した改行 (Windows では CRLF、Unix では LF) が必要な場合は、[Environment]::NewLine代わりに を使用します。


出力を変数にキャプチャし画面に印刷するには:

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

または、コマンドレットまたは高度な<command>関数の場合は、共通パラメータ
-OutVariable/-ov
:

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

では-OutVariable、他のシナリオとは異なり、出力されるオブジェクトが1 つだけの場合でも、 は常にコレクション になる$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 は、引用符で囲まれた文字列で始まるステートメントを既定で式モードで解析しますが、コマンド (コマンドレット、外部プログラム、関数、エイリアス) を呼び出すには引数モードが必要であり、これが&保証されます。

$(...)& { ... }/の主な違いは. { ... }、前者は入力をすべてメモリに収集してから全体として返すのに対し、後者は出力をストリーミングし、1 つずつのパイプライン処理に適していることです。


リダイレクトも基本的には同じように機能します (ただし、以下の注意事項を参照してください)。

$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 は設定変数に格納されているエンコードを使用します。Windows PowerShell では既定で ASCII(!) に設定され、PowerShell [Core] では UTF-8 に設定されます。$OutVariable

      • 外部プログラムからデータを受信すると、PowerShell は に格納されているエンコードを使用して[Console]::OutputEncodingデータをデコードします。どちらの PowerShell エディションでも、既定ではシステムのアクティブなOEMコード ページが使用されます。

      • 見るこの答え詳細については;この答えシステム全体で UTF-8 を ANSI と OEM の両方のコード ページとして設定できる、まだベータ版 (この記事の執筆時点では) の Windows 10 機能について説明します。

  • 出力に1 行以上含まれる場合、PowerShell は既定でそれを文字列の配列[System.Object[]]に分割します。より正確には、出力行は 1 行ずつストリーミングされ、キャプチャされると、要素が文字列 ( ) である型の配列に格納されます[System.String]

  • 出力を単一の、場合によっては複数行の文字列にしたい場合は、-join演算子を使用します (代わりに にパイプすることもできますOut-Stringが、その場合必ず末尾に改行が追加されます)。
    $cmdOutput = (<command>) -join [Environment]::NewLine

  • stderrをstdout にマージして2>&1、成功ストリームの一部としてもキャプチャできるようにするには、次の注意点があります。

    • ソースでこれを実行するには、次の慣用句を使用してリダイレクトを処理します(cmd.exesh Unix 系プラットフォームでは と同様に動作します)。
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = (cmd /c <command> '2>&1') -join "`r`n" # single string

      • cmd /ccmd.exeコマンドで呼び出し<command><command>完了後に終了します。

      • を囲む一重引用符に注意してください2>&1。これにより、リダイレクトがcmd.exePowerShell によって解釈されるのではなく、 に渡されることが保証されます。

      • を組み込むということは、PowerShell 独自の要件に加えて、文字をエスケープしたり環境変数を展開したりするルールがデフォルトで適用されることを意味します。PS v3+ では、 などの - スタイルの環境変数参照を除き、特別なパラメーター (いわゆる解析停止記号 ) を使用してcmd.exe PowerShellによる残りのパラメーターの解釈をオフにすることができます。--%cmd.exe%PATH%

      • この方法ではソースでstdout と stderr をマージするため、PowerShell でstdout から生成された行と stderr から生成された行を区別できないことに注意してください。この区別が必要な場合は、PowerShell 独自の2>&1リダイレクトを使用します (以下を参照)。

    • PowerShell の リダイレクトを使用して2>&1、どの行がどのストリームから来たのかを確認します

      • stderr出力は文字列ではなくエラー レコード( )としてキャプチャされる[System.Management.Automation.ErrorRecord]ため、出力配列には文字列(各文字列は stdout 行を表す) とエラー レコード(各レコードは stderr 行を表す)が混在する場合があります。 の要求に応じて、文字列とエラー レコードの両方が PowerShell の成功2>&1出力ストリームを介して受信されることに注意してください。

      • 注: 以下はWindows PowerShellにのみ適用されます。これらの問題はPowerShell [Core] v6+で修正されていますが、以下に示すオブジェクト タイプによるフィルタリング手法 ( $_ -is [System.Management.Automation.ErrorRecord]) も役立ちます。

      • コンソールでは、エラー レコードはで印刷され、デフォルトでは最初レコードは、コマンドレットの終了しないエラーが表示されるのと同じ形式で複数行表示されます。後続のエラー レコードも赤で印刷されますが、エラーメッセージのみが1 行で印刷されます。

      • コンソールに出力する場合、通常は文字列が出力配列の最初に表示され、その後にエラー レコードが続きます (少なくとも、stdout/stderr 行のバッチ内では「同時に」出力されます)。ただし、幸い、出力をキャプチャすると、 を使用しない場合と同じ出力順序を使用して、適切にインターリーブされます2>&1。つまり、コンソールに出力する場合、キャプチャされた出力は、外部コマンドによって stdout 行と stderr 行が生成された順序を反映しません。

      • を使用して出力全体を1 つの文字列Out-Stringでキャプチャすると、 PowerShell によって余分な行が追加されますAt line:...。これは、エラー レコードの文字列表現に、場所 ( ) やカテゴリ ( )などの追加情報が含まれているためです。興味深いことに、これは最初のエラー レコード+ CategoryInfo ...にのみ適用されます。

        • この問題を回避するには、:.ToString()にパイプする代わりに、各出力オブジェクトにメソッドを適用します。PS v3+ では、次のように簡略化できます。(ボーナスとして、出力がキャプチャされない場合、コンソールに印刷する場合でも、適切にインターリーブされた出力が生成されます。)Out-String
          $cmdOutput = <command> 2>&1 | % { $_.ToString() }

          $cmdOutput = <command> 2>&1 | % ToString

        • または、エラー レコードをフィルター処理して、PowerShell のエラー ストリームに送信しますWrite-Error(ボーナスとして、出力がキャプチャされない場合でも、コンソールに印刷する場合でも、適切にインターリーブされた出力が生成されます)。

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

PowerShell 7.2.x 時点での引数渡しに関する補足事項:

  • 空の文字列引数および埋め込み 文字を含む引数に関しては、外部プログラムに引数を渡すことができなくなります"

  • さらに、 やバッチ ファイルなどの実行可能ファイルの (非標準の) 引用符の必要性にはmsiexec.exe対応していません。

前者の問題に関しては、修正が予定されているかもしれない(ただし、修正はUnix系プラットフォームでは完全になるだろう)。この答えこれには、現在のすべての問題と回避策も詳細に記載されています。

サードパーティのモジュールをインストールするオプションがあるie場合は、NativeモジュールInstall-Module Native)は包括的なソリューションを提供します。


[1] PowerShell 7.1 以降、PowerShell は外部プログラムと通信する際に文字列のみを認識します。PowerShell パイプラインには一般に生のバイトデータの概念はありません。外部プログラムから生のバイトデータが返されるようにするには、 cmd.exe /c(Windows) または(Unix)にシェルアウトして、そこにsh -cファイルに保存し、そのファイルを PowerShell で読み込む必要があります。この答え詳細については。

[2] 2つのアプローチ(組み合わせることもできます)には微妙な違いがありますが、通常は問題にはなりません。コマンドに出力がない場合、[array]型制約アプローチでは が$nullターゲット変数に格納されますが、の場合は空の([object[])配列に@(...)なります。さらに、[array]型制約は、同じ変数への将来の(空でない)割り当ても配列に強制されることを意味します。

おすすめ記事