Потоковый Вывод PowerShell
Я хотел бы захватить некоторые потоковые выходные данные в PowerShell. Например
cmd /c "echo hi && foo"
Эта команда должна вывести hi, а затем bomb. Я знаю, что могу использовать-ErrorVariable:
Invoke-Command { cmd /c "echo hi && foo" } -ErrorVariable ev
Однако есть проблема: в случае длительно выполняющихся команд я хочу передать поток вывода, а не захватить его и получить только вывод stderr/stdout в конце команды
В идеале, я хотел бы иметь возможность разделить stderr и stdout и pipe на два разных потока - и pipe stdout обратно звонящему, но будьте готовы бросить stderr в случае ошибки. Что-то вроде
$stdErr
Invoke-Command "cmd" "/c `"echo hi && foo`"" `
-OutStream (Get-Command Write-Output) `
-ErrorAction {
$stdErr += "`n$_"
Write-Error $_
}
if ($lastexitcode -ne 0) { throw $stdErr}
Самое близкое, что я могу получить, - это использование конвейера, но это не позволяет мне различать stdout и stderr, поэтому я в конечном итоге бросаю весь выходной поток
function Invoke-Cmd {
<#
.SYNOPSIS
Executes a command using cmd /c, throws on errors.
#>
param([string]$Cmd)
)
$out = New-Object System.Text.StringBuilder
# I need the 2>&1 to capture stderr at all
cmd /c $Cmd '2>&1' |% {
$out.AppendLine($_) | Out-Null
$_
}
if ($lastexitcode -ne 0) {
# I really just want to include the error stream here
throw "An error occurred running the command:`n$($out.ToString())"
}
}
Общее использование:
Invoke-Cmd "GitVersion.exe" | ConvertFrom-Json
Обратите внимание, что аналогичная версия, которая просто использует ScriptBlock (и проверяет выходной поток на наличие [ErrorRecord]s), неприемлема, потому что есть много программ, которым "не нравится" выполнение непосредственно из процесса PowerShell
Система .NET.Диагностика.Process API позволяет мне это сделать...но я не могу потоковый вывод из обработчиков потока (из-за потоковой обработки и блокировки - хотя я думаю, что мог бы использовать цикл while и поток / очистить собранный вывод по мере его поступления)
2 ответов:
Примечание: обновленный пример ниже должен теперь работать на всех узлах PowerShell. проблема GitHub непоследовательная обработка собственной команды stderr была открыта для отслеживания несоответствия в предыдущем примере. Обратите внимание, однако, что, поскольку это зависит от недокументированного поведения, поведение может измениться в будущем. Примите это во внимание, прежде чем использовать его в решении, которое должно быть долговечным.
Вы на правильном пути с использованием труб, вероятно, нет нужно вызывать-командовать, почти всегда. Powershell делает различие между stdout и stderr. Попробуйте это, например:
cmd /c "echo hi && foo" | set-variable output
stdoutпередается в set-variable, в то время как ошибка std все еще появляется на экране. Если вы хотите скрыть и захватить выходные данныеstderr, попробуйте сделать следующее:cmd /c "echo hi && foo" 2>$null | set-variable outputЧасть
2>$nullявляется недокументированным трюком, который приводит к тому, что вывод ошибки добавляется к переменной PowerShell$Errorв видеErrorRecord.Вот пример, который отображает stdout, в то время как ловушка
stderrс блокомcatch:function test-cmd { [CmdletBinding()] param() $ErrorActionPreference = "stop" try { cmd /c foo 2>$null } catch { $errorMessage = $_.TargetObject Write-warning "`"cmd /c foo`" stderr: $errorMessage" Format-list -InputObject $_ -Force | Out-String | Write-Debug } } test-cmdГенерирует сообщение:
WARNING: "cmd /c foo" stderr: 'foo' is not recognized as an internal or external commandЕсли вы вызываете с включенным отладочным выводом, вы также увидите подробности брошенной записи ErrorRecord:
DEBUG: Exception : System.Management.Automation.RemoteException: 'foo' is not recognized as an internal or external command, TargetObject : 'foo' is not recognized as an internal or external command, CategoryInfo : NotSpecified: ('foo' is not re...ternal command,:String) [], RemoteException FullyQualifiedErrorId : NativeCommandError ErrorDetails : InvocationInfo : System.Management.Automation.InvocationInfo ScriptStackTrace : at test-cmd, <No file>: line 7 at <ScriptBlock>, <No file>: line 1 PipelineIterationInfo : {} PSMessageDetails :Настройка
$ErrorActionPreference="stop"заставляет PowerShell выдавать исключение, когда дочерний процесс записывает в stderr, что звучит так, как будто это ядро того, что вы хотите. Этот трюк2>$nullделает поведение командлета и внешней команды очень похожим.
- описанное поведение применимо к запуску PowerShell вобычных консольных/терминальных окнах безудаленного взаимодействия . с удалением и в ISE поведение отличается от PSv5.1 - см. Внизу.
- Тот самый2>$nullповедение, на которое опирается ответ Берта -2>$nullтайно все еще пишет в поток ошибок PowerShell и, следовательно, с$ErrorActionPreferenceStopв сущности, прерывание сценария, как только внешняя утилита записывает что - либо в stderr - было классифицировано как ошибка и, вероятно, исчезнет.
Когда PowerShell вызывает внешнюю утилиту, такую как
cmd, ее вывод stderr по умолчанию передается напрямую. То есть вывод stderr выводится прямо на консоль, не будучи включенным в захваченный вывод (будь то присвоение переменной или перенаправление в файл).В то время как вы можете использовать
2>&1как часть командной строкиcmd, вы не сможете отличить вывод stdout от вывода stderr в PowerShell.Напротив, Если вы используете
2>&1в качестве перенаправления PowerShell , вы можете фильтровать поток успеха на основе типа входных объектов :
- A
[string]экземпляр - это stdout line- экземпляр
[System.Management.Automation.ErrorRecord]- это строкаstderr .Следующая функция,
Invoke-CommandLine, пользуется этим:
- Обратите внимание, что часть
cmd /cне встроена, поэтому вы можете вызвать ее следующим образом, например:Invoke-CommandLine 'cmd /c "echo hi && foo"'Нет никакой фундаментальной разницы между передачей вызова командной строки
cmdи прямым вызовом внешней утилиты, такой какgit.exe, но обратите внимание, что только вызов черезcmdпозволяет использовать несколько команд через операторы&,&&, и||, и что толькоcmdинтерпретирует%...%-ссылки на переменные окружения в стиле , если только не используется--%, символ остановки синтаксического анализа.
Invoke-CommandLineвыводит строки stdout и stderr по мере их получения, так что вы можете использовать функцию в конвейере.
Как записано, строки stderr записываются в поток ошибок PowerShell с использованием
Write-Errorпо мере их получения, при этом одно общее исключение выбрасывается после завершения внешней команды, если она сообщает о ненулевое значение$LASTEXITCODE.Легко адаптировать функцию:
- принять меры после получения первой строки stderr.
- to собрать все строки stderr в одной переменной
- и/или, после завершения, принять меры, если любой вход stderr был получен, даже с
$LASTEXITCODEсообщением0.
Invoke-CommandLineиспользуетInvoke-Expression, поэтому применяется обычная оговорка: убедитесь, что вы знаете, какую командную строку вы используете. мимоходом, потому что оно будет исполнено как есть, независимо от того, что в нем содержится.
function Invoke-CommandLine { <# .SYNOPSIS Executes an external utility with stderr output sent to PowerShell's error ' stream, and an exception thrown if the utility reports a nonzero exit code. #> param([parameter(Mandatory)][string] $CommandLine) # Note that using . { ... } is required around the Invoke-Expression # call to ensure that the 2>&1 redirection works as intended. . { Invoke-Expression $CommandLine } 2>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { # stderr line Write-Error $_ # send stderr line to PowerShell's error stream } else { # stdout line $_ # pass stdout line through } } # If the command line signaled failure, throw an exception. if ($LASTEXITCODE) { Throw "Command failed with exit code ${LASTEXITCODE}: $CommandLine" } }
Необязательное чтение: как вызовы внешних утилит вписываются в обработку ошибок PowerShell
текущая версия: Windows PowerShell v5. 1, PowerShell Core v6-beta.2
Значение переменной предпочтения
$ErrorActionPreferenceуправляет только реакцией на ошибки и исключения .NET, возникающие при вызовах командлетов/функций PowerShell или выражения.
Try/Catchпредназначен для перехвата завершающих ошибок PowerShell и исключений .NET.в обычном окне консоли без удаленного взаимодействия внешние утилиты, такие как
cmdв настоящее время никогда не генерируют либо ошибку - все, что они делают, это сообщают код выхода , который PowerShell отражает в автоматической переменной$LASTEXITCODE, и автоматическая переменная$?отражает$False, если код выхода ненулевой.
Примечание: тот факт , что поведение принципиально отличается в хостах, отличных от консольного хоста-который включает в себя Windows ISE и когда задействовано удаленное взаимодействие-проблематично : там вызовы внешних утилит приводят к выводу stderr, обрабатываемому так, как если бы не завершающие ошибки были сообщены ; в частности:
- каждая выходная линия stderr выводится как запись об ошибке, а также записывается в автоматическую коллекцию
$Error.- в дополнении к
$?будучи установленным в$falseс ненулевым кодом выхода, наличие любого вывода stderr также устанавливает его в$False.- это поведение проблематично, поскольку вывод stderr сам по себе не обязательно указывает на ошибку - только ненулевой код выхода .
- Берт создал проблему в Репозиторий PowerShell GitHub для обсуждения этого несоответствия .
по умолчанию, вывод stderr, генерируемый внешней утилитой, передается прямо на консоль. - они не захвачены назначениями переменных PowerShell или перенаправлениями вывода (success-stream).
Как обсуждалось выше, это можно изменить:
2>&1как часть командной строки, передаваемой вcmdотправляет stdout и stderr вместе в поток успеха PowerShell, как строки, без возможности отличить, была ли данная строка stdout или stderr.
2>&1как перенаправление PowerShell также отправляет строки stderr в поток успеха PowerShell, но можно различать строки stdout - и stderr-происхождения по их типу данных:[string]-типизированная строка является строкой stdout-origined, тогда как типизированная строка[System.Management.Automation.ErrorRecord]является строкой, порожденной stderr.
Comments