Nắm bắt chuẩn và lỗi với Start-Process


112

Có lỗi nào trong lệnh PowerShell Start-Processkhi truy cập thuộc tính StandardErrorStandardOutputkhông?

Nếu tôi chạy phần sau, tôi không nhận được đầu ra:

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

Nhưng nếu tôi chuyển hướng đầu ra đến một tệp, tôi sẽ nhận được kết quả mong đợi:

$process = Start-Process -FilePath ping -ArgumentList localhost -NoNewWindow -PassThru -Wait -RedirectStandardOutput stdout.txt -RedirectStandardError stderr.txt

5
Trong trường hợp cụ thể này, bạn có thực sự cần Start-process không? ... $process= ping localhost # sẽ lưu kết quả đầu ra trong biến process.
mjsr

1
Thật. Tôi đang tìm kiếm một cách rõ ràng hơn để xử lý trả về và đối số. Tôi đã kết thúc việc viết kịch bản như bạn đã trình bày.
jzbruno

Câu trả lời:


128

Đó là cách Start-Processđược thiết kế vì một số lý do. Đây là một cách để lấy nó mà không cần gửi tới tệp:

$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
Tôi đang chấp nhận câu trả lời của bạn. Tôi ước họ sẽ không tạo ra các thuộc tính không được sử dụng, nó rất khó hiểu.
jzbruno

6
Nếu bạn gặp khó khăn khi chạy một quá trình theo cách này, xem câu trả lời được chấp nhận ở đây stackoverflow.com/questions/11531068/... , trong đó có một thay đổi nhỏ về WaitForExit và StandardOutput.ReadToEnd
Ralph Willgoss

3
Khi u sử dụng runas -verb nó không cho phép theh -NoNewWindow hoặc Redirection Tùy chọn
Maverick

15
Mã này sẽ bị khóa trong một số điều kiện do cả StdErr và StdOut đều được đọc đồng bộ đến cuối. msdn.microsoft.com/en-us/library/…
codepoke

8
@codepoke - nó hơi tệ hơn thế - vì nó thực hiện lệnh gọi WaitForExit trước, ngay cả khi nó chỉ chuyển hướng một trong số chúng, nó có thể bế tắc nếu bộ đệm luồng bị lấp đầy (vì nó không cố gắng đọc từ nó cho đến khi quá trình đã thoát ra)
James Manning

20

Trong đoạn mã được đưa ra trong câu hỏi, tôi nghĩ rằng việc đọc thuộc tính ExitCode của biến khởi tạo sẽ hoạt động.

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

Lưu ý rằng (như trong ví dụ của bạn) bạn cần thêm các tham số -PassThru-Wait(điều này khiến tôi khó hiểu trong một thời gian).


Điều gì sẽ xảy ra nếu danh sách đối số chứa một biến? Nó dường như không mở rộng.
OO

1
bạn sẽ đặt danh sách đối số trong dấu ngoặc kép. Liệu điều đó có hiệu quả ? ... $ quá trình = Start-Process -FilePath ping -ArgumentList "-t localhost -n 1" -NoNewWindow -PassThru -WAIT
JJones

làm thế nào để hiển thị đầu ra trong cửa sổ powershell cũng như đăng nhập nó vào tệp nhật ký? Nó có khả thi không?
Murali Dhar Darshan

Không thể sử dụng -NoNewWindowvới-Verb runAs
Dragas

11

Tôi cũng gặp sự cố này và đã kết thúc bằng cách sử dụng mã của Andy để tạo một chức năng dọn dẹp mọi thứ khi cần chạy nhiều lệnh.

Nó sẽ trả về các mã stderr, stdout và exit dưới dạng các đối tượng. Một điều cần lưu ý: hàm sẽ không chấp nhận .\trong đường dẫn; đường dẫn đầy đủ phải được sử dụng.

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

Đây là cách sử dụng nó:

$DisableACMonitorTimeOut = Execute-Command -commandTitle "Disable Monitor Timeout" -commandPath "C:\Windows\System32\powercfg.exe" -commandArguments " -x monitor-timeout-ac 0"

Ý tưởng hay, nhưng có vẻ như cú pháp không hoạt động với tôi. Danh sách tham số không nên sử dụng cú pháp param ([type] $ ArgumentName)? bạn có thể thêm một cuộc gọi ví dụ vào chức năng này không?
Thợ khóa

Về "Một điều cần lưu ý: hàm sẽ không chấp nhận. \ Trong đường dẫn; phải sử dụng đường dẫn đầy đủ.": Bạn có thể sử dụng:> $ pinfo.FileName = Resolve-Path $ commandPath
Lupuz

9

QUAN TRỌNG:

Chúng tôi đã sử dụng chức năng như được cung cấp ở trên bởi LPG .

Tuy nhiên, điều này có một lỗi bạn có thể gặp phải khi bắt đầu một quy trình tạo ra nhiều kết quả đầu ra. Do đó, bạn có thể gặp bế tắc khi sử dụng chức năng này. Thay vào đó, hãy sử dụng phiên bản đã điều chỉnh bên dưới:

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

Thông tin thêm về vấn đề này có thể được tìm thấy tại MSDN :

Điều kiện deadlock có thể xảy ra nếu tiến trình mẹ gọi p.WaitForExit trước p.StandardError.ReadToEnd và tiến trình con viết đủ văn bản để lấp đầy luồng được chuyển hướng. Tiến trình cha sẽ đợi vô thời hạn cho tiến trình con thoát. Tiến trình con sẽ đợi vô thời hạn để cha mẹ đọc từ luồng StandardError đầy đủ.


3
Mã này vẫn bị tắc do lệnh gọi đồng bộ tới ReadToEnd (), liên kết của bạn với MSDN cũng mô tả.
bergmeister

1
Điều này bây giờ dường như đã giải quyết được vấn đề của tôi. Tôi phải thừa nhận rằng tôi không hoàn toàn hiểu lý do tại sao nó bị treo, nhưng có vẻ như trình ký tự rỗng đã chặn quá trình kết thúc. Điều kỳ lạ, vì nó đã hoạt động trong một thời gian dài, nhưng đột nhiên ngay trước Giáng sinh, nó bắt đầu bị lỗi, khiến rất nhiều quy trình Java bị treo.
rhellem

8

Tôi thực sự gặp rắc rối với những ví dụ đó từ Andy Arismenditừ LPG . Bạn nên luôn sử dụng:

$stdout = $p.StandardOutput.ReadToEnd()

trước khi gọi

$p.WaitForExit()

Một ví dụ đầy đủ là:

$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

Bạn đã đọc ở đâu rằng "Bạn nên luôn sử dụng: $ p.StandardOutput.ReadToEnd () trước $ p.WaitForExit ()"? Nếu có đầu ra trên bộ đệm đã hết, sau đó sẽ có nhiều đầu ra hơn sau đó, điều đó sẽ bị bỏ qua nếu dòng thực thi trên WaitForExit và quá trình chưa kết thúc (và sau đó xuất ra thêm stderr hoặc stdout) ....
CJBS

Về nhận xét của tôi ở trên, sau đó tôi đã thấy các nhận xét về câu trả lời được chấp nhận liên quan đến deadlocking và tràn bộ đệm trong trường hợp đầu ra lớn, nhưng điều đó sang một bên, tôi mong đợi rằng chỉ vì bộ đệm được đọc đến cuối, điều đó không có nghĩa là quá trình đã hoàn thành và do đó có thể có nhiều đầu ra hơn bị bỏ lỡ. Tui bỏ lỡ điều gì vậy?
CJBS

@CJBS: "chỉ vì bộ đệm được đọc đến cuối, nó không có nghĩa là quá trình đã hoàn thành" - nó có nghĩa là như vậy. Trên thực tế, đó là lý do tại sao nó có thể bế tắc. Đọc "đến cuối" không có nghĩa là "đọc bất cứ điều gì ở đó bây giờ ". Nó có nghĩa là bắt đầu đọc và không dừng lại cho đến khi luồng bị đóng, cũng giống như quá trình kết thúc.
Peter Duniho

0

Đây là phiên bản hàm của tôi đang trả về System.Diagnostics. Quy trình chuẩn với 3 thuộc tính mới

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

Đây là một cách tuyệt vời để lấy đầu ra từ một quy trình powershell khác:

start-process -wait -nonewwindow powershell 'ps | Export-Clixml out.xml'; import-clixml out.xml
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.