Menangkap keluar standar dan kesalahan dengan Start-Process

112

Apakah ada bug dalam Start-Processperintah PowerShell saat mengakses properti StandardErrordan StandardOutput?

Jika saya menjalankan perintah berikut, saya tidak mendapatkan output:

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

Tetapi jika saya mengarahkan output ke file, saya mendapatkan hasil yang diharapkan:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt
jzbruno.dll
sumber
5
Dalam kasus khusus ini apakah Anda benar-benar memerlukan Start-process? ... $process= ping localhost # akan menyimpan output dalam variabel proses.
mjsr
1
Benar. Saya mencari cara yang lebih bersih untuk menangani pemulangan dan pertengkaran. Saya akhirnya menulis naskah seperti yang Anda tunjukkan.
jzbruno

Jawaban:

128

Begitulah cara Start-Processdirancang untuk beberapa alasan. Berikut cara mendapatkannya tanpa mengirim ke file:

$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
sumber
7
Saya menerima jawaban Anda. Saya berharap mereka tidak membuat properti yang tidak digunakan, ini sangat membingungkan.
jzbruno
6
Jika Anda mengalami masalah dalam menjalankan proses dengan cara ini, lihat jawaban yang diterima di sini stackoverflow.com/questions/11531068/… , yang memiliki sedikit modifikasi pada WaitForExit dan StandardOutput.ReadToEnd
Ralph Willgoss
3
Ketika Anda menggunakan -verb runAs tidak mengizinkan theh -NoNewWindow atau Redirection Options
Maverick
15
Kode ini akan menemui jalan buntu dalam beberapa kondisi karena StdErr dan StdOut dibaca secara bersamaan sampai akhir. msdn.microsoft.com/en-us/library/…
codepoke
8
@codepoke - ini sedikit lebih buruk dari itu - karena ia melakukan panggilan WaitForExit terlebih dahulu, bahkan jika hanya dialihkan salah satunya, itu dapat menemui jalan buntu jika buffer aliran terisi (karena tidak mencoba untuk membacanya sampai proses telah keluar)
James Manning
20

Dalam kode yang diberikan dalam pertanyaan, saya pikir membaca properti ExitCode dari variabel inisiasi harus berfungsi.

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

Perhatikan bahwa (seperti dalam contoh Anda) Anda perlu menambahkan parameter -PassThrudan -Wait(ini membuat saya bingung untuk sementara waktu).

JJones
sumber
Bagaimana jika argumentlist berisi variabel? Sepertinya tidak berkembang.
OO
1
Anda akan meletakkan daftar argumen dalam tanda kutip. Apakah itu akan berhasil? ... $ process = Mulai-Proses -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -Tunggu
JJones
bagaimana cara menampilkan output di jendela PowerShell serta memasukkannya ke file log? Apa itu mungkin?
Murali Dhar Darshan
Tidak dapat digunakan -NoNewWindowdengan-Verb runAs
Dragas
11

Saya juga mengalami masalah ini dan akhirnya menggunakan kode Andy untuk membuat fungsi untuk membersihkan berbagai hal ketika banyak perintah perlu dijalankan.

Ini akan mengembalikan kode stderr, stdout, dan keluar sebagai objek. Satu hal yang perlu diperhatikan: fungsi tidak akan menerima .\di jalur; jalur lengkap harus digunakan.

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

Berikut cara menggunakannya:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"
LPG
sumber
Ide bagus, tetapi sepertinya sintaksnya tidak berfungsi untuk saya. Bukankah daftar parameter harus menggunakan sintaks param ([type] $ ArgumentName)? dapatkah Anda menambahkan contoh panggilan ke fungsi ini?
Lockszmith
Mengenai "Satu hal yang perlu diperhatikan: fungsi tidak akan menerima. \ Di jalur; jalur lengkap harus digunakan.": Anda dapat menggunakan:> $ pinfo.FileName = Resolve-Path $
commandPath
9

PENTING:

Kami telah menggunakan fungsi seperti yang disediakan di atas oleh LPG .

Namun, ini mengandung bug yang mungkin Anda temui ketika Anda memulai proses yang menghasilkan banyak output. Karena itu, Anda mungkin menemui jalan buntu saat menggunakan fungsi ini. Alih-alih gunakan versi yang diadaptasi di bawah ini:

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

Informasi lebih lanjut tentang masalah ini dapat ditemukan di MSDN :

Kondisi kebuntuan bisa terjadi jika proses induk memanggil p.WaitForExit sebelum p.StandardError.ReadToEnd dan proses turunan menulis teks yang cukup untuk mengisi aliran yang dialihkan. Proses induk akan menunggu tanpa batas waktu sampai proses anak keluar. Proses turunan akan menunggu tanpa batas waktu hingga induknya membaca dari aliran StandardError penuh.

pserranne.dll
sumber
3
Kode ini masih mengalami kebuntuan karena panggilan sinkron ke ReadToEnd (), yang juga dijelaskan oleh tautan Anda ke MSDN.
bergmeister
1
Ini sekarang tampaknya telah menyelesaikan masalah saya. Saya harus mengakui bahwa saya tidak sepenuhnya mengerti mengapa itu hang, tetapi tampaknya stderr kosong memblokir proses untuk menyelesaikan. Anehnya, karena itu berhasil untuk jangka waktu yang lama, tetapi tiba-tiba tepat sebelum Natal itu mulai gagal, menyebabkan banyak proses Java macet.
rhellem
8

Saya benar-benar kesulitan dengan contoh-contoh dari Andy Arismendi dan dari LPG . Anda harus selalu menggunakan:

$stdout = $p.StandardOutput.ReadToEnd()

sebelum menelepon

$p.WaitForExit()

Contoh lengkapnya adalah:

$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
sumber
Di mana Anda membaca bahwa "Anda harus selalu menggunakan: $ p.StandardOutput.ReadToEnd () sebelum $ p.WaitForExit ()"? Jika ada output pada buffer yang habis, mengikuti lebih banyak output di lain waktu, itu akan terlewatkan jika jalur eksekusi ada di WaitForExit dan prosesnya belum selesai (dan kemudian mengeluarkan lebih banyak stderr atau stdout) ....
CJBS
Mengenai komentar saya di atas, saya kemudian melihat komentar pada jawaban yang diterima mengenai deadlocking dan buffer overflow dalam kasus output besar, tetapi selain itu, saya berharap bahwa hanya karena buffer dibaca sampai akhir, itu tidak berarti prosesnya. telah selesai, dan mungkin akan ada lebih banyak keluaran yang terlewat. Apakah saya melewatkan sesuatu?
CJBS
@CJBS: "hanya karena buffer dibaca sampai akhir, itu tidak berarti proses telah selesai" - itu berarti itu. Padahal, itu sebabnya bisa buntu. Membaca "sampai akhir" tidak berarti "membaca apa pun yang ada di sana sekarang ". Artinya mulai membaca, dan jangan berhenti sampai streaming ditutup, yang sama dengan proses penghentian.
Peter Duniho
0

Berikut adalah versi fungsi saya yang mengembalikan System.Diagnostics.Process standar dengan 3 properti baru

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

Berikut cara kludgy untuk mendapatkan output dari proses PowerShell lain:

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