PowerShell: как «Receive-Job» детально извлекает выходные данные из блока кода задания? - PullRequest
0 голосов
/ 25 июня 2018

Пожалуйста, ознакомьтесь с этим тестовым сценарием и сделанными мною заключениями о том, как работает приемная работа.
У меня все еще есть проблемы, чтобы выяснить, как точно «Receive-Job» извлекает потоки из блока кода.

<# .SYNOPSIS Test the console output and variable capturing of Write- cmdlet calls in a code block used by 'Start-Job'
   .NOTES
    .NET Version                   4.7.2
    PSVersion                      5.1.16299.431
    PSEdition                      Desktop
    PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
    BuildVersion                   10.0.16299.431
    CLRVersion                     4.0.30319.42000
    WSManStackVersion              3.0
    PSRemotingProtocolVersion      2.3
    SerializationVersion           1.1.0.1
#>

Set-StrictMode -Version latest

if ($host.Name -inotmatch 'consolehost') { Clear-Host }

$errorBuffer = $null
$warningBuffer = $null
$outBuffer = $null
$infoBuffer = $null

# Start the job
$job = Start-Job -ScriptBlock {

    Set-StrictMode -Version latest

PowerShell запускает этот блок скрипта в своем собственном процессе, как если бы он запускал внешний исполняемый файл.
Therfore PowerShell может отображать только stdout / success и stderr / error из кодового блока в потоки PowerShell success (1) и error (2) в процессе сценария.
Эти два потока будут переданы Receive-Job и могут быть перенаправлены в строке Receive-Job, как и ожидалось.
И эти два потока могут быть сохранены в переменные по запросу Receive-Job. (-OutVariable -ErrorVariable)
Кроме того, Receive-Job может передавать информацию о потоках PowerShell (поток 6) и предупреждение (поток 3), а также сохранять их в переменных. (-WarningVariable -InformationVariable)
Но сохранение этих потоков в переменных не является перенаправлением.
Каждый вызов командлета Write может отображать сообщение на консоли независимо от параметров -variable.
Видимое сообщение на консоли зависит только от собственных настроек Write-командлета и возможного перенаправления в вызове Write-cmdlet.

    # This will, by default, output to the console over stream 6 (info), and always get captured in $infoBuffer.
    Write-Host "***WRITE_HOST***"           # 6> $null # Supresses the output to the console.

    # This will not output to the console over stream 6 (info) by default, but always get captured in $infoBuffer.
    $InformationPreference = 'Continue'     # Outputs to the console, default is 'SilentlyContinue'.
    Write-Information "***INFO***"          # 6> $null # Supresses the output to the console for preference 'Continue'.
    $InformationPreference = "SilentlyContinue"

    # This will not output to the console over stream 5 (debug) by default, and can't get captured in a variable.
    $DebugPreference = 'Continue'           # Outputs to the console, default is 'SilentlyContinue'.
    Write-Debug "***DEBUG***"               # 5> $null  # Supresses the output to the console for preference 'Continue'.
    $DebugPreference = "SilentlyContinue"

    # This will not output to the console over stream 4 (verbose), by default, and can't get captured in a variable.
    $VerbosePreference = 'Continue'         # Outputs to the console, default is 'SilentlyContinue'.
    Write-Verbose "***Verbose***"           # 4> $null  # Supresses the output to the console for preference 'Continue'.
    $VerbosePreference = 'SilentlyContinue'

    # This will, by default, output to the console over stream 3 (warning), but get captured in $warningBuffer only for
    # preference 'Continue'.
    #$WarningPreference = 'SilentlyContinue'   # Supresses console output AND variable capturing, default is 'Continue'.
    Write-Warning "***WARNING***"              # 3> $null  # Supresses the warning output to the console for preference
    #$WarningPreference = 'Continue'                       # 'Continue'.

    # This will output to the console over stream 2 (error), and always get captured in $errorBuffer, if not redirected
    # in the code block.
    # For 'Receive-Job -ErrorAction Stop' it would raise an execption, the content in $errorBuffer is quite useless then.
    Write-Error '***ERROR***'   # 2> $null # Supresses the output AND variable capturing, but you can supress/redirect
                                           # this stream in the 'Receive-Job' line without breaking the variable
                                           # capturing: 'Receive-Job ... -ErrorVariable errorBuffer 2> $null'

    # These will output to the console over stream 1 (success), and always get captured in $result and $outBuffer, if
    # not redirected in the code block.
    Write-Output '***OUTPUT***'  # 1> $null # Supresses the output AND variable capturing, but you can supress/redirect
    Write-Output '***NEXT_OUTPUT***'        # this stream in the 'Receive-Job' line without breaking the variable
    "***DIRECT_OUT***"                      # capturing: '$result = Receive-Job ... -OutVariable outBuffer 1> $null'
}

# Wait for the job to finish
Wait-Job -Job $job

try
{
    # Working only outside the code block, this is a workaround for catching ALL output.
    #$oldOut = [Console]::Out
    #$stringWriter = New-Object IO.StringWriter
    #[Console]::SetOut($stringWriter)

    # Pull the buffers from the code block
    $result = Receive-Job <#-ErrorAction Stop#> `
                          -Job $job `
                          -ErrorVariable       errorBuffer `
                          -WarningVariable     warningBuffer `
                          -OutVariable         outBuffer `
                          -InformationVariable infoBuffer `
                          # 1> $null #2> $null  # Only the success and error streams can be redirected here, other
                                                # streams are not available.

    # Restore the console
    #[Console]::SetOut($oldOut)

    # Get all catched output
    #$outputOfAllWriteFunctions = $stringWriter.ToString()
}
catch
{
    Write-Host "EXCEPTION: $_" -ForegroundColor Red
}
finally
{
    Write-Host "error: $errorBuffer"
    Write-Host "warning: $warningBuffer"
    Write-Host "out: $outBuffer"
    Write-Host "info: $infoBuffer"
    Write-Host "result: $result"
    #Write-Host "`noutputOfAllWriteFunctions:`n";Write-Host "$outputOfAllWriteFunctions" -ForegroundColor Cyan

    Remove-Job -Job $job
}

Мои окончательные выводы:

Поскольку блок кода Start-Job выполняется в своем собственном процессе, он не может напрямую записывать в консоль процесса сценариев.
Кодовый блок оборачивается механизмом захвата, который захватывает все 6 потоков PS в буферах.
Вызов Receive-Job использует межпроцессное взаимодействие для получения всех этих потоков.
Receive-Job проходит через потоки 1 и 2 и выводит их на свой собственный выход и поэтому доступен для перенаправления.
Receive-Job использует Write-Error для записи потока 2 на консоль, и поэтому Receive-Job вызовет исключение для параметра -ErrorAction Stop.
Затем Write-Error использует Console.Out.WriteLine() для записи на консоль красным цветом.
Затем Receive-Job проверяет хранение переменных и сохраняет поток 1 (успех), 2 (ошибка), 3 (предупреждение) и 6 (информация).
Наконец, Receive-Job использует Console.Out.WriteLine() для записи потока 1, 3, 4, 5 и 6 с различными ForegroundColors в консоль.
Вот почему вы можете захватить ВСЕ эти 6 потоковых выходных данных с помощью Console.SetOut(), даже вывод потока ошибок, для которого я ожидал, потребуется Console.SetError().

Но в этих выводах есть проблема:

Вывод Write-Host записывается в консоль по умолчанию, а его вывод добавляется в информационную переменную.
Так что Write-Host может просто написать в поток 6.
Но вывод Write-Information не отображается на консоли по умолчанию, но также добавляется в информационную переменную.
Так что Write-Information не может просто использовать один и тот же канал IPC с Write-Host.
И Write-Warning может записывать в консоль и переменную независимо, поэтому здесь нельзя использовать только один поток / канал.
Посмотрите на мою диаграмму по этому вопросу.

Выходная транспортная схема 'Receive-Job':

Вы можете проверить диаграмму, перенаправив поток 1-6 в блок кода и поток 1 или 2 в сценарии.

|<-------- code block process -------->|<-- IPC -->|<-------------------- script process ------------------->|
Method              Preference   Stream                 Stream/Variable           Console output

Write-Out           *        --> 1      --> PIPE 1  --> 1                     --> Console.Out.Write(gray)
                                            PIPE 1  --> Out Variable
Write-Error         *        --> 2      --> PIPE 2  --> 2                     --> Console.Out.Write(red)
                                            PIPE 2  --> Error Variable
Write-Warning       Continue ----??????---> PIPE 3  --> Warning Variable
Write-Warning       Continue --> 3      --> PIPE 4                            --> Console.Out.Write(yellow)
Write-Verbose       Continue --> 4      --> PIPE 4                            --> Console.Out.Write(yellow)
Write-Debug         Continue --> 5      --> PIPE 4                            --> Console.Out.Write(yellow)
Write-Information   Continue --> 6      --> PIPE 6                            --> Console.Out.Write(gray)
Write-Information   *        ----??????---> PIPE 5  --> Information Variable
Write-Host          *        ----??????---> PIPE 5  --> Information Variable
Write-Host          *        --> 6      --> PIPE 6                            --> Console.Out.Write(gray)

IPC : Inter Process Communication
*   : always, independent from Preference or has no own Preference

Нет перенаправления, которое вы можете добавить после Write-Information или Write-Warning, чтобы предотвратить сохранение в их переменных.
Если вы перенаправите 3 и 6 после методов, то это повлияет только на вывод консоли, а не на сохранение переменной.
Только если для $InformationPreference (не по умолчанию) или $WarningPreference (по умолчанию) задано значение Продолжить, они записывают в поток 6 или 3, которые всегда записываются серым или желтым цветом в консоль процесса сценария.
И только Write-Warning нужно хранить в своей переменной предпочтение Continue, Write-Informations всегда записывает в свою переменную.

Вопрос:

  • Как «Write-Warning» и «Write-Information» могут передавать свои выходные данные назначенным им переменным в процессе сценария?
    (Они не могут использовать поток 7,8,9, поскольку они не существуют в Windows.)

Лучшая практика:

После вызова Job-Start вам нужно Start-Sleep 1-3 секунды, чтобы дать время блоку кода начать или выйти из строя.
Затем используйте Receive-Job в первый раз, чтобы получить текущий прогресс, запустить отладочную информацию, предупреждение или ошибки.
Не следует использовать Wait-Job, но используйте свой собственный цикл, чтобы проверить состояние выполнения задания и самостоятельно проверить время ожидания.
В этом собственном цикле ожидания вы вызываете Receive-Job каждые X секунд, чтобы получить информацию о прогрессе, отладке и ошибках из процесса блока кода.
Когда состояние задания finished или failed, вы вызываете Receive-Job в последний раз, чтобы получить оставшиеся данные всех буферов.

Для перенаправления / захвата потока 1 (успех) и 2 (ошибка) вы можете использовать обычное перенаправление в строке Receive-Job или сохранение в переменные.
Для захвата потока 3 (предупреждение) и 6 (информация & Write-Host) вы должны использовать переменную хранения.
Вы не можете перенаправить или перехватить поток 4 (подробный) или 5 (отладка) напрямую, но вы можете перенаправить (4>&1 or 5>&1) эти потоки в блоке кода на поток 1 (успех), чтобы добавить их в выходную переменную.

Чтобы подавить вывод консоли Write-Output или Write-Error, вы можете просто перенаправить поток 1 или 2 в строку Receive-Job.
Вам не нужно подавлять вывод консоли на Write-Information, Write-Verbose или Write-Debug, поскольку они не записывают на консоль свои предпочтения по умолчанию.
Если вы хотите захватить вывод Write-Information в назначенной переменной без консольного вывода, вам нужно перенаправить поток 6: Write-Information <message> 6>$null.
Чтобы подавить вывод консоли Write-Warning или Write-Host, необходимо перенаправить поток 3 или 6 в их строки вызова: Write-Warning <message> 3>$null и Write-Host <message> 6>$null.

Знайте:

Если вы перенаправите поток success (1) или error (2) в блок кода, они не будут переданы процессу сценария, не записаны в консоль и не будут сохранены в выходной переменной или переменной ошибки.

...