2

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

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

4

3 回答 3

2

看看这是否有帮助:

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

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

于 2014-12-02T20:45:09.163 回答
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
于 2015-01-01T14:00:01.927 回答
0

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

于 2020-02-26T21:29:46.873 回答