我无法让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
中的代码未运行且事件未写入事件日志。我无法看清我做错了什么。你能帮忙吗?
注意:我实际上并不关心写入事件日志 - 我还想在这里做其他事情 - 但我需要从原始代码中抽象出来以使其合理化。
答案 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