powershell 通过Register-ObjectEvent触发时,写入错误未在控制台中显示错误

kb5ga3dv  于 2023-04-21  发布在  Shell
关注(0)|答案(2)|浏览(101)

我的PowerShell脚本遇到了一个问题,其中“Write-Error”函数在通过“Register-ObjectEvent”触发时未在控制台中显示错误。
以下是我的脚本:

$ScriptBlock = {
  Start-Sleep 3
  Write-Error 'sample Error'
}

$ps = [powershell]::Create()
$null = $ps.AddScript($ScriptBlock)
$handle = $ps.BeginInvoke()
$e = Register-ObjectEvent -InputObject $ps -EventName 'InvocationStateChanged' -Action {
  Write-Warning 'event fired'
  if($event.Sender.HadErrors)
  {
    foreach($ErrorItem in $event.Sender.Streams.Error)
    {
      Write-Warning ('[Error Exception] {0}' -f $ErrorItem.Exception)
      Write-Error -Exception $ErrorItem.Exception
    }
  }
}

当我运行这个脚本时,我在控制台中得到以下输出:

WARNING: event fired 
WARNING: [Error Exception] Microsoft.PowerShell.Commands.WriteErrorException: sample Error

正如您所看到的,正在执行“Write-Error”函数,但控制台中没有显示错误。
有人能帮助我理解为什么会发生这种情况,以及如何解决它吗?
感谢您的评分

uwopmtnx

uwopmtnx1#

我不能谈论设计原理,但是如何处理来自-Action脚本块的输出流**是令人惊讶的(从PowerShell 7.3.3开始编写):

  • 只有 successerroroutput streamsRegister-ObjectEvent调用返回的事件作业中被捕获。
  • 也就是说,您必须稍后使用Receive-Job按需收集这样的输出
  • 所有 * 其他 * 输出流,以及到主机的输出(Write-HostOut-Host)* 直接 * 打印到主机(控制台)。
  • 也就是说,这样的输出打印 * 马上 *,并且 * 不能被捕获 *。
  • 请注意,它立即打印的事实意味着它可能会干扰前台线程中生成的显示输出。

关于-Action脚本块的错误输出需要注意的事项:

  • 它似乎没有记录在自动变量$Error中,即会话范围的错误日志中。
  • 如果发生 terminating 错误-无论是 statement-(例如[int]::Parse('foo'))还是 script-terminating(例如throw 'Aborting')-脚本块的执行都会被 * 悄悄地 * 中止。
  • 但是,所有错误类型 * 都 * 记录在事件作业中,因此您可以稍后通过Receive-Job检查它们。

至于您尝试了变通方法
综上所述,您的Write-Warning调用的输出以正确的方式打印,而Write-Error的输出则没有。
让错误输出 also 立即打印的一个简单方法是使用2>&1重定向到成功输出流,并通过管道传输到Out-Host

Write-Error -Exception $ErrorItem.Exception 2>&1 | Out-Host

这仍然允许您稍后通过Receive-Job检查错误。
相比之下,如果你想将 success-output流发送给 * 事件作业和直接发送给主机,你需要 * 两个 * 输出操作:一次通过Out-Host,并分别到成功输出流;例如:

# First, output to the host with Out-Host, but also capture
# the output in a variable.
($out = 'to the success output stream') | Out-Host
# Second, output the captured output to the success output stream,
# so it can also be retrieved via Receive-Job later.
$out
6ljaweal

6ljaweal2#

以下可能是在PowerShell中使用PSEventJob的方法。对此有2个要求,第一个是从高级函数或脚本块中初始化事件,并将$PSCmdlet传递给事件的SessionState。第二个要求是等待任务完成,这也意味着阻止线程。
下面是一个最小可重复的示例:

function Test-Event {
    [CmdletBinding()]
    param([scriptblock] $ScriptBlock)

    & $ScriptBlock
}

Test-Event {
    $stdin  = [System.Management.Automation.PSDataCollection[object]]::new()
    $stdout = [System.Management.Automation.PSDataCollection[object]]::new()

    $ScriptBlock = {
        Start-Sleep 1
        Write-Error 'Sample Error'
        Start-Sleep 1
        'foo'
        Start-Sleep 1
        'bar'
        Start-Sleep 1
        'baz'
        Start-Sleep 1
        Write-Error 'Sample Error 2'
        Start-Sleep 1
    }

    $ps = [powershell]::Create().AddScript($ScriptBlock)
    $handle = $ps.BeginInvoke($stdin, $stdout)

    $params = @{
        InputObject      = $stdout
        EventName        = 'DataAdding'
        SourceIdentifier = 'PSInstance.DataAddingStdOut'
        Action           = { $cmdlet.WriteObject($EventArgs.ItemAdded) }
    }
    $sub = Register-ObjectEvent @params
    $sub.Module.SessionState.PSVariable.Set('cmdlet', $PSCmdlet)

    $params = @{
        InputObject      = $ps.Streams.Error
        EventName        = 'DataAdding'
        SourceIdentifier = 'PSInstance.DataAddingStdErr'
        Action           = { $cmdlet.WriteError($EventArgs.ItemAdded) }
    }
    $sub = Register-ObjectEvent @params
    $sub.Module.SessionState.PSVariable.Set('cmdlet', $PSCmdlet)

    try {
        while(-not $handle.AsyncWaitHandle.WaitOne(200)) { }
    }
    finally {
        Unregister-Event -SourceIdentifier PSInstance.DataAddingStdOut
        Unregister-Event -SourceIdentifier PSInstance.DataAddingStdErr
        $ps.Dispose()
    }
}

相关问题