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.
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";
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
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
`