【问题标题】:Why can't I Write-Error from PowerShell.Streams.Error.add_DataAdded?为什么我不能从 PowerShell.Streams.Error.add_DataAdded 写入错误?
【发布时间】:2014-12-02 16:26:03
【问题描述】:

我正在研究使用运行空间进行并行运行。目前,我正在尝试从我可能运行的任何脚本中取回消息,以帮助我诊断问题,在它们起源的同一流类型中:详细流中的详细消息等。到目前为止,我可以获得警告和详细消息,但每当我尝试使用 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 中的任何一个 cmets,它会在该点锁定。谁能解释原因和/或给我一个解决方法/修复?

我追求的是这样的:

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()

如果有其他方法可以做到这一点,我愿意接受!

【问题讨论】:

  • 不太明白你在做什么或者这是否足够,但你的问题块中的Write-Host $item.Exception.Message 似乎工作
  • 我这样做是因为否则这些消息不会呈现给开发人员(即我)以帮助我调试。您提供的内容有效,但我试图将其放入错误流中,例如使用写错误。我会在问题中澄清这一点。

标签: powershell powershell-4.0 runspace


【解决方案1】:

看看这是否有帮助:

看看这是否有帮助: http://mjolinor.wordpress.com/2014/06/03/invoke-scritptasync-v2/

如果我没有正确阅读问题,那么它就是为您描述的场景而设计的 - 在运行空间环境中测试脚本并能够在执行期间将诊断信息写入各种输出流,然后在执行完成后检索该信息以及已处置的运行空间。

【讨论】:

  • 这不是我想要的。我已经从原始代码中删除了这个,以避免混淆问题。我之前没有回复,因为我仍在研究您的代码对我工作的影响。不过,我投了赞成票,因为它对我有帮助。
  • 你需要什么不做?
  • 我正在使用 WPF 进行 UI 操作,并使用 Runspace 运行后台线程以保持 UI 响应。我遇到了问题,我正在尝试将诊断信息返回到主 PowerShell 会话,以便我可以看到发生了什么问题。
  • 该函数用于将所有诊断内容写回主会话中的哈希表 ($ResultHash),因此您可以从那里进行处理。
【解决方案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

【讨论】:

    【解决方案3】:

    要侦听看起来很特殊的错误流,您必须在运行空间启动之后添加事件侦听器。这意味着您必须使用 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() }
    
    

    【讨论】:

      猜你喜欢
      • 2019-08-01
      • 2021-12-20
      • 2015-02-23
      • 1970-01-01
      • 1970-01-01
      • 2012-03-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多