Как записать вывод в переменную из внешнего процесса в 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

JNK
источник
Я пытался присвоить его переменной, используя «=», но я не пытался сначала направить вывод в Out-String. Я попробую.
Адам Бертрам
10
Я не понимаю, что здесь происходит, и не могу заставить его работать. Является ли «Shell» ключевым словом powershell? То есть мы на самом деле не используем командлет Start-Process? Можете ли вы привести конкретный пример, пожалуйста (то есть заменить «Shell» и / или «команда» реальным примером).
deadlydog
@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[]], элементами которого являются strings ( [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ссылок на переменные окружения -style, таких как %PATH%.

      • Обратите внимание, что, так как вы объединяете stdout и stderr в источнике с помощью этого подхода, вы не сможете различить строки, происходящие из stdout и stderr, в PowerShell; если вам нужно это различие, используйте 2>&1перенаправление PowerShell - см. ниже.

    • Используйте перенаправление PowerShell, 2>&1 чтобы узнать, какие строки пришли из какого потока :

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

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

      • При выводе на консоль , строки , как правило , приходят первые в выходном массиве, а затем записи об ошибках (по крайней мере среди партии стандартного вывода / вывода STDERR линии «в то же время»), но, к счастью, когда вы захватить выход , оно правильно чередуется , используя тот же порядок вывода, что и без него 2>&1; другими словами: при выводе на консоль захваченный вывод НЕ отражает порядок, в котором строки stdout и stderr были сгенерированы внешней командой.

      • Если вы захватите весь вывод в одну строку с помощьюOut-String , PowerShell добавит дополнительные строки , потому что строковое представление записи об ошибке содержит дополнительную информацию, такую ​​как location ( At line:...) и category ( + 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 {
    $_
  }
}
mklement0
источник
В конечном итоге это сработало для меня после того, как я выбрал свой путь к исполняемому файлу И мои аргументы для него, бросил их в строку и обработал это как свою <команду>.
Дан
2
@Dan: Когда PowerShell сам интерпретирует <command>, вы не должны объединять исполняемый файл и его аргументы в одну строку; с помощью вызова через cmd /cвас вы можете сделать это, и это зависит от ситуации, имеет ли это смысл или нет. На какой сценарий вы ссылаетесь, и можете ли вы привести минимальный пример?
mklement0
Работает: $ command = "c: \ mycommand.exe" + $ Args ..... $ output = cmd / c $ command '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
manojlds
источник
4
Что значит 2> & 1? 'запустить команду с именем 2 и поместить ее вывод в команду запуска с именем 1'?
Ричард
7
Это означает «перенаправить стандартный вывод ошибок (дескриптор файла 2) в то же место, куда идет стандартный вывод (дескриптор файла 1)». По сути, перенаправляет обычные сообщения и сообщения об ошибках в одно и то же место (в этом случае консоль, если stdout не перенаправлен куда-то еще - как файл).
Джованни Тирлони
11

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

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

$scriptOutput
Finzzownt
источник
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-файл, но не параметры!

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

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

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

Davidiam
источник
8

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

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

$rawOutput = (cmd /c <command> 2`>`&1)
sevenforce
источник
2

Я получил следующее на работу:

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

$ результат дает вам необходимое

Фэй Смелтер
источник
1

Эта вещь работала для меня:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)
Алекс Р.
источник
0

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

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

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

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

Келли Дэвис
источник