Làm cách nào để tôi bắt đầu ra thành một biến từ một quy trình bên ngoài trong PowerShell?


158

Tôi muốn chạy một quy trình bên ngoài và nắm bắt đầu ra lệnh của nó tới một biến trong PowerShell. Tôi hiện đang sử dụng này:

$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait

Tôi đã xác nhận lệnh đang thực thi nhưng tôi cần phải bắt đầu ra thành một biến. Điều này có nghĩa là tôi không thể sử dụng -RedirectOutput vì điều này chỉ chuyển hướng đến một tệp.


3
Đầu tiên và quan trọng nhất: Không sử dụng Start-Processđể thực thi các ứng dụng bảng điều khiển (theo định nghĩa bên ngoài) một cách đồng bộ - chỉ cần gọi chúng trực tiếp , như trong bất kỳ shell nào; dí dỏm : netdom /verify $pc /domain:hosp.uhhg.org. Làm như vậy sẽ giữ cho ứng dụng được kết nối với các luồng tiêu chuẩn của bảng điều khiển cuộc gọi, cho phép thu được đầu ra của nó bằng cách gán đơn giản $output = netdom .... Hầu hết các câu trả lời dưới đây hoàn toàn từ bỏ Start-Processủng hộ thực hiện trực tiếp.
mkuity0

@ mkuity0 ngoại trừ có thể nếu ai đó muốn sử dụng -Credentialtham số
CJBS

@CJBS Có, để chạy với một danh tính người dùng khác , việc sử dụng Start-Processlà bắt buộc - nhưng chỉ sau đó (và nếu bạn muốn chạy một lệnh trong một cửa sổ riêng). Và người ta nên nhận thức được những hạn chế không thể tránh khỏi trong trường hợp đó: Không có khả năng nắm bắt đầu ra, ngoại trừ - không xen kẽ - văn bản trong tệp , thông qua -RedirectStandardOutput-RedirectStandardError.
mkuity0

Câu trả lời:


161

Bạn đã thử chưa:

$OutputVariable = (Shell command) | Out-String


Tôi đã cố gán nó cho một biến bằng cách sử dụng "=" nhưng trước tiên tôi không thử chuyển đầu ra thành Chuỗi ngoài. Tôi sẽ thử.
Adam Bertram

10
Tôi không hiểu những gì đang xảy ra ở đây và không thể làm cho nó hoạt động. "Shell" có phải là một từ khóa powershell không? Vì vậy, chúng tôi không thực sự sử dụng lệnh ghép ngắn Start-Process? Bạn có thể vui lòng đưa ra một ví dụ cụ thể không (ví dụ: thay thế "Shell" và / hoặc "lệnh" bằng một ví dụ thực tế).
deadlydog

@deadlydog Thay thế Shell Commandbằng bất cứ thứ gì bạn muốn chạy. Nó đơn giản mà.
JNK

1
@stej, bạn nói đúng. Tôi chủ yếu làm rõ rằng mã trong bình luận của bạn có chức năng khác với mã trong câu trả lời. Những người mới bắt đầu như tôi có thể bị loại bỏ bởi những khác biệt tinh tế trong hành vi như thế này!
Sam

1
@Atique tôi gặp vấn đề tương tự. Hóa ra ffmpeg đôi khi sẽ ghi vào stderr thay vì stdout nếu, ví dụ, bạn sử dụng -itùy chọn mà không chỉ định tệp đầu ra. Chuyển hướng đầu ra bằng cách sử dụng 2>&1như được mô tả trong một số câu trả lời khác là giải pháp.
jmbpiano

159

Lưu ý: Lệnh trong câu hỏi sử dụng Start-Process, ngăn chặn việc bắt trực tiếp đầu ra của chương trình đích. Nói chung, không sử dụng Start-Processđể thực thi các ứng dụng bảng điều khiển một cách đồng bộ - chỉ cần gọi chúng trực tiếp , như trong bất kỳ trình bao nào. Làm như vậy để giữ cho ứng dụng được kết nối với các luồng tiêu chuẩn của bảng điều khiển cuộc gọi, cho phép thu được đầu ra của nó bằng cách gán đơn giản $output = netdom ..., như chi tiết bên dưới.

Về cơ bản , việc bắt đầu ra từ các tiện ích bên ngoài hoạt động giống như với các lệnh gốc PowerShell (bạn có thể muốn xem lại cách thực hiện các công cụ bên ngoài ):

$cmdOutput = <command>   # captures the command's success stream / stdout

Lưu ý rằng $cmdOutputnhận được một mảng các đối tượng nếu <command>tạo ra nhiều hơn 1 đối tượng đầu ra , trong trường hợp chương trình bên ngoài có nghĩa là một chuỗi chuỗi chứa các dòng đầu ra của chương trình .
Nếu bạn muốn $cmdOutputđể luôn nhận một đơn - có khả năng nhiều dòng - chuỗi , sử dụng
$cmdOutput = <command> | Out-String


Để chụp đầu ra trong một biến in ra màn hình :

<command> | Tee-Object -Variable cmdOutput # Note how the var name is NOT $-prefixed

Hoặc, nếu <command>là một lệnh ghép ngắn hoặc hàm nâng cao , bạn có thể sử dụng tham số chung
-OutVariable/-ov
:

<command> -OutVariable cmdOutput   # cmdlets and advanced functions only

Lưu ý rằng với -OutVariable, không giống như trong các kịch bản khác, $cmdOutputluôn luôn một bộ sưu tập , ngay cả khi chỉ một đối tượng là đầu ra. Cụ thể, một thể hiện của [System.Collections.ArrayList]kiểu giống như mảng được trả về.
Xem vấn đề GitHub này để thảo luận về sự khác biệt này.


Để nắm bắt đầu ra từ nhiều lệnh , hãy sử dụng một biểu thức con ( $(...)) hoặc gọi một khối tập lệnh ( { ... }) bằng &hoặc .:

$cmdOutput = $(<command>; ...)  # subexpression

$cmdOutput = & {<command>; ...} # script block with & - creates child scope for vars.

$cmdOutput = . {<command>; ...} # script block with . - no child scope

Lưu ý rằng cần phải có tiền tố chung với &(toán tử cuộc gọi) một lệnh riêng có tên / đường dẫn được trích dẫn - ví dụ: $cmdOutput = & 'netdom.exe' ...- không liên quan đến các chương trình bên ngoài (nó áp dụng tương tự cho các tập lệnh PowerShell), nhưng là một yêu cầu cú pháp : PowerShell phân tích cú pháp một câu lệnh bắt đầu bằng một chuỗi trích dẫn trong chế độ biểu thức theo mặc định, trong khi chế độ đối số là cần thiết để gọi các lệnh (lệnh ghép ngắn, chương trình bên ngoài, hàm, bí danh), đó là những gì &đảm bảo.

Sự khác biệt chính giữa $(...)& { ... }/ . { ... }là cái trước thu thập tất cả đầu vào trong bộ nhớ trước khi trả lại toàn bộ, trong khi dòng sau phát ra đầu ra, phù hợp cho xử lý đường ống từng cái một.


Chuyển hướng cũng hoạt động tương tự, về cơ bản (nhưng xem phần bên dưới):

$cmdOutput = <command> 2>&1 # redirect error stream (2) to success stream (1)

Tuy nhiên, đối với các lệnh bên ngoài, phần sau có khả năng hoạt động như mong đợi:

$cmdOutput = cmd /c <command> '2>&1' # Let cmd.exe handle redirection - see below.

Cân nhắc cụ thể cho các chương trình bên ngoài :

  • Các chương trình bên ngoài , vì chúng hoạt động bên ngoài hệ thống loại của PowerShell, chỉ bao giờ trả về chuỗi thông qua luồng thành công (stdout).

  • Nếu đầu ra chứa nhiều hơn 1 dòng , PowerShell bằng cách chia mặc định nó thành một mảng các chuỗi . Chính xác hơn, các dòng đầu ra được lưu trữ trong một mảng kiểu [System.Object[]]có các phần tử là chuỗi ( [System.String]).

  • Nếu bạn muốn đầu ra là một single , có khả năng nhiều đường dây , đường ống đểOut-String :
    $cmdOutput = <command> | Out-String

  • Chuyển hướng stderr sang stdout với2>&1 , để cũng nắm bắt nó như là một phần của dòng thành công, đi kèm với cảnh báo :

    • Để tạo 2>&1stdout và stderr hợp nhất tại nguồn , hãy cmd.exexử lý chuyển hướng , sử dụng các thành ngữ sau:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cgọi cmd.exevới lệnh <command>và thoát sau khi <command>đã kết thúc.
      • Lưu ý các trích dẫn đơn xung quanh 2>&1, điều này đảm bảo rằng chuyển hướng được chuyển đến cmd.exethay vì được PowerShell diễn giải.
      • Lưu ý rằng liên quan cmd.execó nghĩa là các quy tắc thoát ký tự và mở rộng biến môi trường của nó xuất hiện, theo mặc định bên cạnh các yêu cầu riêng của PowerShell; trong PS v3 +, bạn có thể sử dụng tham số đặc biệt --%( ký hiệu phân tích cú pháp dừng ) để tắt giải thích các tham số còn lại bằng PowerShell, ngoại trừ cmd.execác tham chiếu biến môi trường kiểu như, chẳng hạn như %PATH%.

      • Lưu ý rằng vì bạn đang hợp nhất thiết bị xuất chuẩn và thiết bị xuất chuẩn tại nguồn với cách tiếp cận này, bạn sẽ không thể phân biệt giữa các dòng có nguồn gốc xuất chuẩn và thiết bị xuất chuẩn trong PowerShell; nếu bạn cần sự khác biệt này, hãy sử dụng 2>&1chuyển hướng riêng của PowerShell - xem bên dưới.

    • Sử dụng chuyển hướng của PowerShell 2>&1 để biết dòng nào đến từ luồng nào :

      • Stderr sản lượng được chụp như ghi lỗi ( [System.Management.Automation.ErrorRecord]), không dây, do đó mảng đầu ra có thể chứa một sự pha trộn của chuỗi (mỗi chuỗi đại diện cho một dòng stdout) và hồ sơ lỗi (mỗi bản ghi đại diện cho một dòng stderr) . Lưu ý rằng, theo yêu cầu của 2>&1cả chuỗi và bản ghi lỗi đều được nhận qua luồng đầu ra thành công của PowerShell ).

      • Trong bảng điều khiển, các bản ghi lỗi được in màu đỏ và cái thứ nhất theo mặc định tạo ra màn hình đa dòng , theo cùng định dạng mà lỗi không kết thúc của cmdlet sẽ hiển thị; các bản ghi lỗi tiếp theo cũng in màu đỏ, nhưng chỉ in thông báo lỗi của chúng trên một dòng duy nhất .

      • Khi xuất ra bàn điều khiển , các chuỗi thường đứng đầu trong mảng đầu ra, theo sau là các bản ghi lỗi (ít nhất là trong một loạt các đầu ra dòng stdout / stderr "cùng một lúc"), nhưng may mắn thay, khi bạn nắm bắt được đầu ra , nó được xen kẽ đúng cách , sử dụng cùng một thứ tự đầu ra mà bạn sẽ nhận được mà không có 2>&1; nói cách khác: khi xuất ra bàn điều khiển , đầu ra bị bắt KHÔNG phản ánh thứ tự trong đó các dòng stdout và stderr được tạo bởi lệnh bên ngoài.

      • Nếu bạn nắm bắt được toàn bộ đầu ra trong một đơn chuỗi vớiOut-String , PowerShell sẽ thêm dòng thêm , bởi vì chuỗi đại diện của một bản ghi lỗi chứa thông tin bổ sung như vị trí ( At line:...) và loại ( + CategoryInfo ...); tò mò, điều này chỉ áp dụng cho bản ghi lỗi đầu tiên .

        • Để khắc phục sự cố này, áp dụng .ToString()phương thức cho từng đối tượng đầu ra thay vì đường ống đến Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          trong PS v3 +, bạn có thể đơn giản hóa thành:
          $cmdOutput = <command> 2>&1 | % ToString
          (Như một phần thưởng, nếu đầu ra không được ghi lại, điều này tạo ra đầu ra xen kẽ đúng cách ngay cả khi in ra bàn điều khiển.)
      • Ngoài ra, hãy lọc các bản ghi lỗi ra và gửi chúng đến luồng lỗi của PowerShell vớiWrite-Error (như một phần thưởng, nếu đầu ra không được ghi lại, điều này tạo ra đầu ra xen kẽ chính xác ngay cả khi in ra bàn điều khiển):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}

Điều này cuối cùng đã làm việc cho tôi sau khi tôi lấy con đường thực thi của mình VÀ các đối số của tôi cho nó, ném chúng vào một chuỗi và coi đó là <lệnh> của tôi.
Dan

2
@Dan: Khi PowerShell tự phiên dịch <command>, bạn không được kết hợp các đối số thực thi và các đối số của nó trong một chuỗi; với lời mời thông qua cmd /cbạn có thể làm như vậy, và nó phụ thuộc vào tình huống liệu nó có ý nghĩa hay không. Kịch bản nào bạn đang đề cập đến, và bạn có thể đưa ra một ví dụ tối thiểu không?
mkuity0

Hoạt động: $ lệnh = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ lệnh '2> & 1'
Dan

1
@Dan: Có, nó hoạt động, mặc dù bạn không cần biến trung gian và cấu trúc rõ ràng của chuỗi với +toán tử; các công việc sau cũng vậy: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell đảm nhiệm việc chuyển các phần tử $Argsdưới dạng một chuỗi được phân tách bằng dấu cách trong trường hợp đó, một tính năng được gọi là splatter .
mkuity0

Cuối cùng, một câu trả lời thích hợp hoạt động trong PS6.1 +. Bí mật trong nước sốt thực sự là '2>&1'một phần, và không kèm theo ()nhiều kịch bản có xu hướng làm.
not2qubit

24

Nếu bạn muốn chuyển hướng đầu ra lỗi là tốt, bạn phải làm:

$cmdOutput = command 2>&1

Hoặc, nếu tên chương trình có khoảng trắng trong đó:

$cmdOutput = & "command with spaces" 2>&1

4
2> & 1 có nghĩa là gì? 'Chạy lệnh được gọi là 2 và đưa đầu ra của nó vào lệnh chạy được gọi là 1'?
Richard

7
Nó có nghĩa là "chuyển hướng đầu ra lỗi tiêu chuẩn (mô tả tệp 2) đến cùng một nơi mà đầu ra tiêu chuẩn (mô tả tệp 1) đang diễn ra". Về cơ bản, chuyển hướng thông báo lỗi và thông thường đến cùng một vị trí (trong trường hợp này là bàn điều khiển, nếu thiết bị xuất chuẩn không được chuyển hướng ở một nơi khác - như một tệp).
Giovanni Tirloni

11

Hoặc thử cái này. Nó sẽ nắm bắt đầu ra thành biến $ scriptOutput:

& "netdom.exe" $params | Tee-Object -Variable scriptOutput | Out-Null

$scriptOutput

7
-1, phức tạp không cần thiết. $scriptOutput = & "netdom.exe" $params
CharlesB

8
Loại bỏ out-null và điều này là tuyệt vời cho đường ống đến cả vỏ và một biến cùng một lúc.
ferventcoder

10

Một ví dụ thực tế khác:

$result = & "$env:cust_tls_store\Tools\WDK\x64\devcon.exe" enable $strHwid 2>&1 | Out-String

Lưu ý rằng ví dụ này bao gồm một đường dẫn (bắt đầu bằng một biến môi trường). Lưu ý rằng các trích dẫn phải bao quanh đường dẫn và tệp EXE, nhưng không phải là các tham số!

Lưu ý: Đừng quên& ký tự phía trước lệnh, nhưng bên ngoài dấu ngoặc kép.

Đầu ra lỗi cũng được thu thập.

Phải mất một thời gian để kết hợp này hoạt động, vì vậy tôi nghĩ rằng tôi sẽ chia sẻ nó.


8

Tôi đã thử các câu trả lời, nhưng trong trường hợp của tôi, tôi đã không nhận được đầu ra thô. Thay vào đó, nó đã được chuyển đổi thành ngoại lệ PowerShell.

Kết quả thô tôi nhận được với:

$rawOutput = (cmd /c <command> 2`>`&1)

2

Tôi đã làm việc sau đây:

$Command1="C:\\ProgramData\Amazon\Tools\ebsnvme-id.exe"
$result = & invoke-Expression $Command1 | Out-String

$ kết quả cung cấp cho bạn sự cần thiết


1

Điều này làm việc cho tôi:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)

0

Nếu tất cả những gì bạn đang cố gắng làm là nắm bắt đầu ra từ một lệnh, thì điều này sẽ hoạt động tốt.

Tôi sử dụng nó để thay đổi thời gian hệ thống, vì [timezoneinfo]::localluôn tạo ra cùng một thông tin, ngay cả sau khi bạn đã thực hiện thay đổi cho hệ thống. Đây là cách duy nhất tôi có thể xác thực và ghi nhật ký thay đổi theo múi giờ:

$NewTime = (powershell.exe -command [timezoneinfo]::local)
$NewTime | Tee-Object -FilePath $strLFpath\$strLFName -Append

Có nghĩa là tôi phải mở một phiên PowerShell mới để tải lại các biến hệ thống.

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.