我正在研究使用Runspaces进行并行运行。目前,我正在尝试从我可能运行的任何脚本中获取消息,以帮助我诊断问题,使用它们所源自的相同流类型:详细信息流中的详细消息等。到目前为止,我可以获得警告和详细信息,但每当我尝试使用Write-Error(甚至只是访问ErrorRecord对象属性)时,控制台都会锁定。
最后有一个示例,它将展示我所看到的内容。目前,该脚本生成四条消息:
VERBOSE: This is a verbose message.
WARNING: This is a warning message.
System.Management.Automation.ErrorRecord
This is an error message.
如果取消注释$ps1.Streams.Error.add_DataAdded({})
scriptBlock中的任何一条注释,它会在此时锁定。任何人都可以解释原因和/或给我一个解决方法/解决方法吗?
我是这样的:
VERBOSE: This is a verbose message.
WARNING: This is a warning message.
C:\Work\me\PowerShell\Test-ReadPSDataStreams.ps1 : This is an error message.
At line:1 char:1
+ .\Test-ReadPSDataStreams.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-ReadPSDataStreams.ps1
这是剧本:
$VerbosePreference = 'Continue'
$is1 = [InitialSessionState]::CreateDefault()
$rs1 = [RunspaceFactory]::CreateRunspace($is1)
$rs1.ApartmentState = 'STA'
$rs1.ThreadOptions = 'ReuseThread'
$rs1.Open()
$ps1 = [PowerShell]::Create()
$ps1.Runspace = $rs1
$ps1.Streams.Verbose.add_DataAdded({
Param (
[Object]$sender,
[System.Management.Automation.DataAddedEventArgs]$e
)
foreach ($item in $sender.ReadAll()) {
Write-Verbose $item.Message
}
})
$ps1.Streams.Warning.add_DataAdded({
Param (
[Object]$sender,
[System.Management.Automation.DataAddedEventArgs]$e
)
foreach ($item in $sender.ReadAll()) {
Write-Warning $item.Message
}
})
$ps1.Streams.Error.add_DataAdded({
Param (
[Object]$sender,
[System.Management.Automation.DataAddedEventArgs]$e
)
foreach ($item in $sender.ReadAll()) {
Write-Host $item.GetType()
Write-Host $item.ToString()
#Write-Host $item.ErrorDetails.Message
#Write-Error 'test'
#Write-Error $item.ToString()
}
})
[void]$ps1.AddScript({ $VerbosePreference = 'Continue' })
[void]$ps1.AddScript({ Write-Verbose 'This is a verbose message.' })
[void]$ps1.AddScript({ Write-Warning 'This is a warning message.' })
[void]$ps1.AddScript({ Write-Error 'This is an error message.' })
$ps1.Invoke()
如果还有另一种方式,我愿意接受它!
答案 0 :(得分:2)
我在平板电脑上写这个,所以如果格式不正确,请原谅我。特别重要的是要注意:我的平板电脑键盘没有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
答案 1 :(得分:1)
看看这是否有所帮助:
看看这是否有帮助: http://mjolinor.wordpress.com/2014/06/03/invoke-scritptasync-v2/
如果我正确地阅读了这个问题,它的设计完全符合您描述的情景 - 在运行空间环境中测试脚本,并能够在执行期间将诊断信息写入各种输出流,然后在执行完成后检索该信息和处理空间。
答案 2 :(得分:0)
要侦听似乎很特殊的错误流,必须在运行空间启动后 添加事件侦听器。这意味着您必须使用BeginInvoke()
和EndInvoke()
使用异步执行。
$block = { Write-Error "Some error message" }
$ps = [powershell]::Create().AddScript($block)
$asyncHandle = $ps.BeginInvoke()
$ret.powershell.Streams.Error.add_DataAdded({
Param ( [Object]$sender, [System.Management.Automation.DataAddedEventArgs]$e )
$sender.ReadAll() | % { Write-Error $_ }
})
while(!$ps.IsCompleted) { Start-Sleep -Milliseconds 25 }
try { $ps.EndInvoke($asyncHandle) }
finally { $ps.Dispose() }