Виправлення стандартних помилок та помилок за допомогою Start-Process


112

Чи є помилка в Start-Processкоманді 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

5
У цьому конкретному випадку вам справді потрібен Start-process? ... $process= ping localhost # збереже результат у змінній процесу.
mjsr

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

Відповіді:


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

6
Якщо у вас виникли проблеми із запуском процесу таким чином, див. Тут прийняту відповідь stackoverflow.com/questions/11531068/… , яка має незначні зміни до WaitForExit і StandardOutput.ReadToEnd
Ralph Willgoss

3
Якщо ви використовуєте -verb runAs, це не дозволяє theh -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параметри (це мене на деякий час застало).


Що робити, якщо аргумент містить змінну? Схоже, це не розширюється.
ОО

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

як показати вихід у вікні powershell, а також увійти до файлу журналу? Це можливо?
Муралі Дхар Даршан

Не можна використовувати -NoNewWindowз-Verb runAs
Драгас

11

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

Він поверне коди stderr, stdout та вихід як об’єкти. Варто зазначити одне: функція не прийме .\на шляху; повинні бути використані повні шляхи.

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)? чи можна додати приклад виклику до цієї функції?
Локсміт

Щодо "Одне зауваження: функція не прийме. \ На шляху; повинні використовуватися повні шляхи.": Ви можете використовувати:> $ 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.
bergmeister

1
Це, здається, вирішило моє питання. Я мушу визнати, що я не повністю розумію, чому він завис, але здається, що порожній stderr блокував процес закінчення. Дивна річ, оскільки вона працювала протягом тривалого періоду часу, але раптом перед Xmas вона почала виходити з ладу, викликаючи зависання багатьох Java-процесів.
реллем

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

Ось химерний спосіб отримати вихід з іншого процесу оболонки:

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.