How to add a callback function to an asynchronous job in powershell and get return data

我与影子孤独终老i 提交于 2021-01-19 08:23:29

问题


I've been searching around the internet and combining lots of different pieces of code, but I'm just not succeeding at creating a callback for my asynchronous job.

The idea is that I want to run multiple jobs, and using the callback methods I will be able to parse the output from the different jobs to change certain states and output in the main script.

Currently I have this, and although the event gets called, I'm unable to get the actual output. What am I missing here?

$rsPool = [runspacefactory]::CreateRunspacePool(1,2)
$rsPool.Open();

$WebRequest = {
    param($url)
    return Invoke-WebRequest -Uri ($url)
}

$handler = {
  Write-Host "event called";
  Write-Host $_
}

$jobs = @()

$PSinstance = [powershell]::Create();
$PSinstance.AddScript($WebRequest).AddArgument("https://google.com")
$PSinstance.RunspacePool = $rsPool
$jobs += $PSinstance.BeginInvoke()

Register-ObjectEvent -InputObject $PSinstance -EventName InvocationStateChanged -Action $handler;

#$PSinstance | Get-member -MemberType Event

while($jobs.isCompleted -contains $false) {
    Start-Sleep 1
}

$rsPool.close();

回答1:


As an alternative to using the fairly complex PowerShell SDK, if this is just about running the web requests in parallel and it is acceptable to synchronously wait for them to finish,
ForEach-Object -Parallel - available in PowerShell v7+ - offers a simple solution:

# Uses threads to run two Invoke-WebRequest calls in parallel,
# synchronously waits for both to finish and collects the responses.
$responses = 'https://google.com', 'http://example.org' | ForEach-Object -Parallel {
  Invoke-WebRequest -Uri $_
}

Instead of waiting synchronously, you can also use the -AsJob switch to have job objects returned[1], asynchronously, which you can later manage with the regular *-Job cmdlets (Wait-Job, Receive-Job, ...):

# Uses threads to run two Invoke-WebRequest calls in parallel.
# Asynchronously returns job objects that can be monitored later.
$jobs = 'https://google.com', 'http://example.org' | ForEach-Object -AsJob -Parallel {
  Invoke-WebRequest -Uri $_
}

# ... do other things

# Now wait synchronously (though you could poll the state instead).
# and output the responses.
$jobs | Receive-Job -Wait -AutoRemoveJob

In earlier PowerShell versions you can use the Start-ThreadJob cmdlet (comes with v6+, installable via Install-Module ThreadJob in earlier versions)[2]:

$jobs = 'https://google.com', 'http://example.org' | ForEach-Object {
  $uri = $_
  Start-ThreadJob { Invoke-WebRequest -Uri $using:uri }
}

# ... do other things

# Now wait synchronously (though you could poll the state in a loop instead)
# and output the responses.
$jobs | Receive-Job -Wait -AutoRemoveJob

Note: While you could also use regular child-process-based background jobs, created with Start-Job, the thread-based options above perform much better - see about_Jobs.


[1] Instances of [System.Management.Automation.PSTasks.PSTaskJob], a not currently documented type that derives from System.Management.Automation.Job, which ensures that instances can be used with the *-Job cmdlets.

[2] Start-ThreadJob returns instances of [ThreadJob.ThreadJob], a not currently documented type that derives from System.Management.Automation.Job2, which in turn derives from System.Management.Automation.Job




回答2:


Try this one!

`

$rsPool = [runspacefactory]::CreateRunspacePool(1,2)
$rsPool.Open();

$WebRequest = {
    param($url)
    return Invoke-WebRequest -Uri ($url)
}


$jobs = @()

$PSinstance = [powershell]::Create();
$PSinstance.AddScript($WebRequest).AddArgument("https://google.com")
$PSinstance.RunspacePool = $rsPool
$Jobs += [PSCustomObject]@{ Pipe = $PSinstance; Status = $PSinstance.BeginInvoke() }

$results=@()
while ($Jobs.Status -ne $null)
{
    start-sleep -s 1
    write-host "." -nonewline -fore cyan
    foreach ($completedjob in $Jobs|?{ $_.Status.IsCompleted -eq $true })
    {
        $results+=$completedjob.Pipe.EndInvoke($completedjob.Status)
        $completedjob.Status = $null
    }
}

$rsPool.close();

$results|out-host

`




回答3:


I ended up changing the Invoke method, and the Event registerer to pass a parameter that contains the output.

Although this is probably not the cleanest method, it does work for me (with some checks in place that the data isn't accessed before it's actually available of course.

$Object = New-Object 'System.Management.Automation.PSDataCollection[psobject]';

$jobs += $PSinstance.BeginInvoke($Object, $Object);

Add-Member -InputObject $PSinstance -MemberType NoteProperty -Name EventSubscriber -Value (
    Register-ObjectEvent -InputObject $PSinstance -EventName InvocationStateChanged -MessageData $Object -Action {
        # Ignore initial state change on startup
        if ($event.InvocationStateInfo.State -eq [System.Management.Automation.PSInvocationState]::Running) {
            return;
        }

        Write-Host "event called";
        Write-Host $Object.StatusCode;
    }
);

# Can be accessed after the invoke has finished.
Write-Host "The result is: $Object.StatusCode";


来源:https://stackoverflow.com/questions/64819263/how-to-add-a-callback-function-to-an-asynchronous-job-in-powershell-and-get-retu

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!