Start-Processによる標準出力とエラーのキャプチャ


112

とプロパティStart-ProcessにアクセスするときにPowerShellのコマンドにバグはありますか?StandardErrorStandardOutput

次のコマンドを実行すると、何も出力されません。

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.StandardOutput
$process.StandardError

しかし、出力をファイルにリダイレクトすると、期待どおりの結果が得られます。

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt

5
この特定のケースでは、本当にStart-processが必要ですか?... $process= ping localhost #プロセス変数に出力を保存します。
mjsr 2012年

1
そうだね。戻り値と引数を処理するためのよりクリーンな方法を探していました。私はあなたが示したようにスクリプトを書いてしまいました。
jzbruno

回答:


128

それStart-Processが何らかの理由で設計された方法です。ファイルに送信せずに取得する方法は次のとおりです。

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode

7
私はあなたの答えを受け入れています。使用されないプロパティを作成しないようにしてください。非常に混乱します。
jzbruno 2012年

6
あなたがプロセスをこのように実行している問題が発生している場合、ここで受け入れ答えを見stackoverflow.com/questions/11531068/... WaitForExitに若干の変更があり、StandardOutput.ReadToEndれ、
ラルフWillgoss

3
-verb runAsを使用する場合、-NoNewWindowまたはリダイレクトオプションは許可されません
Maverick

15
StdErrとStdOutの両方が最後まで同期して読み取られるため、このコードはいくつかの条件下でデッドロックします。msdn.microsoft.com/en-us/library/...
codepoke

8
@codepoke-それより少し悪い-最初にWaitForExit呼び出しを行うので、それらの1つだけをリダイレクトしても、ストリームバッファーがいっぱいになるとデッドロックする可能性があります(プロセスまでそれを読み取ろうとしないため)終了しました)
James Manning

20

質問で与えられたコードでは、開始変数のExitCodeプロパティの読み取りが機能するはずだと思います。

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait
$process.ExitCode

(例のように)-PassThru-Waitパラメータを追加する必要があることに注意してください(これはしばらくの間私を見つけました)。


argumentlistに変数が含まれている場合はどうなりますか?拡大していないようです。
OO

1
引数リストは引用符で囲みます。それはうまくいくでしょうか?... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Wait
JJones

powershellウィンドウに出力を表示し、ログファイルに記録する方法 出来ますか?
Murali Dhar Darshan

使用できない-NoNewWindow-Verb runAs
Dragas

11

私もこの問題を抱えており、Andyのコードを使用して、複数のコマンドを実行する必要がある場合にクリーンアップする関数を作成することになりました。

stderr、stdout、および終了コードをオブジェクトとして返します。注意すべき点の1つ:関数は.\パスで受け入れられません。フルパスを使用する必要があります。

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
        stderr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
    }
}

使用方法は次のとおりです。

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"

良い考えですが、構文がうまくいかないようです。パラメータリストでparam([type] $ ArgumentName)構文を使用しないでください。この関数の呼び出し例を追加できますか?
Lockszmith、2016年

「注意すべき1つ:関数はパスで。\を受け入れません。フルパスを使用する必要があります。」:次のように使用できます。> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz

9

重要:

上記のLPGで提供されている関数を使用しています。

ただし、これには、大量の出力を生成するプロセスを開始したときに発生する可能性があるバグが含まれています。このため、この関数を使用すると、デッドロックが発生する可能性があります。代わりに、以下の適合バージョンを使用してください:

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
  Try {
    $pinfo = New-Object System.Diagnostics.ProcessStartInfo
    $pinfo.FileName = $commandPath
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = $commandArguments
    $p = New-Object System.Diagnostics.Process
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    [pscustomobject]@{
        commandTitle = $commandTitle
        stdout = $p.StandardOutput.ReadToEnd()
        stderr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
    }
    $p.WaitForExit()
  }
  Catch {
     exit
  }
}

この問題の詳細については、MSDNを参照してください。

親プロセスがp.StandardError.ReadToEndの前にp.WaitForExitを呼び出し、子プロセスがリダイレクトされたストリームを満たすのに十分なテキストを書き込むと、デッドロック状態が発生する可能性があります。親プロセスは、子プロセスが終了するまで無期限に待機します。子プロセスは、親が完全なStandardErrorストリームから読み取るのを無期限に待機します。


3
このコードは、MSDNへのリンクでも説明されているReadToEnd()への同期呼び出しが原因で依然としてデッドロックしています。
bergmeister 2017年

1
これで私の問題は解決したようです。なぜハングしたのか完全には理解していませんが、空のstderrがプロセスの完了をブロックしているようです。不思議なことに、それは長期間動作していましたが、突然Xmasの直前で失敗し始め、多くのJavaプロセスがハングしました。
rhellem

8

Andy ArismendiLPGの例には本当に問題がありました。常に使用する必要があります:

$stdout = $p.StandardOutput.ReadToEnd()

呼び出す前に

$p.WaitForExit()

完全な例は次のとおりです。

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
$p.WaitForExit()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode

「常に使用する必要があります:$ p.WaitForExit()の前に$ p.StandardOutput.ReadToEnd()」をどこで読みましたか?バッファに出力があり、後でさらに出力が続く場合、実行の行がWaitForExitにあり、プロセスが終了していないと、出力が失われます(その後、より多くのstderrまたはstdoutが出力されます)。
CJBS 2017年

上記の私のコメントに関して、私は後で、大規模な出力の場合のデッドロックとバッファオーバーフローに関する受け入れられた回答のコメントを見ましたが、それはさておき、バッファが最後まで読み取られたからといって、プロセスを意味するわけではありませんが完了したため、見逃されている出力が増える可能性があります。何か不足していますか?
CJBS 2017年

@CJBS:「バッファが最後まで読み取られるからといって、プロセスが完了したことを意味するわけではありません」 -それはそれが意味することです。実際、それがデッドロックを引き起こす可能性がある理由です。「終わりまで」を読むことは、「今あるものをすべて読む」という意味ではありません。これは読み込みを開始することを意味し、ストリームが閉じられるまで停止しないでください。これはプロセスの終了と同じです。
Peter Duniho

0

これは、3つの新しいプロパティを持つ標準のSystem.Diagnostics.Processを返す関数の私のバージョンです。

Function Execute-Command ($commandTitle, $commandPath, $commandArguments)
{
    Try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $commandPath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.WindowStyle = 'Hidden'
        $pinfo.CreateNoWindow = $True
        $pinfo.Arguments = $commandArguments
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $stdout = $p.StandardOutput.ReadToEnd()
        $stderr = $p.StandardError.ReadToEnd()
        $p.WaitForExit()
        $p | Add-Member "commandTitle" $commandTitle
        $p | Add-Member "stdout" $stdout
        $p | Add-Member "stderr" $stderr
    }
    Catch {
    }
    $p
}

0

これは、別のpowershellプロセスから出力を取得するための扱いにくい方法です。

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.