ฉันจะจับเอาท์พุทเป็นตัวแปรจากกระบวนการภายนอกใน PowerShell ได้อย่างไร


158

ฉันต้องการเรียกใช้กระบวนการภายนอกและจับเอาท์พุทคำสั่งไปยังตัวแปรใน PowerShell ฉันกำลังใช้สิ่งนี้อยู่:

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

ฉันยืนยันว่าคำสั่งกำลังดำเนินการ แต่ฉันต้องจับเอาท์พุทเป็นตัวแปร ซึ่งหมายความว่าฉันไม่สามารถใช้ -RedirectOutput ได้เนื่องจากเป็นการเปลี่ยนเส้นทางไปยังไฟล์เท่านั้น


3
สิ่งแรกและสำคัญที่สุด: อย่าใช้Start-Processเพื่อเรียกใช้แอปพลิเคชันคอนโซล (ตามคำจำกัดความภายนอก) แบบซิงโครนัส - เพียงเรียกใช้โดยตรงในเชลล์ใด ๆ เพื่อปัญญา: netdom /verify $pc /domain:hosp.uhhg.org. $output = netdom ...การทำเช่นนี้จะช่วยให้แอพลิเคชันที่เชื่อมต่อกับลำธารมาตรฐานคอนโซลโทรที่ช่วยให้การส่งออกของมันจะถูกจับโดยการโอนง่าย คำตอบส่วนใหญ่ที่ระบุด้านล่างโดยนัยละเลยStart-Processในความโปรดปรานของการดำเนินการโดยตรง
mklement0

@ mklement0 ยกเว้นบางทีหากต้องการใช้-Credentialพารามิเตอร์
CJBS

@CJBS ใช่เพื่อที่จะใช้งานด้วยเอกลักษณ์ของผู้ใช้ที่แตกต่างกันการใช้งานStart-Processเป็นสิ่งที่ต้องมี - แต่ก็ต่อเมื่อ (และถ้าคุณต้องการเรียกใช้คำสั่งในหน้าต่างแยกต่างหาก) และหนึ่งควรจะตระหนักถึงข้อ จำกัด ที่หลีกเลี่ยงไม่ได้ในกรณีที่ว่าไม่มีความสามารถในการส่งออกจับยกเว้นที่ไม่ - บรรณนิทัศน์ - ข้อความในไฟล์ผ่านทางและ-RedirectStandardOutput -RedirectStandardError
mklement0

คำตอบ:


161

คุณเคยลองไหม:

$OutputVariable = (Shell command) | Out-String


ฉันพยายามกำหนดให้กับตัวแปรโดยใช้ "=" แต่ฉันไม่ได้พยายามไพพ์เอาต์พุตไปยัง Out-String ก่อน ฉันจะลองดูสิ
Adam Bertram

10
ฉันไม่เข้าใจว่าเกิดอะไรขึ้นที่นี่และทำงานไม่ได้ คำว่า "Shell" เป็นคำสำคัญหรือ ดังนั้นเราไม่ได้ใช้ cmdlet เริ่มกระบวนการจริงหรือ คุณช่วยยกตัวอย่างที่เป็นรูปธรรมได้ไหม (เช่นแทนที่ "Shell" และ / หรือ "command" ด้วยตัวอย่างจริง)
deadlydog

@deadlydog แทนที่Shell Commandด้วยสิ่งที่คุณต้องการเรียกใช้ มันง่ายมาก
JNK

1
@stej คุณพูดถูก ฉันชี้แจงว่ารหัสในความคิดเห็นของคุณมีฟังก์ชั่นที่แตกต่างกับรหัสในคำตอบ ผู้เริ่มต้นอย่างฉันสามารถถูกเหวี่ยงโดยความแตกต่างเล็กน้อยในพฤติกรรมเช่นนี้!
Sam

1
@ ติ๊กฉันวิ่งเข้าไปในปัญหาเดียวกัน ปรากฎว่าบางครั้ง ffmpeg จะเขียนไปยัง stderr แทนที่จะเป็น stdout หากคุณใช้-iตัวเลือกดังกล่าวโดยไม่ต้องระบุไฟล์เอาต์พุต การเปลี่ยนเส้นทางเอาต์พุตโดยใช้2>&1ตามที่อธิบายไว้ในคำตอบอื่น ๆ คือคำตอบ
jmbpiano

159

หมายเหตุ: คำสั่งในคำถามใช้Start-Processซึ่งป้องกันการดักจับผลลัพธ์ของโปรแกรมเป้าหมายโดยตรง โดยทั่วไป อย่าใช้Start-Processเพื่อเรียกใช้แอปพลิเคชันคอนโซลแบบซิงโครนัส - เพียงเรียกใช้โดยตรงเช่นเดียวกับเชลล์ การทำเช่นนี้จะทำให้แอปพลิเคชันเชื่อมต่อกับสตรีมมาตรฐานของคอนโซลการโทรทำให้สามารถบันทึกเอาต์พุตได้โดยการมอบหมายอย่างง่าย$output = netdom ...ดังรายละเอียดด้านล่าง

โดยพื้นฐานแล้วการจับเอาท์พุทจากยูทิลิตี้ภายนอกทำงานเหมือนกับคำสั่ง PowerShell-native (คุณอาจต้องการทบทวนวิธีการใช้งานเครื่องมือภายนอก ):

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

ทราบว่า$cmdOutputได้รับอาร์เรย์ของวัตถุถ้า<command>ผลิตวัตถุผลผลิตมากกว่า 1ซึ่งในกรณีของนั้นโปรแกรมภายนอกหมายถึงอาร์เรย์สตริงที่มีโปรแกรมการส่งออกเส้น
หากคุณต้องการ$cmdOutputได้รับสตริงเดี่ยวที่อาจเกิดขึ้นหลายสายให้ใช้
$cmdOutput = <command> | Out-String


ในการจับภาพเอาต์พุตในตัวแปรแล้วพิมพ์ไปที่หน้าจอ :

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

หรือถ้า<command>เป็นฟังก์ชันcmdletหรือขั้นสูงคุณสามารถใช้พารามิเตอร์ทั่วไป
-OutVariable/-ov
:

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

โปรดทราบว่า-OutVariableแตกต่างจากในสถานการณ์อื่น ๆ ที่ $cmdOutputเป็นเสมอคอลเลกชันแม้เพียงหนึ่งวัตถุเป็นเอาท์พุท โดยเฉพาะอินสแตนซ์ของ[System.Collections.ArrayList]ชนิดเหมือนอาร์เรย์จะถูกส่งกลับ
ดูปัญหา GitHub นี้เพื่อการสนทนาเกี่ยวกับความคลาดเคลื่อนนี้


ในการดักจับเอาต์พุตจากหลายคำสั่งให้ใช้ subexpression ( $(...)) หรือเรียกบล็อกสคริปต์ ( { ... }) ด้วย&หรือ.:

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

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

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

โปรดทราบว่าจำเป็นที่จะต้องทั่วไปคำนำหน้าด้วย&(ผู้ประกอบการโทร) คำสั่งของแต่ละบุคคลที่มีชื่อ / เส้นทางที่ยกมา - เช่น$cmdOutput = & 'netdom.exe' ...- ไม่เกี่ยวข้องกับโปรแกรมภายนอกต่อ se (มันเท่า ๆ กันนำไปใช้กับสคริปต์ PowerShell) แต่เป็นไวยากรณ์ต้องการ : PowerShell แยกวิเคราะห์คำสั่งที่เริ่มต้นด้วยสตริงที่ยกมาในโหมดการแสดงออกโดยค่าเริ่มต้นในขณะที่โหมดอาร์กิวเมนต์ที่จำเป็นในการเรียกคำสั่ง (cmdlets โปรแกรมภายนอกฟังก์ชั่นนามแฝง) ซึ่งเป็นสิ่งที่ทำให้มั่นใจ&

ความแตกต่างที่สำคัญระหว่าง$(...)และ& { ... }/ . { ... }คือการที่อดีตรวบรวมอินพุตทั้งหมดในหน่วยความจำก่อนที่จะส่งคืนทั้งหมดในขณะที่สตรีมหลังเอาต์พุตเหมาะสำหรับการประมวลผลไพพ์ไลน์แบบหนึ่งต่อหนึ่ง


การเปลี่ยนเส้นทางยังใช้งานได้เหมือนเดิมโดยพื้นฐาน (แต่ดูคำเตือนด้านล่าง):

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

อย่างไรก็ตามสำหรับคำสั่งภายนอกต่อไปนี้มีแนวโน้มที่จะทำงานได้ตามที่คาดไว้:

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

ข้อควรพิจารณาเฉพาะสำหรับโปรแกรมภายนอก :

  • โปรแกรมภายนอกเนื่องจากทำงานนอกระบบชนิดของ PowerShell จะส่งคืนสตริงผ่านสตรีมสำเร็จเท่านั้น (stdout)

  • หากการส่งออกมีมากกว่า 1 เส้น , PowerShell โดยค่าเริ่มต้นแยกลงในอาร์เรย์ของสตริง แม่นยำยิ่งขึ้นบรรทัดเอาต์พุตจะถูกเก็บไว้ในอาร์เรย์ประเภท[System.Object[]]ที่มีองค์ประกอบเป็นสตริง ( [System.String])

  • หากคุณต้องการส่งออกจะเป็นคนเดียวที่อาจเกิดขึ้นหลายสายสตริง , ท่อOut-String :
    $cmdOutput = <command> | Out-String

  • การเปลี่ยนเส้นทาง stderr ไปยัง stdout ด้วย2>&1เช่นกันเพื่อจับภาพไว้เป็นส่วนหนึ่งของสตรีมความสำเร็จมาพร้อมกับcaveats :

    • ที่จะทำให้2>&1stdout ผสานและ stderr ที่แหล่งที่มา , ให้cmd.exeจัดการเปลี่ยนเส้นทางโดยใช้สำนวนต่อไปนี้:
      $cmdOutput = cmd /c <command> '2>&1' # *array* of strings (typically)
      $cmdOutput = cmd /c <command> '2>&1' | Out-String # single string

      • cmd /cเรียกใช้cmd.exeด้วยคำสั่ง<command>และออกหลังจาก<command>เสร็จสิ้น
      • สังเกตเครื่องหมายอัญประกาศเดี่ยว2>&1ซึ่งรับประกันว่าการเปลี่ยนเส้นทางจะถูกส่งไปยังcmd.exeแทนที่จะถูกตีความโดย PowerShell
      • โปรดทราบว่าเกี่ยวข้องกับcmd.exeหมายความว่าของกฎระเบียบสำหรับการหลบหนีตัวอักษรและการขยายตัวแปรสภาพแวดล้อมเข้ามาเล่นโดยเริ่มต้นในนอกเหนือไปจากความต้องการของตัวเอง PowerShell ของ; ใน PS v3 + คุณสามารถใช้พารามิเตอร์พิเศษ--%(ที่เรียกว่าสัญลักษณ์หยุดแยก ) เพื่อปิดการตีความของพารามิเตอร์ที่เหลือโดย PowerShell ยกเว้นสำหรับการอ้างอิงตัวแปรสภาพแวดล้อมสไตล์เช่นcmd.exe%PATH%

      • โปรดทราบว่าเนื่องจากคุณรวม stdout และ stderr ที่แหล่งที่มาด้วยวิธีนี้คุณจะไม่สามารถแยกความแตกต่างระหว่างบรรทัด stdout-originated และ stderr-origin กำเนิดใน PowerShell; หากคุณต้องการความแตกต่างนี้ให้ใช้การ2>&1เปลี่ยนเส้นทางของ PowerShell - ดูด้านล่าง

    • ใช้การ 2>&1เปลี่ยนเส้นทางของ PowerShellเพื่อทราบว่าสายใดมาจากสตรีมใด :

      • stderrเอาท์พุทถูกจับเป็นบันทึกข้อผิดพลาด ( [System.Management.Automation.ErrorRecord]) ไม่ใช่สตริงดังนั้นอาร์เรย์การส่งออกอาจมีการผสมของสตริง (แต่ละสายเป็นตัวแทนของสาย stdout) และบันทึกข้อผิดพลาด (แต่ละระเบียนที่เป็นตัวแทนของสาย stderr ก) โปรดทราบว่าตามที่ร้องขอโดย2>&1ทั้งสตริงและบันทึกข้อผิดพลาดจะได้รับผ่านสตรีมเอาต์พุตความสำเร็จของ PowerShell

      • ในคอนโซลบันทึกข้อผิดพลาดจะพิมพ์เป็นสีแดงและรายการที่1โดยค่าเริ่มต้นจะสร้างการแสดงผลหลายบรรทัดในรูปแบบเดียวกับที่ข้อผิดพลาดที่ไม่สิ้นสุดของ cmdlet จะแสดงขึ้น ภายหลังบันทึกข้อผิดพลาดพิมพ์สีแดงเช่นกัน แต่พิมพ์เฉพาะข้อผิดพลาดของพวกเขาข้อความบนบรรทัดเดียว

      • เมื่อส่งออกไปยังคอนโซลสตริงมักจะมาก่อนในอาร์เรย์อาร์เรย์ตามด้วยบันทึกข้อผิดพลาด (อย่างน้อยในกลุ่มของ stdout / stderr บรรทัดเอาท์พุท "ในเวลาเดียวกัน") แต่โชคดีเมื่อคุณจับเอาท์พุท มันเป็น interleaved อย่างถูกต้องโดยใช้คำสั่งเอาท์พุทเดียวกับที่คุณจะได้รับโดยไม่ต้อง2>&1; กล่าวอีกนัยหนึ่ง: เมื่อแสดงผลไปยังคอนโซลเอาต์พุตที่ดักจับจะไม่แสดงลำดับที่บรรทัด stdout และ stderr ถูกสร้างโดยคำสั่งภายนอก

      • ถ้าคุณจับภาพการส่งออกทั้งหมดในครั้งเดียวสตริงกับOut-String , PowerShell จะเพิ่มสายพิเศษเพราะเป็นตัวแทนสตริงของบันทึกข้อผิดพลาดมีข้อมูลเพิ่มเติมเช่นสถานที่ตั้ง ( At line:...) และหมวดหมู่ ( + CategoryInfo ...); อยากรู้อยากเห็นสิ่งนี้ใช้เฉพาะกับบันทึกข้อผิดพลาดแรก

        • เมื่อต้องการแก้ไขปัญหานี้ใช้.ToString()วิธีการกับแต่ละวัตถุผลลัพธ์แทนที่จะไพพ์กับOut-String:
          $cmdOutput = <command> 2>&1 | % { $_.ToString() };
          ใน PS v3 + คุณสามารถลดความซับซ้อนของ:
          $cmdOutput = <command> 2>&1 | % ToString
          (เป็นโบนัสหากไม่ได้จับเอาท์พุทสิ่งนี้จะสร้างเอาต์พุตอินเตอร์เลดอย่างเหมาะสมแม้เมื่อพิมพ์ไปยังคอนโซล)
      • อีกทางเลือกหนึ่งให้กรองข้อผิดพลาดออกและส่งไปยังสตรีมข้อผิดพลาดของ PowerShell ด้วยWrite-Error (เป็นโบนัสหากไม่ได้จับเอาท์พุทสิ่งนี้จะสร้างเอาต์พุตแบบแทรกอย่างถูกต้องแม้เมื่อพิมพ์ไปยังคอนโซล):

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

ในที่สุดมันก็ใช้งานได้สำหรับฉันหลังจากที่ฉันใช้เส้นทางที่ปฏิบัติการได้และข้อโต้แย้งของฉันมันโยนมันในสตริงและถือว่าเป็นคำสั่งของฉัน
Dan

2
@Dan: เมื่อ PowerShell ตีความตัวเอง<command>คุณจะต้องไม่รวมไฟล์ปฏิบัติการและอาร์กิวเมนต์ในสตริงเดียว ด้วยการร้องขอผ่านcmd /cคุณสามารถทำได้และขึ้นอยู่กับสถานการณ์ว่าเหมาะสมหรือไม่ คุณหมายถึงสถานการณ์ใดและคุณสามารถยกตัวอย่างเล็กน้อยได้หรือไม่
mklement0

ทำงานได้: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ คำสั่ง '2> & 1'
Dan

1
@Dan: ใช่มันใช้งานได้แม้ว่าคุณไม่ต้องการตัวแปรกลางและการสร้างสตริงอย่างชัดเจนด้วย+โอเปอเรเตอร์ ต่อไปนี้การทำงานมากเกินไป: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell ดูแลผ่านองค์ประกอบของ$Argsเป็นสตริงพื้นที่แยกออกจากกันในกรณีที่มีคุณสมบัติที่เรียกว่าsplatting
mklement0

ในที่สุดคำตอบที่เหมาะสมที่ทำงานใน PS6.1 + ความลับในซอสนั้นเป็น'2>&1'ส่วนหนึ่งและไม่รวมอยู่ใน()สคริปต์หลายอย่างที่ควรทำ
not2qubit

24

หากคุณต้องการเปลี่ยนเส้นทางข้อผิดพลาดเช่นกันคุณต้องทำ:

$cmdOutput = command 2>&1

หรือถ้าชื่อโปรแกรมมีช่องว่างอยู่:

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

4
2> & 1 หมายถึงอะไร 'รันคำสั่งที่เรียกว่า 2 และใส่ผลลัพธ์ลงในคำสั่ง run ที่เรียกว่า 1'?
ริชาร์ด

7
หมายความว่า "เปลี่ยนเส้นทางข้อผิดพลาดมาตรฐาน (ตัวอธิบายไฟล์ 2) ไปยังตำแหน่งเดียวกับที่เอาต์พุตมาตรฐาน (ตัวอธิบายไฟล์ 1) กำลังดำเนินการ" โดยทั่วไปแล้วการเปลี่ยนเส้นทางข้อความปกติและข้อผิดพลาดไปยังสถานที่เดียวกัน (ในกรณีนี้คอนโซลถ้า stdout ไม่ได้ถูกเปลี่ยนเส้นทางที่อื่น - เช่นไฟล์)
Giovanni Tirloni

11

หรือลองสิ่งนี้ มันจะจับผลลัพธ์เป็นตัวแปร $ scriptOutput:

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

$scriptOutput

7
-1, ซับซ้อนโดยไม่จำเป็น $scriptOutput = & "netdom.exe" $params
CharlesB

8
การลบ out-null และนี่เป็นวิธีที่ยอดเยี่ยมสำหรับการวางท่อทั้งเชลล์และตัวแปรในเวลาเดียวกัน
ferventcoder

10

อีกตัวอย่างในชีวิตจริง:

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

ขอให้สังเกตว่าตัวอย่างนี้มีเส้นทาง (ซึ่งเริ่มต้นด้วยตัวแปรสภาพแวดล้อม) โปรดสังเกตว่าเครื่องหมายคำพูดต้องล้อมรอบพา ธ และไฟล์ EXE แต่ไม่ใช่พารามิเตอร์!

หมายเหตุ:อย่าลืมตัว&ละครที่อยู่ด้านหน้าของคำสั่ง แต่อยู่นอกเครื่องหมายคำพูด

มีการรวบรวมเอาต์พุตข้อผิดพลาดด้วย

ฉันต้องใช้เวลาสักพักกว่าจะได้ชุดค่าผสมนี้ใช้งานได้ดังนั้นฉันคิดว่าฉันจะแบ่งปัน


8

ฉันลองคำตอบ แต่ในกรณีของฉันฉันไม่ได้รับผลดิบ แต่จะถูกแปลงเป็นข้อยกเว้น PowerShell

ผลลัพธ์ดิบที่ฉันได้รับ:

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

2

ฉันได้งานต่อไปนี้:

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

ผลลัพท์$ช่วยให้คุณมีความต้องการ



0

หากสิ่งที่คุณพยายามทำคือการจับเอาท์พุทจากคำสั่งแล้วสิ่งนี้จะทำงานได้ดี

ฉันใช้เพื่อเปลี่ยนเวลาของระบบเช่น[timezoneinfo]::localเคยสร้างข้อมูลเดียวกันเสมอแม้หลังจากที่คุณทำการเปลี่ยนแปลงระบบ นี่เป็นวิธีเดียวที่ฉันสามารถตรวจสอบและบันทึกการเปลี่ยนแปลงในเขตเวลา:

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

หมายความว่าฉันต้องเปิดเซสชันPowerShellใหม่เพื่อโหลดตัวแปรระบบอีกครั้ง

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