为什么PowerShell.BeginInvoke没有调用回调?

时间:2014-12-10 16:09:23

标签: powershell callback powershell-v4.0

我无法让PowerShell调用所提供的回调:

$rs = [RunspaceFactory]::CreateRunspace()
$rs.Open()

$ps = [PowerShell]::Create() 
$ps.Runspace = $rs

$ps.AddScript( {
    Get-Service
} ) | Out-Null

$psdcInputs = New-Object Management.Automation.PSDataCollection[String]
$psdcInputs.Complete()
$psdcOutputs = New-Object Management.Automation.PSDataCollection[Object]
$psis = New-Object Management.Automation.PSInvocationSettings

$asyncCallback = {
    Param (
        [IAsyncResult]$result
    )

    Write-EventLog -LogName Application -Source Testing -EntryType Information `
        -Category 0 -EventId 1234 -Message "Test."

    $result.AsyncState.EndInvoke($result)
}

$aResult = $ps.BeginInvoke($psdcInputs, $psdcOutputs, $psis, $asyncCallback, $ps)

scriptblock次运行,$psdcOutputs包含一组ServiceController个对象,正如预期的那样。但$asyncCallback scriptblock中的代码未运行且事件未写入事件日志。我无法看清我做错了什么。你能帮忙吗?

注意:我实际上并不关心写入事件日志 - 我还想在这里做其他事情 - 但我需要从原始代码中抽象出来以使其合理化。

2 个答案:

答案 0 :(得分:1)

如果您要做的是在执行其他操作时在后台执行某些操作,然后使用PowerShell返回异步结果,则使用PowerShell。您在示例中所做的不是PowerShell。就像“我能吃芝士汉堡”不是英语。我并不是说粗鲁,只是想强调在PowerShell脚本中使用PowerShell .NET API是不合理的。如果您使用C#编写或执行一些非常高级的低级PowerShell攻击,API非常有用。

为了帮助您入门,请查看与PowerShell Job-noun相关的命令行开关。

Start-Job -ScriptBlock {Start-Sleep -Seconds $args[0]; Write-Host "Job is done!"} -ArgumentList @(1) -Name MyJob # dispatch a script, pass a parameter to it 
Write-Host "Print this line while we wait for job to be done." # could be doing whatever
Wait-Job -Name MyJob # blocking wait
Write-Host "Print this line when we know job is done."
Receive-Job -Name MyJob # receive job output, like EndInvoke
Get-Job -Name MyJob | Remove-Job # cleanup

如果您需要更多异步信令,请查看PowerShell事件。

如果PowerShell不是您想要写的,请编写C#代码并在PowerShell ad hoc中编译/运行它。

P.S。:.Net API中的PowerShell类是Runspace的包装器。因此,Factory方法Create()返回已经打开,准备使用Runspace的PowerShell类。所以这是多余的:

$ps.Runspace = $rs

它将简单地删除使用PowerShell类创建的完美的Runspace类实例。

答案 1 :(得分:1)

AsyncCallbacks,Actions,EventHandlers等...主要在后台进程中运行  因此无法访问主线程中的变量。

事件发布者总是将自己和一组预定义的事件参数传递给  你的经纪人。这些事件参数的内容由设计师决定  事件,你没有发言权。除非你走C#路线。

但是...... Add-Member是你的救世主。我重新编写了你的​​样本来进行演示  我正在谈论的概念。

$asyncCallback = {
    Param (
        # Event source object
        [System.Management.Automation.Powershell]
        $sender,

        # Inheritor of [System.EventArgs]
        [System.Management.Automation.PSInvocationStateChangedEventArgs]
        $e
    )

    # Ignore initial state change on startup
    if ($e.InvocationStateInfo.State -eq [System.Management.Automation.PSInvocationState]::Running)
    {
        return
    }

    Write-Host $sender.Message
    Write-Host "Event Fired!"
    Write-Host ("Invocation State: {0}" -f $e.InvocationStateInfo.State)

    #Write-EventLog -LogName Application -Source Testing -EntryType Information `
    #    -Category 0 -EventId 1234 -Message "Test."

    # Use the NoteProperty references attached to the Powershell object by Add-Member
    [void]$sender.EndInvoke($sender.AsyncResult)

    # Clean up if you like
    $sender.Dispose()

    #
    # You can unregister the event from within the event handler, but you
    # shouldn't do so if you plan on recycling/restarting the background
    # powershell instance.
    #
    # Unregister the event subscription
    Unregister-Event -SourceIdentifier $sender.EventSubscriber.Name

    #
    # WARNING!
    # Call To Remove-Job From Parent Thread!
    #
    # You cannot dispose of the EventJob (THIS) from within the job itself.
    # That would kind of be like a snake trying to eat it's own tail...
    #
    # As such, you should be very careful about how you remove background jobs. If
    # you use the command sequence below from anywhere within your main thead, you
    # will break this event handler (and any others created by Register-ObjectEvent).
    #
    # (Dispose of Job)
    # Get-Job | Remove-Job
    #
}

<#
 # This section is unnecessary unless you are modifying the apartment state
 # of the runspace before opening it. The shell returned by Create() already
 # has a runspace.
 #
 # $rs = [RunspaceFactory]::CreateRunspace()
 # $rs.Open()
 # $ps.Runspace = $rs
 #>
$ps = [PowerShell]::Create().AddScript( {
    #Get-Service
    Get-Process
    Start-Sleep -Seconds 2
} )

#
# Subscribe to the Powershell state changed event. Attach the registration object
# to the Powershell object for future reference.
#
Add-Member -InputObject $ps -MemberType NoteProperty -Name EventSubscriber -Value (
    Register-ObjectEvent -InputObject $ps -EventName InvocationStateChanged -Action $asyncCallback)

<#
 # This call structure is unnecessary as you aren't using the InvocationSettings
 #
 # $psis = New-Object Management.Automation.PSInvocationSettings
 # $aResult = $ps.BeginInvoke($psdcInputs, $psdcOutputs, $psis, $asyncCallback, $ps)
 #>

#
# Initialize invocation parameters
#
# Attach references to any data/objects/scriptblocks that you must be accessable
# within your event handler using Add-Member.
#
Add-Member -InputObject $ps -MemberType NoteProperty -Name Message -Value (
    "Hello World! It's Me {0}" -f $ps.EventSubscriber.Name)

$psdcInputs = New-Object Management.Automation.PSDataCollection[String]
$psdcInputs.Complete()
$psdcOutputs = New-Object Management.Automation.PSDataCollection[Object]

Add-Member -InputObject $ps -MemberType NoteProperty -Name AsyncResult -Value (
    $ps.BeginInvoke($psdcInputs, $psdcOutputs))

# Watch for race conditions
Start-Sleep -Seconds 10

# Kill all remaining background jobs (including the EventJob asyncCallback)
Get-Job

Get-Job | Remove-Job | Out-Null