Ответ 1
Вы пробовали:
$OutputVariable = (Shell command) | Out-String
Я хотел бы запустить внешний процесс и записать его вывод команды в переменную PowerShell. В настоящее время я использую это:
$params = "/verify $pc /domain:hosp.uhhg.org"
start-process "netdom.exe" $params -WindowStyle Hidden -Wait
Я подтвердил, что команда выполняется, но мне нужно записать вывод в переменную. Это означает, что я не могу использовать -RedirectOutput, потому что это только перенаправляет в файл.
Вы пробовали:
$OutputVariable = (Shell command) | Out-String
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() }
;$cmdOutput = <command> 2>&1 | % ToString
В качестве альтернативы, отфильтруйте записи об ошибках и отправьте их в поток ошибок PowerShell с помощью Write-Error
(в качестве бонуса, если выходные данные не перехвачены, это приводит к правильному чередующемуся выводу даже при печати на консоль):
$cmdOutput = <command> 2>&1 | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) {
Write-Error $_
} else {
$_
}
}
Если вы также хотите перенаправить вывод ошибок, вам нужно сделать:
$cmdOutput = command 2>&1
Или, если в имени программы есть пробелы:
$cmdOutput = & "command with spaces" 2>&1
Или попробуйте это. Он будет выводить вывод в переменную $scriptOutput:
& "netdom.exe" $params | Tee-Object -Variable scriptOutput | Out-Null
$scriptOutput
Еще один пример из жизни:
$result = & "$env:cust_tls_store\Tools\WDK\x64\devcon.exe" enable $strHwid 2>&1 | Out-String
Обратите внимание, что в этом примере указан путь (который начинается с переменной среды). Обратите внимание, что кавычки должны содержать путь и EXE файл, но не параметры!
Примечание: Не забывайте символ &
перед командой, но за пределами кавычек.
Вывод ошибок также собирается.
Мне потребовалось некоторое время, чтобы эта комбинация работала, поэтому я решил поделиться ею.
Я попробовал ответы, но в моем случае я не получил необработанный вывод. Вместо этого он был преобразован в исключение PowerShell.
Необработанный результат, который я получил с помощью:
$rawOutput = (cmd /c <command> 2'>'&1)
Если все, что вы пытаетесь сделать, это захватить вывод команды, то это будет хорошо работать.
Я использую его для изменения системного времени, поскольку [timezoneinfo]::local
всегда выдает одну и ту же информацию, даже после того, как вы внесли изменения в систему. Это единственный способ проверить и зарегистрировать изменения в часовом поясе:
$NewTime = (powershell.exe -command [timezoneinfo]::local)
$NewTime | Tee-Object -FilePath $strLFpath\$strLFName -Append
Это означает, что мне нужно открыть новый сеанс PowerShell, чтобы перезагрузить системные переменные.
Я пытаюсь выполнить команды 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 .....
Это сработало для меня:
$scriptOutput = (cmd /s /c $FilePath $ArgumentList)