139

およびプロパティ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
4

10 に答える 10

156

それ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
于 2012-01-06T17:43:23.970 に答える
28

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

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

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

于 2014-12-11T10:19:52.303 に答える
17

重要:

上記の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ストリームから読み取るのを無期限に待機します。

于 2017-03-24T09:05:09.530 に答える
15

私もこの問題を抱えていて、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"
于 2015-11-11T14:32:10.150 に答える
11

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

$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
于 2016-06-20T15:34:31.863 に答える
2

別のPowerShellプロセスから出力を取得するための厄介な方法は次のとおりです。

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
于 2019-07-11T13:55:35.483 に答える
1

stdoutとstderrの両方を取得するには、次を使用します。

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 = "cmd"
$arguments = '/c echo hello 1>&2'   #this writes 'hello' to stderr

$runResult = (GetProgramOutput $exe $arguments)
$stdout = $runResult[-2]
$stderr = $runResult[-1]

[System.Console]::WriteLine("Standard out: " + $stdout)
[System.Console]::WriteLine("Standard error: " + $stderr)
于 2020-11-26T11:33:08.177 に答える
1

これは、このスレッドで他の人が投稿した例に基づいて作成したものです。このバージョンでは、コンソールウィンドウが非表示になり、出力表示のオプションが提供されます。

function Invoke-Process {
    [CmdletBinding(SupportsShouldProcess)]
    param
        (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ArgumentList,

        [ValidateSet("Full","StdOut","StdErr","ExitCode","None")]
        [string]$DisplayLevel
        )

    $ErrorActionPreference = 'Stop'

    try {
        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $FilePath
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.UseShellExecute = $false
        $pinfo.WindowStyle = 'Hidden'
        $pinfo.CreateNoWindow = $true
        $pinfo.Arguments = $ArgumentList
        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $result = [pscustomobject]@{
        Title = ($MyInvocation.MyCommand).Name
        Command = $FilePath
        Arguments = $ArgumentList
        StdOut = $p.StandardOutput.ReadToEnd()
        StdErr = $p.StandardError.ReadToEnd()
        ExitCode = $p.ExitCode
        }
        $p.WaitForExit()

        if (-not([string]::IsNullOrEmpty($DisplayLevel))) {
            switch($DisplayLevel) {
                "Full" { return $result; break }
                "StdOut" { return $result.StdOut; break }
                "StdErr" { return $result.StdErr; break }
                "ExitCode" { return $result.ExitCode; break }
                }
            }
        }
    catch {
        exit
        }
}

例:Invoke-Process -FilePath "FQPN" -ArgumentList "ARGS" -DisplayLevel Full

于 2021-03-18T23:35:50.723 に答える
1

タイムアウトがある場合ReadToEnd()、オプションではありません。いくつかの凝ったループを行うこともできますが、IMOがこれを行うための「最もクリーンな」方法は、ストリームを無視することです。OutputDataReceived代わりに/ErrorDataReceivedイベントをフックして、出力を収集します。このアプローチは、他の人が言及したスレッドの問題も回避します。

これはC#では簡単ですが、Powershellではトリッキーで冗長です。特に、add_OutputDataReceived何らかの理由でご利用いただけません。(これがバグなのか機能なのかはわかりませんが、少なくともPowerShell 5.1の場合はそうです。)これを回避するには、を使用できますRegister-ObjectEvent

$stdout = New-Object System.Text.StringBuilder
$stderr = New-Object System.Text.StringBuilder

$proc = [System.Diagnostics.Process]@{
  StartInfo = @{
    FileName = 'ping.exe'
    Arguments = 'google.com'
    RedirectStandardOutput = $true
    RedirectStandardError = $true
    UseShellExecute = $false
    WorkingDirectory = $PSScriptRoot
  }
}

$stdoutEvent = Register-ObjectEvent $proc -EventName OutputDataReceived -MessageData $stdout -Action {
  $Event.MessageData.AppendLine($Event.SourceEventArgs.Data)
}

$stderrEvent = Register-ObjectEvent $proc -EventName ErrorDataReceived -MessageData $stderr -Action {
  $Event.MessageData.AppendLine($Event.SourceEventArgs.Data)
}

$proc.Start() | Out-Null
$proc.BeginOutputReadLine()
$proc.BeginErrorReadLine()
Wait-Process -Id $proc.Id -TimeoutSec 5

if ($proc.HasExited) {
  $exitCode = $proc.ExitCode
}
else {
  Stop-Process -Force -Id $proc.Id
  $exitCode = -1
}

# Be sure to unregister.  You have been warned.
Unregister-Event $stdoutEvent.Id
Unregister-Event $stderrEvent.Id
Write-Output $stdout.ToString()
Write-Output $stderr.ToString()
Write-Output "Exit code: $exitCode"
  • 示されているコードはハッピーパスです(stderrは空です)
  • タイムアウトパスをテストするには-TimeoutSec.5
  • 悲しい道をテストするには(stderrにコンテンツがありますFileName'cmd'Arguments/C asdf
于 2021-08-26T17:26:28.867 に答える
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
}
于 2018-08-01T10:07:27.130 に答える