Захват стандартного выхода и ошибки при запуске процесса


есть ли ошибка в Powershell при доступе к StandardError и StandardOutput свойства?

если я запускаю следующее, Я не получаю вывода

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

но если я перенаправить вывод в файл, я получаю ожидаемый результат

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
6 73

6 ответов:

вот так Start-Process была разработана по какой-то причине. Вот способ получить его без отправки в файл:

$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

в коде, приведенном в вопросе, я думаю, что чтение свойства ExitCode переменной инициализации должно работать.

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

обратите внимание, что (как в вашем примере) вам нужно добавить-PassThru и-Wait params (это поймало меня на некоторое время)

у меня также была эта проблема, и в итоге я использовал код Andys для создания функции для очистки, когда нужно запустить несколько команд, он вернет коды stderr, stdout и exit в качестве объектов. Одна вещь, чтобы отметить, что функция не будет принимать .\ в пути должны использоваться полные пути.

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

вот как это использовать

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"

у меня действительно были проблемы с этими примерами выше от @Andy Arismendi и @LPG. Вы всегда должны использовать:

$stdout = $p.StandardOutput.ReadToEnd()

перед вызовом

$p.WaitForExit()

полный пример:

$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

важно:

мы использовали функцию как обеспечено выше LPG. Однако это содержит ошибку, с которой вы можете столкнуться при запуске процесса, который генерирует много выходных данных. Из-за этого вы можете оказаться в тупике при использовании этой функции. Вместо этого используйте адаптированную версию ниже:

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

дополнительную информацию по этому вопросу можно найти в MSDN:

условие взаимоблокировки может возникнуть, если родительский процесс вызывает p. WaitForExit перед P. StandardError.ReadToEnd и дочерний процесс записывает достаточно текста, чтобы заполнить перенаправленный поток. Родительский процесс будет бесконечно ждать завершения дочернего процесса. Дочерний процесс будет бесконечно ждать, пока родитель прочитает из полного потока StandardError.

EDIT: добавлена отсутствующая фигурная скобка в конце блока Try.

вот моя версия функции, которая возвращает стандартную систему.Диагностика.Процесс с 3 новыми свойствами

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
}