Потоковый Вывод 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 и поток / очистить собранный вывод по мере его поступления)

876   2  

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 и, следовательно, с $ErrorActionPreference Stop в сущности, прерывание сценария, как только внешняя утилита записывает что - либо в 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

    Ничего не найдено.