Захват стандартных выходов и ошибок с помощью 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
jzbruno
источник
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параметры (это застало меня на некоторое время).

JJones
источник
Что, если в списке аргументов есть переменная? Кажется, не расширяется.
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"
LPG
источник
Хорошая идея, но похоже, что синтаксис у меня не работает. Разве в списке параметров не должен использоваться синтаксис 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.

Pserranne
источник
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
Rainer
источник
Где вы читали, что «Вы всегда должны использовать: $ 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
js2010
источник