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

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

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).


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"

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.


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

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
}

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.