Як я можу захопити вихід у змінну із зовнішнього процесу в 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. Я спробую.
Адам Бертрам

10
Я не розумію, що відбувається тут, і не можу змусити його працювати. Чи є ключовим словом "Shell" ключове слово? Тож ми насправді не використовуємо командлет Start-Process? Чи можете ви надати конкретний приклад, будь ласка (тобто замініть "Оболонку" та / або "команду" реальним прикладом).
смертельної собаки

@deadlydog Замініть Shell Commandтим, що ви хочете запустити. Це так просто.
JNK

1
@stej, ти маєш рацію. Я в основному уточнював, що код у вашому коментарі відрізнявся функцією від коду у відповіді. Початківців, як я, можна відкинути за допомогою тонких відмінностей у поведінці, як ці!
Сем

1
@Atique Я зіткнувся з тим же номером. Виявляється, ffmpeg іноді запише в stderr замість stdout, якщо, наприклад, ви використовуєте -iпараметр, не вказуючи вихідний файл. 2>&1Рішення буде перенаправлено на результат, як описано в деяких інших відповідях.
jmbpiano

159

Примітка. Команда у запитанні використовує Start-Process, що запобігає прямому захопленню результатів цільової програми. Як правило, не використовуйте Start-Processконсольні програми синхронно - просто викликайте їх безпосередньо , як у будь-якій оболонці. Це підтримує додаток підключеним до стандартних потоків викликової консолі, дозволяючи фіксувати його вихід простим призначенням $output = netdom ..., як детально описано нижче.

По суті , захоплення виводу з зовнішніх утиліт працює так само, як і з нативними командами PowerShell (можливо, вам потрібно оновити, як виконувати зовнішні інструменти ):

$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>це командлет або розширені функції, ви можете використовувати загальний параметр
-OutVariable/-ov
:

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

Зверніть увагу , що з -OutVariable, в відміну від інших сценаріїв, $cmdOutputце завжди колекція , навіть якщо тільки один об'єкт виводиться. Зокрема, [System.Collections.ArrayList]повертається екземпляр типу масиву .
Дивіться цей випуск GitHub для обговорення цієї невідповідності.


Щоб захопити вихід з декількох команд , використовуйте або субекспресію ( $(...)), або викликайте блок сценарію ( { ... }) з &або .:

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

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

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

Зауважте, що загальна потреба префіксувати &(оператор виклику) окрему команду, ім'я / шлях якої є цитований - наприклад,$cmdOutput = & 'netdom.exe' ...- не пов'язана із зовнішніми програмами такому (церівній мірі відноситьсядо скриптів PowerShell), але синтаксис вимога : PowerShell аналізує оператор, якийза замовчуваннямпочинається з цитованого рядка в режимі виразів , тоді як режим аргументів потрібен для виклику команд (командлети, зовнішні програми, функції, псевдоніми), що ізабезпечуєте, що.&

Ключова відмінність між $(...)та & { ... }/ . { ... }полягає в тому, що перший збирає весь вхід у пам'ять перед поверненням у цілому, тоді як другий передає вихід, придатний для обробки конвеєра по одному.


Перенаправлення також принципово працюють (але див. Застереження нижче):

$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 , щоб також захопити його як частину потоку успіху, поставляється із застереженнями :

    • Щоб зробити 2>&1злиття stdout та 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 та stderr. в PowerShell; якщо вам потрібна ця відмінність, використовуйте власне 2>&1перенаправлення PowerShell - див. нижче.

    • Використовуйте PowerShell 2>&1 перенаправлення щоб дізнатися, які рядки надходили з якого потоку :

      • Stderr вихід захоплюється в якості записів про помилки ( [System.Management.Automation.ErrorRecord]), а не рядки, так що вихідний масив може містити суміш з рядків (кожен рядок , що представляє собою стандартний висновок рядка) і записи про помилки (кожен запис , що представляє Stderr лінію) . Слід зазначити, що в відповідно до проханням 2>&1, як рядки і приймаються через PowerShell в запису про помилки успіху вихідний потік).

      • У консолі записи про помилки друкуються червоним кольором , а 1-й за замовчуванням створює багаторядковий дисплей у тому ж форматі, що відображатиметься непомітна помилка cmdlet; наступні записи про помилки друкуються також червоним кольором, але друкують лише повідомлення про помилку в одному рядку .

      • Під час виведення на консоль рядки зазвичай надходять спочатку у вихідний масив, після чого записуються помилки (принаймні серед партії рядків stdout / stderr, що виводяться "в той же час"), але, на щастя, коли ви фіксуєте вихід , він правильно переплетений , використовуючи той самий порядок виводу, який ви отримали б без 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
          (Як бонус, якщо вихід не захоплений, це дає належний переплетений вихід навіть під час друку на консоль.)
      • Як альтернативи, фільтрувати записи про помилки Out і відправити їх в потік помилок PowerShell зWrite-Error ( в якості бонусу, якщо вихід не враховуються, це призводить до правильно перемежовується вихід навіть при друку на консоль):

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

Зрештою, це спрацювало для мене після того, як я взяв свій виконуваний шлях І мої аргументи за це, кинув їх у рядок і розглядав це як мій <command>.
Ден

2
@Dan: Коли PowerShell сам інтерпретує <command>, ви не повинні поєднувати виконуваний файл та його аргументи в одну рядок; з викликом через cmd /cвас може це зробити, і це залежить від ситуації, має він сенс чи ні. На який сценарій ви звертаєтесь, і чи можете ви навести мінімальний приклад?
mklement0

Роботи: $ Команда = "C: \ mycommand.exe" + $ Args ..... $ вихід = CMD / с $ команда '2> & 1'
Dan

1
@Dan: Так, це працює, хоча вам не потрібна проміжна змінна та явна побудова рядка з +оператором; також працює наступне: cmd /c c:\mycommand.exe $Args '2>&1'- PowerShell піклується про передачу елементів $Argsяк пробілу, розділеного пробілом, у цьому випадку - функції, що називається бризки .
mklement0

Нарешті, відповідна відповідь, яка працює в PS6.1 +. Секрет у соусі справді є '2>&1'частиною, а не укладеним у ()стільки сценаріїв, як правило.
not2qubit

24

Якщо ви також хочете перенаправити вихідну помилку, вам потрібно зробити:

$cmdOutput = command 2>&1

Або якщо в назві програми є пробіли:

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

4
Що означає 2> & 1? 'команда запустити 2 і поставити її в команду запуску під назвою 1'?
Річард

7
Це означає "перенаправити стандартний вихід помилок (дескриптор файлу 2) на те саме місце, де відбувається стандартний вихід (дескриптор файлу 1)". В основному, перенаправляє звичайні повідомлення та повідомлення про помилки на те саме місце (у цьому випадку консоль, якщо stdout не перенаправлено кудись інше - як файл).
Джованні Тірлоні

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 для перезавантаження системних змінних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.