Captura de error y salida estándar con Start-Process

112

¿Hay un error en el Start-Processcomando de PowerShell al acceder a StandardErroryStandardOutput propiedades ?

Si ejecuto lo siguiente, no obtengo ningún resultado:

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

Pero si redirijo la salida a un archivo, obtengo el resultado esperado:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
jzbruno
fuente
5
En este caso específico, ¿realmente necesita Start-process? ... $process= ping localhost # guardaría la salida en la variable de proceso.
mjsr
1
Cierto. Estaba buscando una forma más limpia de manejar devoluciones y argumentos. Terminé escribiendo el guión como lo mostraste.
jzbruno

Respuestas:

128

Así Start-Processfue como fue diseñado por alguna razón. Aquí hay una forma de obtenerlo sin enviarlo al archivo:

$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
fuente
7
Acepto tu respuesta. Ojalá no hubieran creado propiedades que no se usan, es muy confuso.
jzbruno
6
Si tiene problemas para ejecutar un proceso de esta manera, consulte la respuesta aceptada aquí stackoverflow.com/questions/11531068/… , que tiene una ligera modificación en WaitForSalir y StandardOutput
Lee
3
Cuando usa -verb runAs, no permite el -NoNewWindow o las opciones de redirección
Maverick
15
Este código se bloqueará en algunas condiciones debido a que tanto StdErr como StdOut se leen sincrónicamente hasta el final. msdn.microsoft.com/en-us/library/…
codepoke
8
@codepoke, es un poco peor que eso, ya que primero hace la llamada WaitForSalir, incluso si solo redirigió a uno de ellos, podría bloquearse si el búfer de transmisión se llena (ya que no intenta leer de él hasta el proceso ha salido)
James Manning
20

En el código dado en la pregunta, creo que leer la propiedad ExitCode de la variable de inicio debería funcionar.

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

Tenga en cuenta que (como en su ejemplo) debe agregar los parámetros -PassThruy -Wait(esto me sorprendió por un tiempo).

JJones
fuente
¿Qué pasa si la lista de argumentos contiene una variable? No parece expandirse.
OO
1
pondría la lista de argumentos entre comillas. Funcionaría eso ? ... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Wait
JJones
¿Cómo mostrar la salida en la ventana de PowerShell y registrarla en un archivo de registro? ¿Es posible?
Murali Dhar Darshan
No se puede utilizar -NoNewWindowcon-Verb runAs
Dragas
11

También tuve este problema y terminé usando el código de Andy para crear una función para limpiar las cosas cuando se deben ejecutar varios comandos.

Devolverá stderr, stdout y códigos de salida como objetos. Una cosa a tener en cuenta: la función no aceptará .\en la ruta; Deben utilizarse rutas completas.

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

A continuación, le indicamos cómo utilizarlo:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"
GLP
fuente
Buena idea, pero parece que la sintaxis no me funciona. ¿No debería la lista de parámetros utilizar la sintaxis param ([tipo] $ ArgumentName)? ¿Puede agregar una llamada de ejemplo a esta función?
Cerrajero
Con respecto a "Una cosa a tener en cuenta: la función no acepta. \ En la ruta; se deben usar rutas completas.": Puede usar:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz
9

IMPORTANTE:

Hemos estado utilizando la función proporcionada anteriormente por LPG .

Sin embargo, esto contiene un error que puede encontrar cuando inicia un proceso que genera una gran cantidad de resultados. Debido a esto, puede terminar con un punto muerto al usar esta función. En su lugar, utilice la versión adaptada a continuación:

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

Puede encontrar más información sobre este problema en MSDN :

Puede producirse una condición de interbloqueo si el proceso padre llama a p.WaitForSalir antes de p.StandardError.ReadToEnd y el proceso hijo escribe suficiente texto para completar la secuencia redirigida. El proceso padre esperaría indefinidamente a que salga el proceso hijo. El proceso hijo esperaría indefinidamente a que el padre leyera el flujo de StandardError completo.

pserranne
fuente
3
Este código todavía se bloquea debido a la llamada sincrónica a ReadToEnd (), que también describe su enlace a MSDN.
Bergmeister
1
Esto ahora parece haber resuelto mi problema. Debo admitir que no entiendo completamente por qué se bloqueó, pero parece que stderr vacío bloqueó el proceso para finalizar. Algo extraño, ya que funcionó durante un largo período de tiempo, pero de repente, justo antes de Navidad, comenzó a fallar, lo que provocó que muchos procesos de Java se bloquearan.
rhellem
8

Realmente tuve problemas con esos ejemplos de Andy Arismendi y de LPG . Siempre debes usar:

$stdout = $p.StandardOutput.ReadToEnd()

antes de llamar

$p.WaitForExit()

Un ejemplo completo es:

$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
fuente
¿Dónde leíste que "Siempre debes usar: $ p.StandardOutput.ReadToEnd () antes de $ p.WaitForSalir ()"? Si hay salida en el búfer que se agota, después de más salida en un momento posterior, se perderá si la línea de ejecución está en WaitForSalir y el proceso no ha terminado (y posteriormente genera más stderr o stdout) ....
CJBS
Con respecto a mi comentario anterior, más tarde vi los comentarios sobre la respuesta aceptada con respecto al interbloqueo y el desbordamiento del búfer en casos de salida grande, pero aparte de eso, esperaría que solo porque el búfer se lea hasta el final, no significa el proceso se ha completado y, por lo tanto, podría faltar más salida. ¿Me estoy perdiendo de algo?
CJBS
@CJBS: "solo porque el búfer se lea hasta el final, no significa que el proceso se haya completado" , sí lo significa. De hecho, es por eso que puede estancarse. Leer "hasta el final" no significa "leer lo que hay ahora ". Significa comenzar a leer y no detenerse hasta que se cierre la transmisión, que es lo mismo que el proceso que termina.
Peter Duniho
0

Aquí está mi versión de la función que está devolviendo System.Diagnostics.Process estándar con 3 propiedades nuevas

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
fuente
0

Aquí hay una forma ingeniosa de obtener el resultado de otro proceso de PowerShell:

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