Захват стандартных выходов и ошибок с помощью Start-Process


112

Есть ли ошибка в PowerShell в Start-Processкоманде при доступе к 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

5
В этом конкретном случае вам действительно нужен Start-process? ... $process= ping localhost # сохранит вывод в переменной процесса.
mjsr 07

1
Правда. Я искал более чистый способ обработки возврата и аргументов. В итоге я написал сценарий, как вы показали.
jzbruno 09

Ответы:


128

Так почему-то 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

7
Принимаю твой ответ. Хотелось бы, чтобы они не создавали неиспользуемые свойства, это очень сбивает с толку.
jzbruno 09

6
Если у вас возникли проблемы с запуском процесса таким образом, см. Принятый ответ здесь stackoverflow.com/questions/11531068/… , в котором есть небольшая модификация WaitForExit и StandardOutput.ReadToEnd
Ральф Уиллгосс

3
Когда вы используете -verb runAs, это не позволяет использовать -NoNewWindow или параметры перенаправления
Maverick

15
Этот код будет зависать при некоторых условиях из-за того, что StdErr и StdOut синхронно читаются до конца. msdn.microsoft.com/en-us/library/…
codepoke

8
@codepoke - это немного хуже, чем это - поскольку он сначала выполняет вызов WaitForExit, даже если он перенаправляет только один из них, он может заблокироваться, если буфер потока заполнится (поскольку он не пытается читать из него, пока процесс вышел)
James Manning

20

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

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

Обратите внимание , что (как в вашем примере), необходимо добавить -PassThruи -Waitпараметры (это застало меня на некоторое время).


Что, если в списке аргументов есть переменная? Кажется, не расширяется.
OO

1
вы бы заключили список аргументов в кавычки. Это сработает? ... $ process = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Wait
JJones

как показать вывод в окне PowerShell, а также записать его в файл журнала? Является ли это возможным?
Мурали Дхар Даршан

Невозможно использовать -NoNewWindowс-Verb runAs
Драгас

11

У меня также была эта проблема, и в итоге я использовал код Энди для создания функции для очистки, когда необходимо запустить несколько команд.

Он вернет коды 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"

Хорошая идея, но похоже, что синтаксис у меня не работает. Разве в списке параметров не должен использоваться синтаксис param ([type] $ ArgumentName)? вы можете добавить пример вызова этой функции?
Lockszmith 07

Относительно «Одно замечание: функция не принимает. \ В пути; должны использоваться полные пути.»: Вы можете использовать:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz

9

ВАЖНЫЙ:

Мы использовали функцию, предоставленную 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.


3
Этот код по-прежнему блокируется из-за синхронного вызова ReadToEnd (), который также описывается вашей ссылкой на MSDN.
Бергмайстер

1
Кажется, теперь моя проблема решена. Я должен признать, что не совсем понимаю, почему он завис, но похоже, что пустой stderr заблокировал завершение процесса. Странная вещь, так как он работал долгое время, но внезапно прямо перед Рождеством он начал давать сбой, в результате чего зависало множество Java-процессов.
rhellem 04

8

У меня действительно были проблемы с примерами Энди Арисменди и 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

Где вы читали, что «Вы всегда должны использовать: $ p.StandardOutput.ReadToEnd () перед $ p.WaitForExit ()»? Если в буфере есть выходные данные, которые исчерпаны, после дополнительных выходных данных в более позднее время, они будут пропущены, если строка выполнения находится на WaitForExit, а процесс не завершен (и впоследствии выводит больше stderr или stdout) ....
CJBS

Что касается моего комментария выше, я позже увидел комментарии к принятому ответу относительно взаимоблокировки и переполнения буфера в случаях большого вывода, но, помимо этого, я ожидал, что только потому, что буфер читается до конца, это не означает процесс завершено, и, следовательно, может быть больше пропущенных результатов. Я что-то упускаю?
CJBS

@CJBS: «то, что буфер прочитан до конца, не означает, что процесс завершен» - это действительно означает это. Собственно, поэтому он может зайти в тупик. Чтение «до конца» не означает «читать все там в настоящее время ». Это означает начать чтение и не останавливаться, пока поток не будет закрыт, что аналогично завершению процесса.
Питер Дунихо

0

Вот моя версия функции, которая возвращает стандартный System.Diagnostics.Process с 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
}

0

Вот сложный способ получить вывод другого процесса PowerShell:

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.