web-dev-qa-db-fra.com

Capture de sortie standard et d'erreur avec Start-Process

Existe-t-il un bug dans la commande Start-Process de PowerShell lors de l'accès aux propriétés StandardError et StandardOutput?

Si je lance 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
83
jzbruno

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

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 -PassThru et -Wait (cela m’a pris au dépourvu).

14
JJones

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

Il retournera stderr, stdout et les codes de sortie sous forme d'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"
9
LPG

J'ai vraiment eu des problèmes avec ces exemples d'Andy Arismendi et de LPG . Vous devriez 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
6
Rainer

IMPORTANT:

Nous utilisons la fonction décrite ci-dessus par LPG .

Cependant, cela contient un bogue que vous pourriez rencontrer lorsque vous démarrez un processus générant beaucoup de sortie. Pour cette raison, vous pourriez vous retrouver avec une impasse lorsque vous utilisez 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 ce problème sont disponibles sur le site at MSDN

Une situation de blocage peut entraîner 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 à partir du flux StandardError complet.

4
pserranne

Voici ma version de la fonction qui renvoie standard System.Diagnostics.Process 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
Anabela Mazurek

Voici un moyen simple d'obtenir le résultat d'un autre processus powershell:

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