and*_*iDo 7 powershell asynchronous io-redirection start-job
请查看此测试脚本以及我对“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
Run Code Online (Sandbox Code Playgroud)
PowerShell 在其自己的进程中启动此脚本块,就像启动外部可执行文件一样。
因此,PowerShell 只能将代码块中的 stdout/success 和 stderr/error 映射到脚本进程中 PowerShell 的成功 (1) 和错误 (2) 流。
这两个流将被传递Receive-Job
并可以Receive-Job
按预期在行中重定向。
这两个流可以根据Receive-Job
请求存储到变量中。( -OutVariable -ErrorVariable
)
此外,Receive-Job
可以捕获 PowerShell 流信息(流 6)和警告(流 3),也可以将它们存储在变量中。( -WarningVariable -InformationVariable
)
但是将这些流存储在变量中不是重定向。
Write- cmdlet 的每次调用都可以在控制台上显示一条消息,独立于 - 变量开关。
控制台上的可见消息仅取决于 Write-cmdlet 自己的首选项和 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
}
Run Code Online (Sandbox Code Playgroud)
因为代码块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()
将具有不同 ForegroundColors 的流 1、3、4、5 和 6 写入控制台。
这就是为什么您可以使用 捕获所有这 6 个流输出Console.SetOut()
,甚至是错误流输出,这是我预期Console.SetError()
需要的。
的输出Write-Host
被默认写入到控制台,并且其输出被添加到信息的变量。
所以Write-Host
也许只是写入流 6。
但Write-Information
默认情况下,输出在控制台上不可见,但也会添加到信息变量中。
所以Write-Information
不能只与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
Run Code Online (Sandbox Code Playgroud)
您无法在其后添加Write-Information
或Write-Warning
阻止存储在其变量中的重定向。
如果你在方法之后重定向 3 和 6,那么它只会影响控制台输出,而不是变量存储。
仅当$InformationPreference
(not default) 或$WarningPreference
(default) 设置为 Continue 时,它们才会写入流 6 或 3,流 6 或 3 始终以灰色或黄色写入脚本进程的控制台。
并且只Write-Warning
需要优先Continue
存储在它的变量中,Write-Informations
总是写入它的变量。
在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
or 的控制台输出Write-Error
,您可以Receive-Job
在行中重定向流 1 或 2 。
您不必抑制Write-Information
,Write-Verbose
或 的控制台输出Write-Debug
,因为它们不会使用默认首选项写入控制台。
如果要Write-Information
在没有控制台输出的情况下捕获分配变量中的输出,则必须重定向流 6: Write-Information <message> 6>$null
。
要抑制Write-Warning
or 的控制台输出Write-Host
,您必须在其调用行中重定向流 3 或 6:Write-Warning <message> 3>$null
and Write-Host <message> 6>$null
。
如果在代码块中重定向流成功 (1) 或错误 (2),它们不会被转移到脚本进程,不会写入控制台,也不会存储在输出或错误变量中。
小智 1
你的术语使用有点难以理解,但我会用我有限的经验尽力而为。
Write-Host 的输出默认写入控制台,并将其输出添加到信息变量中。
所以 Write-Host 可能只是写入流 6。
但是 Write-Information 的输出默认在控制台上不可见,但也会添加到信息变量中。
所以 Write-Information 不能只与 Write-Host 共享同一个 IPC 管道。
首先,我在某处读到(不记得了,所以无法链接,抱歉)并为自己确认了这一点,Write-Host
并且Write-Information
实际上使用了相同的流。然而,Write-Host
本质上是一种特殊情况Write-Information
,它忽略首选项变量并始终写入。因此,当相应的首选项变量设置正确时,我希望Write-Information
出现在其相应的变量中。
而且 Write-Warning 可以独立写入控制台和变量,因此这里也不能只使用一个流/管道。
这种观察可能是一种设计选择。(我在这里猜测)我希望它的工作方式与Tee-Object
cmdlet 类似,因此它确实可以写入控制台和变量,尽管只是一个流。
$result = 'some string' | Tee-Object -Variable var
Write-Host $result
Write-Host $var
# same string in both variables
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1771 次 |
最近记录: |