前言
我在我的平板电脑上写这个,所以如果格式不正确,请原谅我。需要特别注意的是:我的平板电脑键盘没有 PowerShell 用于其转义字符的反引号字符;因此,我在示例中将换行符序列写为“\n”。
我的解决方案
我玩弄了你的技术,这就是我观察到的:
(1) 要解决错误流未关闭的问题,请改为通过其 $Error 变量访问后台主机的错误。为此,请使用同步哈希表作为 SessionStateProxy 来提供对后台主机 $Error 的访问;请参见下面的示例。
→ $PRXY = [HashTable]::Synchronised(@{})
$rs1 = [RunspaceFactory]::CreateRunspace()
→ $rs1.SessionStateProxy.SetVariable("PRXY",$PRXY)
$rs1.Open()
$ps1 = [PowerShell]::Create().AddScript({
→ $PRXY.Error = $Error
Write-Error "This is just a test from ps1"
})
$ps1.Runspace = $rs1
一篇关于如何使用多线程同步哈希表的好文章可以在这里找到:http:
//learn-powershell.net/2013/04/19/sharing-variables-and-live-objects-between-powershell-runspaces/
其他事情
(2) 使用 Write-Error 将后台运行空间的错误发送到交互式控制台,会用 Write-Error cmdlet 的 InvocationInfo 覆盖错误的 InvocationInfo。因此,我将错误对象直接发送到控制台输出。
(3) 使用事件处理程序给我带来了问题。作为一种解决方法,我使用了 Register-ObjectEvent 并在 while 循环中使用事件轮询来捕获来自后台运行空间的消息。
(4) 使用 stream.[type].ReadAll() with: warning、verbose 和 debug 导致主机挂起,与尝试从错误流中读取的方式相同。为了解决这个问题,我将流内容发送到管道 ForEach 循环,然后调用流 Clear() 方法。
完整示例
使用两个运行空间来演示这个概念。我想再次提醒你,这篇文章是用平板电脑写的,所以不要指望下面的例子不需要先调试就可以运行。当我回到使用 ISE 的真实计算机时,我将编辑以下脚本以修复任何语法错误。
# Turn on verbose and debug messages
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
# Create background runspaces
$PRXY = [HashTable]::Synchronised(@{})
$rs = @()
For ($i = 0; $i -le 1; $i++)
{
$rs += [RunspaceFactory]::CreateRunspace()
$rs[$i].SessionStateProxy.SetVariable("PRXY", $PRXY)
$rs[$i].Open()
}
$sb1 = {
$PRXY.PS1.Error = $Error
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
For ($i = 0; $i -lt 5; $i++)
{
$msg = "Test [$i]"
Write-Error $msg
Write-Warning $msg
Write-Verbose $msg
Write-Debug $msg
Start-Sleep -Milliseconds 750
}
}
$sb2 = {
$PRXY.PS2.Error = $Error
$VerbosePreference = "Continue"
$DebugPreference = "Continue"
For ($i = 0; $i -lt 5; $i++)
{
$msg = "Test [$i]"
Write-Error $msg
Write-Warning $msg
Write-Verbose $msg
Write-Debug $msg
Start-Sleep -Milliseconds 500
}
}
$PRXY.PS1 = @{}
$ps1 = [PowerShell]::Create().AddScript($sb1)
$ps1.Runspace = $rs[0]
$PRXY.PS2 = @{}
$ps2 = [PowerShell]::Create().AddScript($sb2)
$ps2.Runspace = $rs[1]
# Map event SourceIdentifiers to the runspace that produces the event
$EventRegister = @{}
$EventRegister.Error = @{}
$EventRegister.Warning = @{}
$EventRegister.Verbose = @{}
$EvevtRegister.Debug = @{}
$Registered = @()
# Register PS1 --------------------
Register-ObjectEvent -InputObject $ps1.streams.error -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Error.Add($id, $ps1)
$Registered += $id
}
}
Register-ObjectEvent -InputObject $ps1.streams.warning -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Warning.Add($id, $ps1)
$Registered += $id
}
}
Register-ObjectEvent -InputObject $ps1.streams.verbose -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Verbose.Add($id, $ps1)
$Registered += $id
}
}
Register-ObjectEvent -InputObject $ps1.streams.debug -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Debug.Add($id, $ps1)
$Registered += $id
}
}
# Register PS2 -----------------------
Register-ObjectEvent -InputObject $ps2.streams.error -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Error.Add($id, $ps2)
$Registered += $id
}
}
Register-ObjectEvent -InputObject $ps2.streams.warning -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Warning.Add($id, $ps2)
$Registered += $id
}
}
Register-ObjectEvent -InputObject $ps2.streams.verbose -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Verbose.Add($id, $ps2)
$Registered += $id
}
}
Register-ObjectEvent -InputObject $ps2.streams.debug -EventName DataAdded
ForEach ( $id in (@(Get-EventSubscriber)).SourceIdentifier )
{
If ( $id -notin $Registered )
{
$EventRegister.Debug.Add($id, $ps2)
$Registered += $id
}
}
$hndl_ps1 = $ps1.BeginInvoke()
$hndl_ps2 = $ps2.BeginInvoke()
While ( !(hndl_ps1.IsCompleted) -or
!(hndl_ps2.IsCompleted) )
{
$Event = Wait-Event
If ( $EventRegister.Error.ContainsKey($Event.SourceIdentifier) )
{
$psid = $EventRegister.Error[$Event.SourceIdentifier].InstanceId
$stamp = "$psid::$($Event.TimeGenerated)"
Write-Error $stamp
If ( $psid -eq $ps1.InstanceId )
{
$PRXY.PS1.Error
$PRXY.PS1.Error.Clear()
}
If ( $psid -eq $ps2.InstanceId )
{
$PRXY.PS2.Error
$PRXY.PS2.Error.Clear()
}
Remove-Event -EventIdentifier $Event.EventIdentifier
Continue
}
If ( $EventRegister.Warning.ContainsKey($Event.SourceIdentifier) )
{
$stamp = "$($EventRegister.Warning[$Event.SourceIdentifier].InstanceId::"
$stamp += "$($Event.TimeGenerated)"
$EventRegister.Warning[$Event.SourceIdentifier].streams.warning |
ForEach {Write-Warning "{0}\n{1}\n\n" -f $stamp, $_}
$EventRegister.Warning[$Event.SourceIdentifier].streams.warning.Clear()
Remove-Event -EventIdentifier $Event.EventIdentifier
Continue
}
If ( $EventRegister.Verbose.ContainsKey($Event.SourceIdentifier) )
{
$stamp = "$($EventRegister.Verbose[$Event.SourceIdentifier].InstanceId)::"
$stamp += "$($Event.TimeGenerated)"
$EventRegister.Verbose[$Event.SourceIdentifier].streams.verbose |
ForEach {Write-Verbose "{0}\n{1}\n\n" -f $stamp, $_}
$EventRegister.Verbose[$Event.SourceIdentifier].streams.verbose.Clear()
Remove-Event -EventIdentifier $Event.EventIdentifier
Continue
}
If ( $EventRegister.Debug.ContainsKey($Event.SourceIdentifier) )
{
$stamp = "$($EventRegister.Debug[$Event.SourceIdentifier].InstanceId)::"
$stamp += "$($Event.TimeGenerated)"
$EventRegister.Debug[$Event.SourceIdentifier].streams.debug |
ForEach {Write-Debug "{0}\n{1}\n\n" -f $stamp, $_}
$EventRegister.Debug[$Event.SourceIdentifier].streams.debug.Clear()
Remove-Event -EventIdentifier $Event.EventIdentifier
Continue
}
}
$ps1.EndInvoke($hndl_ps1)
$ps2.EndInvoke($hndl_ps2)
# Optionally you can read the contents of all the streams after EndInvoke()
# to see if any messages were missed.
$ps1.streams.error
$ps1.streams.warning.ReadAll()
$ps1.streams.verbose.ReadAll()
$ps1.streams.debug.ReadAll()
$ps2.streams.error
$ps2.streams.warning.ReadAll()
$ps2.streams.verbose.ReadAll()
$ps2.streams.debug.ReadAll()
# Unregister subscribers if in the ISE
Get-EventSubscriber | Unregister-Event -SourceIdentifier $_.SourceIdentifier