Capturando saída padrão e erro com Start-Process

112

Existe um bug no Start-Processcomando do PowerShell ao acessar as propriedades StandardErrore StandardOutput?

Se eu executar o seguinte, não obtenho saída:

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

Mas se eu redirecionar a saída para um arquivo, obtenho o resultado esperado:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
jzbruno
fonte
5
Neste caso específico, você realmente precisa de Start-process? ... $process= ping localhost # salvaria a saída na variável de processo.
mjsr
1
Verdade. Eu estava procurando uma maneira mais limpa de lidar com retornos e discussões. Acabei escrevendo o roteiro como você mostrou.
jzbruno

Respostas:

128

É assim que Start-Processfoi projetado por algum motivo. Esta é uma maneira de obtê-lo sem enviar para o arquivo:

$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
fonte
7
Estou aceitando sua resposta. Eu gostaria que eles não tivessem criado propriedades que não fossem usadas, é muito confuso.
jzbruno
6
Se você tiver problemas para executar um processo dessa forma, consulte a resposta aceita aqui stackoverflow.com/questions/11531068/… , que tem uma pequena modificação em WaitForExit e StandardOutput.ReadToEnd
Ralph Willgoss
3
Quando você usa o -verb runAs, ele não permite oh -NoNewWindow ou as opções de redirecionamento
Maverick
15
Este código entrará em conflito sob algumas condições devido a StdErr e StdOut serem lidos de forma síncrona até o fim. msdn.microsoft.com/en-us/library/…
codepoke
8
@codepoke - é um pouco pior do que isso - uma vez que faz a chamada WaitForExit primeiro, mesmo que apenas redirecione um deles, pode causar um deadlock se o buffer de fluxo ficar cheio (uma vez que não tenta ler dele até o processo saiu)
James Manning
20

No código fornecido na pergunta, acho que a leitura da propriedade ExitCode da variável de inicialização deve funcionar.

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

Observe que (como no seu exemplo) você precisa adicionar os parâmetros -PassThrue -Wait(isso me surpreendeu por um tempo).

JJones
fonte
E se argumentlist contiver uma variável? Não parece se expandir.
OO
1
você colocaria a lista de argumentos entre aspas. Isso funcionaria ? ... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Wait
JJones
como mostrar a saída na janela do PowerShell, bem como registrá-la em um arquivo de log? É possível?
Murali Dhar Darshan
Não é possível usar -NoNewWindowcom-Verb runAs
Dragas
11

Eu também tive esse problema e acabei usando o código de Andy para criar uma função para limpar as coisas quando vários comandos precisam ser executados.

Ele retornará stderr, stdout e códigos de saída como objetos. Uma coisa a se notar: a função não aceita .\no caminho; caminhos completos devem ser usados.

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

Veja como usá-lo:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"
GLP
fonte
Boa ideia, mas parece que a sintaxe não está funcionando para mim. A lista de parâmetros não deveria usar a sintaxe param ([type] $ ArgumentName)? você pode adicionar um exemplo de chamada para esta função?
Serralheiro
Sobre "Uma coisa a observar: a função não aceita. \ No caminho; caminhos completos devem ser usados.": Você pode usar:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz
9

IMPORTANTE:

Temos usado a função fornecida acima pelo LPG .

No entanto, isso contém um bug que você pode encontrar ao iniciar um processo que gera muita saída. Devido a isso, você pode acabar com um deadlock ao usar esta função. Em vez disso, use a versão adaptada abaixo:

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

Mais informações sobre esse problema podem ser encontradas no MSDN :

Uma condição de deadlock pode ocorrer se o processo pai chamar p.WaitForExit antes de p.StandardError.ReadToEnd e o processo filho gravar texto suficiente para preencher o fluxo redirecionado. O processo pai esperaria indefinidamente pela saída do processo filho. O processo filho esperaria indefinidamente pelo pai ler o fluxo StandardError completo.

pserranne
fonte
3
Este código ainda bloqueia devido à chamada síncrona para ReadToEnd (), que seu link para o MSDN também descreve.
Bergmeister
1
Isso agora parece ter resolvido meu problema. Devo admitir que não entendo completamente por que ele travou, mas parece que o stderr vazio bloqueou o processo para terminar. Coisa estranha, já que funcionou por um longo período de tempo, mas de repente, pouco antes do Natal, começou a falhar, fazendo com que muitos processos Java travassem.
Rhellem
8

Eu realmente tive problemas com esses exemplos de Andy Arismendi e do LPG . Você deve sempre usar:

$stdout = $p.StandardOutput.ReadToEnd()

antes de ligar

$p.WaitForExit()

Um exemplo completo é:

$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
fonte
Onde você leu que "Você deve sempre usar: $ p.StandardOutput.ReadToEnd () antes de $ p.WaitForExit ()"? Se houver saída no buffer que está esgotada, seguindo mais saída em um momento posterior, isso será perdido se a linha de execução estiver em WaitForExit e o processo não tiver terminado (e subsequentemente produzir mais stderr ou stdout) ....
CJBS
Em relação ao meu comentário acima, vi mais tarde os comentários sobre a resposta aceita sobre deadlock e estouro de buffer em casos de saída grande, mas fora isso, eu esperaria que só porque o buffer é lido até o fim, isso não significa que o processo foi concluído e, portanto, pode haver mais saída perdida. Estou esquecendo de algo?
CJBS
@CJBS: "só porque o buffer é lido até o fim, não significa que o processo foi concluído" - significa isso. Na verdade, é por isso que ele pode travar. Ler "até o fim" não significa "ler o que está lá agora ". Significa começar a ler e não parar até que o fluxo seja fechado, o que é o mesmo que o término do processo.
Peter Duniho
0

Aqui está minha versão de função que está retornando System.Diagnostics.Process padrão com 3 novas propriedades

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

Esta é uma maneira complicada de obter a saída de outro processo do PowerShell:

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