2014-12-10 4 views
0

У меня возникли проблемы с получением PowerShell, чтобы вызвать функцию обратного вызова в комплект поставки:Почему PowerShell.BeginInvoke не вызывает обратный вызов?

$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 объектов, как и ожидалось. Но код в $asyncCallbackscriptblock не запускается, и событие не записывается в журнал событий. Я не вижу, что я делаю неправильно. Вы можете помочь, пожалуйста?

Примечание: Я не беспокоюсь о записи в журнал событий - есть другие вещи, которые я хочу здесь сделать, - но мне нужно было абстрагировать это из исходного кода, чтобы сделать его разумным.

+1

По какой-то причине '$ ps.HadErrors' установлен в' True' после вызова 'BeginInvoked'. –

+0

Вы правы! Даже если скриптовый блок пуст, он имеет значение True. Вы сталкивались с этим раньше? – user2871239

+0

Я запустил эквивалентный C#, и он не устанавливает HadErrors в true и обратный вызов действительно запускается. – user2871239

ответ

1

Если вы пытаетесь выполнить что-то в фоновом режиме, а вы делаете что-то еще, а затем возвращаете асинхронные результаты с помощью PowerShell, используйте PowerShell. Что вы делаете в своем примере, это не PowerShell. Точно так же, как «я могу иметь чизбургер», это не английский. Я не хочу быть грубым, просто хочу подчеркнуть, что использовать PowerShell .NET API в сценариях PowerShell необоснованно. API полезен, если вы пишете на C# или делаете очень продвинутые хай-текны PowerShell низкого уровня.

Для начала ознакомьтесь с инструкциями PowerShell Job-существительного.

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 .: Класс PowerShell в .Net API - это оболочка для Runspace. Таким образом, метод Factory Create() возвращает класс PowerShell, который уже имеет открытый, готовый к использованию Runspace. так что это является излишним:

$ps.Runspace = $rs 

Это будет просто падение вполне хороший экземпляр класса, который пространство выполнения был создан с классом PowerShell.

+0

Ну, да, вы правы во многих отношениях, но, как я уже отмечал, этот пример был упрощенным выпиской из исходного кода. Я не могу использовать C#, потому что политика не позволит мне: я должен использовать PowerShell. Но мои пользователи хотят простой пользовательский интерфейс. Итак, я использую PowerShell для вызова пользовательского интерфейса WPF, и мне нужны фоновые потоки, чтобы поддерживать пользовательский интерфейс. Рабочие места не подходят для этого, потому что время запуска измеряется в секундах. Другие использовали этот подход, и это сработало для них, поэтому я подумал, что я буду исследовать его ... – user2871239

+0

... и спасибо за заметку о Runspace: я этого не осознал, но это может быть не так актуально, как в реальном код Я использую InitialSessionState для создания Runspace. – user2871239

0

AsyncCallbacks, Actions, EventHandlers и т. Д. ... в основном работают в фоновом режиме и поэтому не имеют доступа к переменным в основном потоке.

Издатели событий всегда передают себя и предопределенный набор аргументов событий вашего обработчика. То, что входит в эти аргументы, определяется разработчиком событием, и вы не можете сказать об этом. Если вы не отправитесь на маршрут C#.

BUT ..... Добавить-член - ваш спаситель здесь. Я переписал ваш образец для демонстрации концепций, о которых я говорю.

$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 
+0

Я ценю ответ - спасибо большое, но это было некоторое время назад, когда я разместил это; это может быть некоторое время, прежде чем я получу некоторое время, чтобы оглянуться на него. – user2871239

Смежные вопросы