Acquisizione di standard out ed errori con Start-Process


112

C'è un bug nel Start-Processcomando di PowerShell quando si accede alle proprietà StandardErrore StandardOutput?

Se eseguo quanto segue non ottengo alcun output:

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

Ma se reindirizzo l'output a un file ottengo il risultato atteso:

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

5
In questo caso specifico hai davvero bisogno di Start-process? ... $process= ping localhost # salverebbe l'output nella variabile di processo.
mjsr

1
Vero. Stavo cercando un modo più pulito per gestire il ritorno e gli argomenti. Ho finito per scrivere la sceneggiatura come mi hai mostrato.
jzbruno

Risposte:


128

Ecco come è Start-Processstato progettato per qualche motivo. Ecco un modo per ottenerlo senza inviare a file:

$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
Accetto la tua risposta. Vorrei che non avessero creato proprietà che non vengono utilizzate, è molto confuso.
jzbruno

6
Se hai problemi a eseguire un processo in questo modo, vedi la risposta accettata qui stackoverflow.com/questions/11531068/… , che ha una leggera modifica a WaitForExit e StandardOutput.ReadToEnd
Ralph Willgoss

3
Quando si utilizza il -verb runAs non consente il -NoNewWindow o le Opzioni di reindirizzamento
Maverick

15
Questo codice si bloccherà in alcune condizioni a causa della lettura sincrona fino alla fine di StdErr e StdOut. msdn.microsoft.com/en-us/library/...
codepoke

8
@codepoke - è leggermente peggio di così - poiché esegue prima la chiamata WaitForExit, anche se ne ha reindirizzato solo uno, potrebbe bloccarsi se il buffer del flusso viene riempito (poiché non tenta di leggere da esso fino al processo è uscito)
James Manning

20

Nel codice fornito nella domanda, penso che la lettura della proprietà ExitCode della variabile di avvio dovrebbe funzionare.

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

Nota che (come nel tuo esempio) devi aggiungere i parametri -PassThrue -Wait(questo mi ha colto di sorpresa per un po ').


E se la lista degli argomenti contiene una variabile? Non sembra espandersi.
OO

1
metti l'elenco degli argomenti tra virgolette. Funzionerebbe? ... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru
-Wait

come mostrare l'output nella finestra di PowerShell e registrarlo in un file di registro? È possibile?
Murali Dhar Darshan

Non può essere utilizzato -NoNewWindowcon-Verb runAs
Dragas il

11

Ho anche avuto questo problema e ho finito per utilizzare il codice di Andy per creare una funzione per ripulire le cose quando è necessario eseguire più comandi.

Restituirà stderr, stdout e codici di uscita come oggetti. Una cosa da notare: la funzione non accetterà .\nel percorso; devono essere utilizzati percorsi completi.

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

Ecco come usarlo:

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

Buona idea, ma sembra che la sintassi non funzioni per me. L'elenco dei parametri non dovrebbe utilizzare la sintassi param ([type] $ ArgumentName)? puoi aggiungere una chiamata di esempio a questa funzione?
Lockszmith

Per quanto riguarda "Una cosa da notare: la funzione non accetta. \ Nel percorso; devono essere utilizzati percorsi completi.": Puoi usare:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz

9

IMPORTANTE:

Abbiamo utilizzato la funzione fornita sopra da LPG .

Tuttavia, questo contiene un bug che potresti incontrare quando avvii un processo che genera molto output. A causa di ciò potresti finire con un deadlock quando usi questa funzione. Utilizza invece la versione adattata di seguito:

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

Ulteriori informazioni su questo problema sono disponibili su MSDN :

Una condizione di deadlock può verificarsi se il processo padre chiama p.WaitForExit prima di p.StandardError.ReadToEnd e il processo figlio scrive testo sufficiente per riempire il flusso reindirizzato. Il processo genitore aspetterà indefinitamente la fine del processo figlio. Il processo figlio aspetterà indefinitamente che il genitore legga dal flusso StandardError completo.


3
Questo codice si blocca ancora a causa della chiamata sincrona a ReadToEnd (), descritta anche dal collegamento a MSDN.
bergmeister

1
Questo ora sembra aver risolto il mio problema. Devo ammettere che non capisco appieno perché si è bloccato, ma sembra che lo stderr vuoto abbia bloccato il processo per terminare. Cosa strana, dal momento che ha funzionato per un lungo periodo di tempo, ma improvvisamente subito prima di Natale ha iniziato a fallire, causando il blocco di molti processi Java.
rhellem

8

Ho davvero avuto problemi con quegli esempi di Andy Arismendi e di LPG . Dovresti sempre usare:

$stdout = $p.StandardOutput.ReadToEnd()

prima di chiamare

$p.WaitForExit()

Un esempio completo è:

$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

Dove hai letto che "Dovresti sempre usare: $ p.StandardOutput.ReadToEnd () prima di $ p.WaitForExit ()"? Se c'è un output sul buffer che è esaurito, a seguito di più output in un secondo momento, ciò verrà perso se la linea di esecuzione è su WaitForExit e il processo non è terminato (e successivamente restituisce più stderr o stdout) ....
CJBS

Per quanto riguarda il mio commento sopra, in seguito ho visto i commenti sulla risposta accettata riguardo al deadlock e al buffer overflow in casi di grande output, ma a parte questo, mi aspetterei che solo perché il buffer viene letto fino alla fine, non significa il processo è stato completato e potrebbe esserci più output che manca. Mi sto perdendo qualcosa?
CJBS

@CJBS: "solo perché il buffer viene letto fino alla fine, non significa che il processo sia stato completato" - lo significa. In effetti, è per questo che può bloccarsi. Leggere "fino alla fine" non significa "leggere quello che c'è adesso ". Significa iniziare a leggere e non fermarsi fino a quando il flusso non è chiuso, che è lo stesso del processo che termina.
Peter Duniho

0

Ecco la mia versione della funzione che restituisce System.Diagnostics.Process standard con 3 nuove proprietà

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

Ecco un modo kludgy per ottenere l'output da un altro processo PowerShell:

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.