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