Standardausgabe und Fehler mit Start-Process erfassen

111

Gibt es einen Fehler im PowerShell- Start-ProcessBefehl beim Zugriff auf die Eigenschaften StandardErrorund StandardOutput?

Wenn ich Folgendes ausführe, erhalte ich keine Ausgabe:

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

Wenn ich die Ausgabe jedoch in eine Datei umleitung, erhalte ich das erwartete Ergebnis:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
jzbruno
quelle
5
In diesem speziellen Fall benötigen Sie wirklich Start-Prozess? ... $process= ping localhost # würde die Ausgabe in der Prozessvariablen speichern.
mjsr
1
Wahr. Ich suchte nach einem saubereren Weg, um mit Rückgaben und Argumenten umzugehen. Am Ende habe ich das Drehbuch so geschrieben, wie Sie es gezeigt haben.
Jzbruno

Antworten:

127

So Start-Processwurde es aus irgendeinem Grund entworfen. Hier ist eine Möglichkeit, es zu erhalten, ohne es in eine Datei zu senden:

$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
Andy Arismendi
quelle
7
Ich akzeptiere deine Antwort. Ich wünschte, sie hätten keine Eigenschaften erstellt, die nicht verwendet werden, das ist sehr verwirrend.
Jzbruno
6
Wenn Sie Probleme haben, einen Prozess auf diese Weise auszuführen , lesen Sie die akzeptierte Antwort hier stackoverflow.com/questions/11531068/… , die eine geringfügige Änderung an WaitForExit und StandardOutput.ReadToEnd
Ralph Willgoss
3
Wenn Sie das -verb runAs verwenden, sind weder das -NoNewWindow noch die Umleitungsoptionen zulässig
Maverick
15
Dieser Code blockiert unter bestimmten Bedingungen, da sowohl StdErr als auch StdOut bis zum Ende synchron gelesen werden. msdn.microsoft.com/en-us/library/…
Codepoke
8
@codepoke - es ist etwas schlimmer als das - da der WaitForExit-Aufruf zuerst ausgeführt wird, selbst wenn nur einer von ihnen umgeleitet wird, kann es zu einem Deadlock kommen, wenn der Stream-Puffer voll wird (da erst nach dem Prozess versucht wird, daraus zu lesen hat beendet)
James Manning
20

In dem in der Frage angegebenen Code denke ich, dass das Lesen der ExitCode-Eigenschaft der Initiationsvariablen funktionieren sollte.

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

Beachten Sie, dass Sie (wie in Ihrem Beispiel) die Parameter -PassThruund hinzufügen müssen -Wait(dies hat mich eine Weile überrascht).

JJones
quelle
Was ist, wenn die Argumentliste eine Variable enthält? Es scheint sich nicht auszudehnen.
OO
1
Sie würden die Argumentliste in Anführungszeichen setzen. Funktioniert das ? ... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Wait
JJones
Wie kann ich die Ausgabe im Powershell-Fenster anzeigen und in einer Protokolldatei protokollieren? Ist es möglich?
Murali Dhar Darshan
Kann nicht -NoNewWindowmit-Verb runAs
Dragas
11

Ich hatte auch dieses Problem und verwendete schließlich Andys Code , um eine Funktion zum Aufräumen zu erstellen, wenn mehrere Befehle ausgeführt werden müssen.

Es werden stderr-, stdout- und exit-Codes als Objekte zurückgegeben. Eines ist zu beachten: Die Funktion wird .\im Pfad nicht akzeptiert . Es müssen vollständige Pfade verwendet werden.

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
    }
}

So verwenden Sie es:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"
LPG
quelle
Gute Idee, aber anscheinend funktioniert die Syntax bei mir nicht. Sollte die Parameterliste nicht die Syntax param ([type] $ ArgumentName) verwenden? Können Sie dieser Funktion einen Beispielaufruf hinzufügen?
Schlosser
Zu "Eines ist zu beachten: Die Funktion akzeptiert nicht. \ Im Pfad; vollständige Pfade müssen verwendet werden.": Sie könnten Folgendes verwenden:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz
8

Ich hatte wirklich Probleme mit diesen Beispielen von Andy Arismendi und von LPG . Sie sollten immer verwenden:

$stdout = $p.StandardOutput.ReadToEnd()

vor dem Anruf

$p.WaitForExit()

Ein vollständiges Beispiel ist:

$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
Rainer
quelle
Wo haben Sie gelesen, dass "Sie immer $ p.StandardOutput.ReadToEnd () vor $ p.WaitForExit () verwenden sollten"? Wenn der Puffer erschöpft ist und zu einem späteren Zeitpunkt mehr ausgegeben wird, wird dies übersehen, wenn sich die Ausführungszeile auf WaitForExit befindet und der Prozess noch nicht abgeschlossen ist (und anschließend mehr stderr oder stdout ausgibt) ....
CJBS
In Bezug auf meinen obigen Kommentar habe ich später die Kommentare zu der akzeptierten Antwort bezüglich Deadlocking und Pufferüberlauf bei großer Ausgabe gesehen, aber abgesehen davon würde ich erwarten, dass nur weil der Puffer bis zum Ende gelesen wird, dies nicht den Prozess bedeutet abgeschlossen ist, und es könnte somit mehr Ausgabe fehlen. Vermisse ich etwas
CJBS
@CJBS: "Nur weil der Puffer bis zum Ende gelesen wird, heißt das nicht, dass der Prozess abgeschlossen ist" - das bedeutet es. In der Tat kann es deshalb zum Stillstand kommen. "Bis zum Ende" zu lesen bedeutet nicht "lesen, was jetzt da ist ". Dies bedeutet, dass Sie mit dem Lesen beginnen und nicht aufhören müssen, bis der Stream geschlossen ist. Dies entspricht dem Beenden des Prozesses.
Peter Duniho
8

WICHTIG:

Wir haben die oben von LPG bereitgestellte Funktion verwendet .

Dies enthält jedoch einen Fehler, der auftreten kann, wenn Sie einen Prozess starten, der viel Ausgabe generiert. Aus diesem Grund kann es bei Verwendung dieser Funktion zu einem Deadlock kommen. Verwenden Sie stattdessen die unten angepasste Version:

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
  }
}

Weitere Informationen zu diesem Thema finden Sie unter MSDN :

Eine Deadlock-Bedingung kann auftreten, wenn der übergeordnete Prozess p.WaitForExit vor p.StandardError.ReadToEnd aufruft und der untergeordnete Prozess genügend Text schreibt, um den umgeleiteten Stream zu füllen. Der übergeordnete Prozess würde unbegrenzt warten, bis der untergeordnete Prozess beendet wird. Der untergeordnete Prozess würde unbegrenzt darauf warten, dass der übergeordnete Prozess aus dem vollständigen StandardError-Stream liest.

Pserranne
quelle
3
Dieser Code blockiert immer noch aufgrund des synchronen Aufrufs von ReadToEnd (), den auch Ihr Link zu MSDN beschreibt.
Bergmeister
1
Dies scheint nun mein Problem gelöst zu haben. Ich muss zugeben, dass ich nicht ganz verstehe, warum es hängen geblieben ist, aber es scheint, dass leere stderr den Prozess blockiert haben, um zu beenden. Seltsame Sache, da es lange Zeit funktionierte, aber plötzlich kurz vor Weihnachten begann es zu scheitern, was dazu führte, dass viele Java-Prozesse hängen blieben.
Rhellem
0

Hier ist meine Version der Funktion, die Standard System.Diagnostics.Process mit 3 neuen Eigenschaften zurückgibt

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
}
Anabela Mazurek
quelle
0

Hier ist eine klobige Möglichkeit, die Ausgabe eines anderen Powershell-Prozesses zu erhalten:

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
js2010
quelle