การจับเอามาตรฐานและข้อผิดพลาดกับ 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 จะไม่อนุญาตให้ใช้ -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 รวมถึงบันทึกลงในไฟล์บันทึก เป็นไปได้ไหม?
Murali Dhar Darshan

ไม่สามารถใช้-NoNewWindowกับ-Verb runAs
Dragas

11

ฉันมีปัญหานี้เช่นกันและลงเอยด้วยการใช้โค้ดของ Andyเพื่อสร้างฟังก์ชันเพื่อล้างสิ่งต่างๆเมื่อต้องเรียกใช้คำสั่งหลายคำสั่ง

มันจะส่งคืนรหัส 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"

เป็นความคิดที่ดี แต่ดูเหมือนว่าไวยากรณ์จะไม่ทำงานสำหรับฉัน รายการพารามิเตอร์ไม่ควรใช้ไวยากรณ์ ([type] $ ArgumentName) หรือไม่ คุณสามารถเพิ่มการเรียกใช้ตัวอย่างในฟังก์ชันนี้ได้หรือไม่?
ช่างกุญแจ

เกี่ยวกับ "สิ่งหนึ่งที่ควรทราบ: ฟังก์ชันจะไม่ยอมรับ \ ในเส้นทางต้องใช้เส้นทางแบบเต็ม": คุณสามารถใช้:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz

9

สำคัญ:

เราได้รับการใช้ฟังก์ชั่นตามที่ระบุไว้ข้างต้นโดยแอลพีจี

อย่างไรก็ตามสิ่งนี้มีจุดบกพร่องที่คุณอาจพบเมื่อคุณเริ่มกระบวนการที่สร้างผลลัพธ์จำนวนมาก ด้วยเหตุนี้คุณอาจต้องหยุดชะงักเมื่อใช้ฟังก์ชันนี้ ใช้เวอร์ชันดัดแปลงด้านล่างแทน:

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 จำนวนมากหยุดทำงาน
rhellem

8

ผมมีปัญหากับตัวอย่างเหล่านั้นจากแอนดี้ Arismendiและจากแอลพีจี คุณควรใช้:

$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

คุณอ่านที่ไหนว่า "You should always use: $ p.StandardOutput.ReadToEnd () before $ p.WaitForExit ()"? หากมีเอาต์พุตบนบัฟเฟอร์ที่หมดแล้วให้ติดตามเอาต์พุตเพิ่มเติมในเวลาต่อมาสิ่งนั้นจะพลาดไปหากบรรทัดการดำเนินการอยู่บน WaitForExit และกระบวนการยังไม่เสร็จสิ้น (และต่อมาเอาต์พุต stderr หรือ stdout เพิ่มเติม) ...
CJBS

เกี่ยวกับความคิดเห็นของฉันด้านบนฉันเห็นความคิดเห็นในคำตอบที่ยอมรับในภายหลังเกี่ยวกับการหยุดชะงักและการล้นของบัฟเฟอร์ในกรณีที่มีเอาต์พุตขนาดใหญ่ แต่ฉันคาดหวังว่าเพียงเพราะอ่านบัฟเฟอร์จนจบไม่ได้หมายความว่ากระบวนการ เสร็จสมบูรณ์แล้วและอาจมีเอาต์พุตมากกว่านี้ที่พลาดไป ฉันพลาดอะไรไปรึเปล่า?
CJBS

@CJBS: "เพียงเพราะอ่านบัฟเฟอร์จนจบไม่ได้หมายความว่ากระบวนการเสร็จสมบูรณ์" - หมายความว่าอย่างนั้น นั่นเป็นสาเหตุที่ทำให้เกิดการหยุดชะงักได้ การอ่าน "จนจบ" ไม่ได้หมายความว่า "อ่านอะไรก็ได้ในตอนนี้ " หมายถึงเริ่มอ่านและอย่าหยุดจนกว่าสตรีมจะปิดซึ่งเหมือนกับกระบวนการยุติ
Peter Duniho

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.