Как записать вывод в переменную из внешнего процесса в PowerShell?

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

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

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

Ответ 1

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

$OutputVariable = (Shell command) | Out-String

Ответ 2

Note: The command in the question uses [TG40], which prevents direct capturing of the target program output. Generally, do not use [TG41] to execute console applications synchronously - just invoke them directly, as in any shell. Doing so keeps the application connected to the calling console standard streams, allowing its output to be captured by simple assignment [TG42], as detailed below.

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

$cmdOutput = <command>   # captures the command 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 на стандартный вывод с помощью 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; если вам нужно это различие, используйте собственное перенаправление PowerShell 2>&1 - см. ниже.

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

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

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

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

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

Ответ 3

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

$cmdOutput = command 2>&1

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

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

Ответ 4

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

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

$scriptOutput

Ответ 5

Еще один пример из жизни:

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

Ответ 8

Я пытаюсь выполнить команды git (и записывать вывод в переменные) в powershell script, используя информацию в этом потоке. Я могу очень легко выполнять команды, просто используя:

# Make sure we're in the correct folder
cd myGitRepo

# Fetch sha1 of a specific commit into a Powershell variable - works fine without variable assignment
git log --grep='My specific commit message here' --format=%H

# This does not work, git reports 'not a git repository'
$myVar = git log.... # git fatal: not a git repo

Похоже, что функция назначения переменных каким-то образом игнорирует текущий путь и заставляет команду выполнять в корневой папке (которая не является git репо). Знает ли кто-нибудь, почему это происходит, и как я могу это исправить?

Обновление 2: cd является псевдонимом для Set-Location, поэтому я должен получить что-то совершенно неправильное.

Обновление. Как ни странно, делая Set-Location (вместо cd), myGitRepo, похоже, исправляет проблему, то есть это работает:

Set-Location '.\myGitRepo'
$myVar = git log .....

Ответ 9

Это сработало для меня:

$scriptOutput = (cmd /s /c $FilePath $ArgumentList)