为什么我不能从PowerShell.Streams.Error.add_DataAdded中写入错误?

时间:2014-12-02 16:26:03

标签: powershell powershell-v4.0 runspace

我正在研究使用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()

如果还有另一种方式,我愿意接受它!

3 个答案:

答案 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() }