157 votes

Capture de la sortie standard et de l'erreur avec Start-Process

Y a-t-il un bug dans l'outil PowerShell Start-Process lors de l'accès à la StandardError et StandardOutput des propriétés ?

Si j'exécute la commande suivante, je n'obtiens aucun résultat :

$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

7 votes

Dans ce cas précis, avez-vous vraiment besoin de Start-process ?... $process= ping localhost # enregistrerait la sortie dans la variable de processus.

1 votes

C'est 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é.

0 votes

@mjsr Un moyen d'obtenir la sortie et le ExitCode en le faisant sans Start-process ? J'ai besoin de savoir si la commande a réussi, mais ce serait bien de passer par la sortie pour le message d'erreur.

164voto

Andy Arismendi Points 16501

C'est ainsi que Start-Process a été conçu pour une raison quelconque. Voici un moyen de l'obtenir sans l'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

7 votes

J'accepte votre réponse. J'aurais aimé qu'ils ne créent pas de propriétés qui ne sont pas utilisées, c'est très confus.

1 votes

@jzbruno L'équipe PowerShell n'a pas créé les propriétés StandardOutput/StandardError... Elles font partie du système sous-jacent System.Diagnostics.Process objet . Cependant, ils ne sont disponibles que lorsque l UseShellExecute a la valeur false. Cela dépend donc de la manière dont l'équipe PowerShell a implémenté Start-Process dans les coulisses... Malheureusement je ne peux pas regarder le code source :-(

7 votes

Si vous avez des difficultés à exécuter un processus de cette manière, voyez la réponse acceptée ici stackoverflow.com/questions/11531068/ qui modifie légèrement les fonctions WaitForExit et StandardOutput.ReadToEnd.

32voto

JJones Points 570

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

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

Notez que (comme dans votre exemple) vous devez ajouter l'élément -PassThru et -Wait paramètres (cela m'a surpris pendant un moment).

0 votes

Que faire si argumentlist contient une variable ? Il ne semble pas s'étendre.

2 votes

Vous devez mettre la liste des arguments entre guillemets. Cela fonctionnerait-il ? ... $process = Start-Process -FilePath ping -ArgumentList " -t localhost -n 1" -NoNewWindow -PassThru -Wait

0 votes

Comment afficher la sortie dans la fenêtre powershell ainsi que l'enregistrer dans un fichier journal ? Est-ce possible ?

18voto

pserranne Points 149

IMPORTANT :

Nous avons utilisé la fonction comme indiqué ci-dessus par GPL .

Cependant, cela contient un bug que vous pouvez rencontrer lorsque vous démarrez un processus qui génère beaucoup de sortie. De ce fait, vous risquez de vous retrouver dans une impasse en utilisant 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
  }
}

De plus amples informations sur cette question sont disponibles à MSDN :

Une situation 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 que le processus enfant se termine. Le processus enfant attendrait indéfiniment que le parent lise le flux complet de StandardError.

7 votes

Ce code se bloque toujours à cause de l'appel synchrone à ReadToEnd(), que votre lien vers MSDN décrit également.

1 votes

Cela semble maintenant avoir résolu mon problème. Je dois admettre que je ne comprends pas bien pourquoi il s'est bloqué, mais il semble que le stderr vide ait empêché le processus de se terminer. C'est étrange, car cela a fonctionné pendant une longue période, mais soudainement, juste avant Noël, il a commencé à échouer, provoquant le blocage de nombreux processus Java.

15voto

LPG Points 154

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

Il retournera stderr, stdout, et les codes de sortie comme 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"

0 votes

Bonne idée, mais il semble que la syntaxe ne fonctionne pas pour moi. La liste des paramètres ne devrait-elle pas utiliser la syntaxe param( [type]$ArgumentName ) ? Pouvez-vous ajouter un exemple d'appel à cette fonction ?

1 votes

Concernant "Une chose à noter : la fonction n'accepte pas les .\ dans le chemin ; les chemins complets doivent être utilisés" : Vous pourriez utiliser : > $pinfo.FileName = Resolve-Path $commandPath

12voto

Rainer Points 685

J'ai vraiment eu des problèmes avec ces exemples d'Andy Arismendi et à partir de GPL . 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

0 votes

Où avez-vous lu que "Vous devez toujours utiliser : $p.StandardOutput.ReadToEnd() avant $p.WaitForExit()" ? S'il y a une sortie sur le tampon qui est épuisée, suite à une sortie ultérieure, elle sera manquée si la ligne d'exécution est sur WaitForExit et que le processus ne s'est pas terminé (et sort par la suite plus de stderr ou stdout).....

0 votes

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 la mémoire tampon en cas de sortie importante, mais cela mis à part, je m'attendrais à ce que le fait que la mémoire tampon soit lue jusqu'à la fin ne signifie pas que le processus est terminé, et il pourrait donc y avoir plus de sortie qui est manquée. Est-ce que quelque chose m'échappe ?

2 votes

@CJBS : "ce n'est pas parce que le tampon est lu jusqu'au bout que le processus est terminé" -- ça veut dire ça. En fait, c'est pourquoi il peut y avoir des blocages. Lire "jusqu'au bout" ne veut pas dire "lire tout ce qui est là". maintenant ". Cela signifie qu'il faut commencer à lire, et ne pas s'arrêter jusqu'à ce que le flux soit fermé, ce qui équivaut à la fin du processus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X