Bagaimana cara menangkap output ke dalam variabel dari proses eksternal di PowerShell?

158

Saya ingin menjalankan proses eksternal dan menangkap output perintah itu ke variabel di PowerShell. Saya sedang menggunakan ini:

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

Saya telah mengkonfirmasi bahwa perintah sedang dieksekusi, tetapi saya perlu menangkap keluaran menjadi variabel. Ini berarti saya tidak dapat menggunakan -RedirectOutput karena ini hanya pengalihan ke file.

Adam Bertram
sumber
3
Pertama dan terpenting: Jangan gunakan Start-Processuntuk menjalankan aplikasi konsol (secara eksternal) secara sinkron - cukup panggil mereka secara langsung , seperti pada shell apa pun; yakni: netdom /verify $pc /domain:hosp.uhhg.org. Dengan melakukan hal itu, aplikasi tetap terhubung ke stream standar konsol pemanggil, sehingga hasilnya dapat ditangkap dengan penugasan sederhana $output = netdom .... Sebagian besar jawaban yang diberikan di bawah ini secara implisit dilupakan Start-Processdemi eksekusi langsung.
mklement0
@ mklement0 kecuali mungkin jika seseorang ingin menggunakan -Credentialparameter
CJBS
@ CJBS Ya, untuk menjalankan dengan identitas pengguna yang berbeda , penggunaan Start-Processadalah suatu keharusan - tetapi hanya saat itu (dan jika Anda ingin menjalankan perintah di jendela terpisah). Dan orang harus menyadari keterbatasan yang tak terhindarkan dalam kasus itu: Tidak ada kemampuan untuk menangkap output, kecuali sebagai - non-interleaved - teks dalam file , melalui -RedirectStandardOutputdan -RedirectStandardError.
mklement0

Jawaban:

161

Sudahkah Anda mencoba:

$OutputVariable = (Shell command) | Out-String

JNK
sumber
Saya mencoba untuk menetapkannya ke variabel menggunakan "=" tapi saya tidak mencoba untuk mem-pipe output ke Out-String terlebih dahulu. Saya akan mencobanya.
Adam Bertram
10
Saya tidak mengerti apa yang terjadi di sini dan tidak bisa membuatnya bekerja. Apakah "Shell" kata kunci powershell? Jadi kita tidak benar-benar menggunakan cmdlet Mulai-Proses? Bisakah Anda memberi contoh konkret (misalnya, ganti "Shell" dan / atau "perintah" dengan contoh nyata).
deadlydog
@deadlydog Ganti Shell Commanddengan apa pun yang ingin Anda jalankan. Sesederhana itu.
JNK
1
@stej, kamu benar. Saya terutama mengklarifikasi bahwa kode dalam komentar Anda memiliki fungsi yang berbeda dengan kode dalam jawabannya. Pemula seperti saya dapat dilempar oleh perbedaan halus dalam perilaku seperti ini!
Sam
1
@Atique saya mengalami masalah yang sama. Ternyata ffmpeg terkadang akan menulis ke stderr alih-alih stdout jika, misalnya, Anda menggunakan -iopsi tanpa menentukan file output. Mengarahkan output menggunakan 2>&1seperti yang dijelaskan dalam beberapa jawaban lain adalah solusinya.
jmbpiano
159

Catatan: Perintah dalam pertanyaan menggunakan Start-Process, yang mencegah penangkapan langsung dari output program target. Secara umum, jangan gunakan Start-Processuntuk menjalankan aplikasi konsol secara sinkron - cukup panggil secara langsung , seperti pada shell apa pun. Dengan melakukan hal itu, aplikasi tetap terhubung ke stream standar konsol pemanggil, sehingga outputnya dapat ditangkap dengan penugasan sederhana $output = netdom ..., sebagaimana dirinci di bawah ini.

Pada dasarnya , menangkap output dari utilitas eksternal bekerja sama dengan perintah-perintah asli PowerShell (Anda mungkin ingin penyegaran tentang cara menjalankan alat eksternal ):

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

Catatan yang $cmdOutputmenerima array objek jika <command>menghasilkan lebih dari 1 objek output , yang dalam kasus program eksternal berarti array string yang berisi jalur output program .
Jika Anda ingin $cmdOutputselalu menerima string tunggal - berpotensi multi-garis - , gunakan
$cmdOutput = <command> | Out-String


Untuk menangkap output dalam variabel dan mencetak ke layar :

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

Atau, jika <command>adalah cmdlet atau canggih fungsi, Anda dapat menggunakan parameter umum
-OutVariable/-ov
:

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

Perhatikan bahwa dengan -OutVariable, tidak seperti dalam skenario lain, $cmdOutputadalah selalu sebuah koleksi , bahkan jika hanya satu objek adalah output. Secara khusus, sebuah instance dari [System.Collections.ArrayList]tipe seperti array dikembalikan.
Lihat masalah GitHub ini untuk diskusi tentang perbedaan ini.


Untuk menangkap output dari beberapa perintah , gunakan subekspresi ( $(...)) atau panggil blok skrip ( { ... }) dengan &atau .:

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

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

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

Perhatikan bahwa kebutuhan umum untuk awalan dengan &(operator call) perintah individu yang nama / path dikutip - misalnya, $cmdOutput = & 'netdom.exe' ...- tidak berhubungan dengan program eksternal per se (sama-sama berlaku untuk script PowerShell), tetapi sebuah sintaks persyaratan : PowerShell mem-parsing pernyataan yang dimulai dengan string yang dikutip dalam mode ekspresi secara default, sedangkan mode argumen diperlukan untuk menjalankan perintah (cmdlet, program eksternal, fungsi, alias), yang merupakan &jaminan.

Perbedaan utama antara $(...)dan & { ... }/ . { ... }adalah bahwa yang pertama mengumpulkan semua input dalam memori sebelum mengembalikannya secara keseluruhan, sedangkan yang terakhir mengalirkan output, cocok untuk pemrosesan pipa satu-per-satu.


Pengalihan juga berfungsi sama, secara mendasar (tetapi lihat peringatan di bawah):

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

Namun, untuk perintah eksternal, yang berikut ini lebih mungkin berfungsi seperti yang diharapkan:

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

Pertimbangan khusus untuk program eksternal :

  • Program eksternal , karena mereka beroperasi di luar sistem tipe PowerShell, hanya pernah mengembalikan string melalui aliran kesuksesan mereka (stdout).

  • Jika output berisi lebih dari 1 baris , PowerShell secara default membaginya menjadi array string . Lebih tepatnya, garis keluaran disimpan dalam larik tipe [System.Object[]]yang elemennya adalah string ( [System.String]).

  • Jika Anda ingin output menjadi string tunggal , berpotensi multi-line , pipa keOut-String :
    $cmdOutput = <command> | Out-String

  • Mengarahkan stderr ke stdout dengan2>&1 , sehingga juga menangkapnya sebagai bagian dari aliran kesuksesan, dilengkapi dengan peringatan :

    • Untuk membuat 2>&1penggabungan stdout dan stderr pada sumbernya , biarkan cmd.exemenangani pengalihan , menggunakan idiom berikut:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cmemanggil cmd.exedengan perintah <command>dan keluar setelah <command>selesai.
      • Perhatikan tanda kutip tunggal di sekitar 2>&1, yang memastikan bahwa pengalihan diteruskan cmd.exedaripada ditafsirkan oleh PowerShell.
      • Perhatikan bahwa melibatkan cmd.exeberarti bahwa yang aturan untuk melarikan diri karakter dan memperluas variabel lingkungan ikut bermain, secara default di samping persyaratan PowerShell sendiri; di PS v3 + Anda dapat menggunakan parameter khusus --%(yang disebut simbol stop-parsing ) untuk mematikan interpretasi parameter yang tersisa oleh PowerShell, kecuali untuk cmd.exereferensi variabel-lingkungan-gaya seperti %PATH%.

      • Perhatikan bahwa karena Anda menggabungkan stdout dan stderr pada sumber dengan pendekatan ini, Anda tidak akan dapat membedakan antara baris yang berasal dari stdout dan yang berasal dari stderr di PowerShell; jika Anda memang membutuhkan perbedaan ini, gunakan 2>&1pengalihan PowerShell sendiri - lihat di bawah.

    • Gunakan pengalihan PowerShell 2>&1 untuk mengetahui baris mana yang berasal dari aliran apa :

      • Stderr output ditangkap sebagai catatan error ( [System.Management.Automation.ErrorRecord]), bukan string, sehingga output array mungkin berisi campuran dari string (masing-masing string yang mewakili garis stdout) dan catatan error (setiap record mewakili garis stderr) . Perhatikan bahwa, seperti yang diminta oleh 2>&1, string dan catatan kesalahan diterima melalui aliran output sukses PowerShell ).

      • Di konsol, catatan kesalahan dicetak dengan warna merah , dan yang pertama secara default menghasilkan tampilan multi-baris , dalam format yang sama dengan yang ditampilkan oleh kesalahan non-terminasi cmdlet; catatan kesalahan selanjutnya mencetak dengan warna merah juga, tetapi hanya mencetak pesan kesalahan mereka , pada satu baris .

      • Ketika keluaran ke konsol , senar biasanya datang pertama dalam array output, diikuti oleh catatan kesalahan (setidaknya di antara batch stdout / garis stderr output "pada saat yang sama"), tapi, untungnya, ketika Anda menangkap output , itu disisipkan dengan benar , menggunakan urutan output yang sama Anda akan dapatkan tanpa 2>&1; dengan kata lain: saat mengeluarkan ke konsol , keluaran yang ditangkap TIDAK mencerminkan urutan di mana garis stdout dan stderr dihasilkan oleh perintah eksternal.

      • Jika Anda menangkap seluruh output dalam satu string denganOut-String , PowerShell akan menambahkan baris tambahan , karena representasi string dari catatan kesalahan berisi informasi tambahan seperti lokasi ( At line:...) dan kategori ( + CategoryInfo ...); anehnya, ini hanya berlaku untuk catatan kesalahan pertama .

        • Untuk bekerja di sekitar masalah ini, menerapkan .ToString()metode untuk setiap objek output bukan pipa ke Out-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          di PS v3 + Anda dapat menyederhanakan untuk:
          $cmdOutput = <command> 2>&1 | % ToString
          (Sebagai bonus, jika output tidak ditangkap, ini menghasilkan output yang disisipkan dengan benar bahkan ketika mencetak ke konsol.)
      • Atau, filter catatan kesalahan keluar dan kirim ke aliran kesalahan PowerShell denganWrite-Error (sebagai bonus, jika output tidak ditangkap, ini menghasilkan output yang disisipkan dengan benar bahkan ketika mencetak ke konsol):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}
mklement0
sumber
Ini akhirnya bekerja untuk saya setelah saya mengambil jalur yang dapat dieksekusi DAN argumen saya untuk itu, melemparkannya ke dalam string dan memperlakukannya sebagai <perintah> saya.
Dan
2
@Dan: Ketika PowerShell sendiri menginterpretasikan <command>, Anda tidak boleh menggabungkan executable dan argumennya dalam satu string; dengan doa melalui cmd /cAnda dapat melakukannya, dan itu tergantung pada situasi apakah itu masuk akal atau tidak. Skenario mana yang Anda maksud, dan dapatkah Anda memberikan contoh minimal?
mklement0
Bekerja: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ command '2> & 1'
Dan
1
@ Dan: Ya, itu bekerja, meskipun Anda tidak memerlukan variabel menengah dan konstruksi eksplisit dari string dengan +operator; berikut ini juga berfungsi: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell menangani melewatkan elemen $Argssebagai string yang dipisahkan oleh spasi dalam kasus itu, fitur yang disebut splatting .
mklement0
Akhirnya jawaban yang tepat yang bekerja di PS6.1 +. Rahasia dalam saus memang '2>&1'bagiannya, dan tidak tertutupi ()seperti yang banyak dilakukan oleh skrip.
not2qubit
24

Jika Anda ingin mengarahkan output kesalahan juga, Anda harus melakukan:

$cmdOutput = command 2>&1

Atau, jika nama program memiliki spasi di dalamnya:

$cmdOutput = & "command with spaces" 2>&1
manojlds
sumber
4
Apa artinya 2> & 1? 'jalankan perintah yang disebut 2 dan masukkan outputnya ke dalam perintah yang dijalankan yang disebut 1'?
Richard
7
Ini berarti "mengarahkan output kesalahan standar (file descriptor 2) ke tempat yang sama di mana output standar (file deskriptor 1) akan". Pada dasarnya, pengalihan pesan normal dan kesalahan ke tempat yang sama (dalam hal ini konsol, jika stdout tidak dialihkan ke tempat lain - seperti file).
Giovanni Tirloni
11

Atau coba ini. Ini akan menangkap output ke variabel $ scriptOutput:

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

$scriptOutput
Finzzownt
sumber
7
-1, tidak perlu rumit. $scriptOutput = & "netdom.exe" $params
CharlesB
8
Menghapus out-null dan ini bagus untuk memipakan shell dan variabel sekaligus.
ferventcoder
10

Contoh kehidupan nyata lainnya:

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

Perhatikan bahwa contoh ini mencakup lintasan (yang dimulai dengan variabel lingkungan). Perhatikan bahwa tanda kutip harus mengelilingi path dan file EXE, tetapi bukan parameternya!

Catatan: Jangan lupa &karakter di depan perintah, tetapi di luar tanda kutip.

Output kesalahan juga dikumpulkan.

Butuh beberapa saat untuk membuat kombinasi ini berfungsi, jadi saya berpikir bahwa saya akan membagikannya.

Davidiam
sumber
8

Saya mencoba jawabannya, tetapi dalam kasus saya, saya tidak mendapatkan hasil mentah. Sebaliknya itu dikonversi ke pengecualian PowerShell.

Hasil mentah yang saya dapatkan:

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

Saya mendapat yang berikut ini untuk bekerja:

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

$ result memberi Anda yang dibutuhkan

Faye Smelter
sumber
1

Hal ini berhasil untuk saya:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)
Alex R.
sumber
0

Jika semua yang Anda coba lakukan adalah menangkap output dari suatu perintah, maka ini akan bekerja dengan baik.

Saya menggunakannya untuk mengubah waktu sistem, karena [timezoneinfo]::localselalu menghasilkan informasi yang sama, bahkan setelah Anda membuat perubahan pada sistem. Ini adalah satu-satunya cara saya dapat memvalidasi dan mencatat perubahan zona waktu:

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

Berarti saya harus membuka sesi PowerShell baru untuk memuat ulang variabel sistem.

Kelly Davis
sumber