Capture de la sortie standard et des erreurs avec Start-Process

112

Y a-t-il un bogue dans la Start-Processcommande de PowerShell lors de l'accès aux propriétés StandardErroret StandardOutput?

Si j'exécute ce qui suit, je n'obtiens aucune sortie:

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

Mais si je redirige la sortie vers un fichier, j'obtiens le résultat attendu:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
jzbruno
la source
5
Dans ce cas précis, avez-vous vraiment besoin de Start-process? ... $process= ping localhost # enregistrerait la sortie dans la variable de processus.
mjsr
1
Vrai. Je cherchais un moyen plus propre de gérer les retours et les arguments. J'ai fini par écrire le script comme vous l'avez montré.
jzbruno

Réponses:

128

Voilà comment a Start-Processété conçu pour une raison quelconque. Voici un moyen de l'obtenir sans envoyer au fichier:

$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
la source
7
J'accepte votre réponse. J'aurais aimé qu'ils n'aient pas créé des propriétés qui ne sont pas utilisées, c'est très déroutant.
jzbruno
6
Si vous rencontrez des difficultés pour exécuter un processus de cette façon, voir la réponse acceptée ici stackoverflow.com/questions/11531068/… , qui a une légère modification de WaitForExit et StandardOutput.ReadToEnd
Ralph Willgoss
3
Lorsque vous utilisez le -verb runAs, il n'autorise pas leh -NoNewWindow ou les options de redirection
Maverick
15
Ce code se bloquera dans certaines conditions en raison de la lecture synchrone de StdErr et StdOut jusqu'à la fin. msdn.microsoft.com/en-us/library/…
codepoke
8
@codepoke - c'est légèrement pire que cela - puisqu'il effectue d'abord l'appel WaitForExit, même s'il n'en redirige qu'un seul, il pourrait se bloquer si le tampon de flux se remplit (puisqu'il n'essaye pas de lire à partir de celui-ci avant le processus a quitté)
James Manning
20

Dans le code donné dans la question, je pense que la lecture de la propriété ExitCode de la variable d'initiation devrait fonctionner.

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

Notez que (comme dans votre exemple) vous devez ajouter les paramètres -PassThruet -Wait(cela m'a surpris pendant un moment).

JJones
la source
Que faire si la liste d'arguments contient une variable? Cela ne semble pas s'étendre.
OO le
1
vous mettriez la liste d'arguments entre guillemets. Cela fonctionnerait-il? ... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Wait
JJones
comment afficher la sortie dans la fenêtre PowerShell et la consigner dans un fichier journal? C'est possible?
Murali Dhar Darshan
Ne peut pas utiliser -NoNewWindowavec-Verb runAs
Dragas
11

J'ai également eu ce problème et j'ai fini par utiliser le code d' Andy pour créer une fonction pour nettoyer les choses lorsque plusieurs commandes doivent être exécutées.

Il renverra les codes stderr, stdout et exit en tant qu'objets. Une chose à noter: la fonction n'acceptera pas .\dans le chemin; les chemins complets doivent être utilisés.

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

Voici comment l'utiliser:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"
GPL
la source
Bonne idée, mais il semble que la syntaxe ne fonctionne pas pour moi. La liste de paramètres ne devrait-elle pas utiliser la syntaxe param ([type] $ ArgumentName)? pouvez-vous ajouter un exemple d'appel à cette fonction?
Lockszmith
Concernant "Une chose à noter: la fonction n'acceptera pas. \ Dans le chemin; les chemins complets doivent être utilisés.": Vous pouvez utiliser:> $ pinfo.FileName = Resolve-Path $
commandPath
9

IMPORTANT:

Nous avons utilisé la fonction fournie ci-dessus par LPG .

Cependant, cela contient un bogue que vous pourriez rencontrer lorsque vous démarrez un processus qui génère beaucoup de sortie. Pour cette raison, vous pourriez vous retrouver avec un blocage lors de l'utilisation de cette fonction. Utilisez plutôt la version adaptée ci-dessous:

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

Vous trouverez de plus amples informations sur ce problème sur MSDN :

Une condition de blocage peut se produire si le processus parent appelle p.WaitForExit avant p.StandardError.ReadToEnd et que le processus enfant écrit suffisamment de texte pour remplir le flux redirigé. Le processus parent attendrait indéfiniment la fin du processus enfant. Le processus enfant attendrait indéfiniment que le parent lise à partir du flux StandardError complet.

pserranne
la source
3
Ce code se bloque toujours en raison de l'appel synchrone à ReadToEnd (), que votre lien vers MSDN décrit également.
bergmeister
1
Cela semble maintenant avoir résolu mon problème. Je dois admettre que je ne comprends pas tout à fait pourquoi il s'est bloqué, mais il semble que stderr vide a bloqué le processus pour se terminer. Chose étrange, car cela a fonctionné pendant une longue période de temps, mais soudainement juste avant Noël, il a commencé à échouer, provoquant le blocage de nombreux processus Java.
rhellem
8

J'ai vraiment eu des problèmes avec ces exemples d'Andy Arismendi et de LPG . Vous devez toujours utiliser:

$stdout = $p.StandardOutput.ReadToEnd()

avant d'appeler

$p.WaitForExit()

Un exemple complet est:

$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
la source
Où avez-vous lu que "Vous devriez toujours utiliser: $ p.StandardOutput.ReadToEnd () avant $ p.WaitForExit ()"? S'il y a une sortie sur le tampon qui est épuisée, après plus de sortie à un moment ultérieur, cela sera manqué si la ligne d'exécution est sur WaitForExit et que le processus n'est pas terminé (et génère ensuite plus de stderr ou stdout) ....
CJBS
En ce qui concerne mon commentaire ci-dessus, j'ai vu plus tard les commentaires sur la réponse acceptée concernant le blocage et le débordement de tampon en cas de sortie importante, mais cela mis à part, je m'attendrais à ce que ce n'est pas parce que le tampon est lu jusqu'à la fin que cela signifie le processus est terminé, et il pourrait donc y avoir plus de sortie manquée. Est-ce que je manque quelque chose?
CJBS
@CJBS: "ce n'est pas parce que le tampon est lu jusqu'à la fin que le processus est terminé" - cela veut dire que. En fait, c'est pourquoi il peut se bloquer. Lire «jusqu'à la fin» ne veut pas dire «lire tout ce qui est là maintenant ». Cela signifie commencer la lecture et ne pas s'arrêter tant que le flux n'est pas fermé, ce qui équivaut à la fin du processus.
Peter Duniho
0

Voici ma version de la fonction qui renvoie System.Diagnostics.Process standard avec 3 nouvelles propriétés

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
la source
0

Voici un moyen simple d'obtenir la sortie d'un autre processus PowerShell:

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