Как записать вывод в переменную из внешнего процесса в PowerShell? - PullRequest
131 голосов
/ 11 ноября 2011

Я бы хотел запустить внешний процесс и записать вывод его команды в переменную PowerShell. Я сейчас использую это:

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

Я подтвердил, что команда выполняется, но мне нужно записать вывод в переменную. Это означает, что я не могу использовать -RedirectOutput, потому что это только перенаправляет в файл.

Ответы [ 7 ]

130 голосов
/ 11 ноября 2011

Вы пробовали:

$OutputVariable = (Shell command) | Out-String

126 голосов
/ 14 марта 2016

Примечание: в команде, о которой идет речь, используется 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> является командлетом или расширенной функцией, вы можетеse общий параметр
-OutVariable / -ov
:

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

Обратите внимание, что при -OutVariable, в отличие от других сценариев, $cmdOutput всегда равен a collection , даже если выводится только один объект.В частности, возвращается экземпляр типа массива [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, только когда-либо возвращают строки через их поток успеха (стандартный вывод).

  • Если выходные данные содержат более 1 строки , PowerShell byпо умолчанию разбивает его на массив строк .Точнее, выходные строки хранятся в массиве типа [System.Object[]], элементами которого являются строки ([System.String]).

  • Если вы хотите, чтобы вывод был один , потенциально многолинейный строка , труба к Out-String:
    $cmdOutput = <command> | Out-String

  • Перенаправление stderr на стандартный вывод с помощью 2>&1, чтобы также захватить его как часть потока успеха, поставляется с предостережениями :

    • Чтобы 2>&1 объединить stdout и stderr в источнике , let 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]),не строки, поэтому выходной массив может содержать mix из строк (каждая строка представляет строку вывода) и записей ошибок (каждая запись представляетстрока stderr) .Обратите внимание, что по запросу 2>&1 и строки, и записи об ошибках принимаются через поток вывода PowerShell success ).

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

      • При выводе на консоль строки обычно приходят сначала в выходной массив, за которыми следуют записи об ошибках (по крайней мере, из пакета 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
          (В качестве бонуса, если выходные данные не захватываются, это создает правильно чередующиеся выходные данные даже при печати наconsole.)
      • Либо фильтрует записи ошибок out и отправляет их в поток ошибок PowerShell с помощью Write-Error (какбонус, если выходные данные не фиксируются, это приводит к правильному чередующемуся выводу даже при печати на консоль):

$cmdOutput = <command> 2>&1 | ForEach-Object {
  if ($_ -is [System.Management.Automation.ErrorRecord]) {
    Write-Error $_
  } else {
    $_
  }
}
20 голосов
/ 18 ноября 2011

Если вы также хотите перенаправить вывод ошибки, вам нужно сделать:

$cmdOutput = command 2>&1

Или, если в имени программы есть пробелы:

$cmdOutput = & "command with spaces" 2>&1
11 голосов
/ 07 марта 2013

Или попробуйте это. Он захватит вывод в переменную $ scriptOutput:

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

$scriptOutput
10 голосов
/ 01 сентября 2014

Другой реальный пример:

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

Обратите внимание, что в этом примере содержится путь (который начинается с переменной среды).Обратите внимание, что кавычки должны окружать путь и EXE-файл, но не параметры!

Примечание: Не забывайте символ & перед командой, но за пределамикавычки.

Вывод ошибок также собирается.

Мне потребовалось некоторое время, чтобы заставить эту комбинацию работать, поэтому я подумал, что поделюсь ею.

3 голосов
/ 06 ноября 2015

Я попробовал ответы, но в моем случае я не получил необработанный вывод.Вместо этого он был преобразован в исключение PowerShell.

Необработанный результат, с которым я получил:

$rawOutput = (cmd /c <command> 2`>`&1)
0 голосов
/ 28 июля 2016

Если все, что вы пытаетесь сделать, это захватить вывод команды, тогда это будет хорошо работать.

Я использую его для изменения системного времени, так как [timezoneinfo]::local всегда выдает одну и ту же информацию, даже после , когда вы внесли изменения в систему. Это единственный способ проверить и зарегистрировать изменения в часовом поясе:

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

Это означает, что мне нужно открыть новый PowerShell сеанс для перезагрузки системных переменных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...