Here’s a quick tutorial on how you can do asynchronous script execution in PowerGUI admin console.
Suppose you want to have a node in PowerGUI which would show computers in your office which are currently online. The script (using Quest AD cmdlets and Test-Connection cmdlet from PowerShell v2) could look like:
Get-QADComputer -Location 'EMEA/RU/St.Petersburg' |
ForEach-Object {
if (Test-Connection -ComputerName $_.Name -Quiet)
{ $_ }
}
You will probably have a different Location parameter in your environment. Please change it here and in all samples below.
And it seems to work but is taking awfully long time to execute (sure, we could have optimized it a bit – but that’s not the point ;)). And you kind of have to sit there and watch it executing because if you click another node in PowerGUI admin console it will abort the operation…
The easy fix is to turn it into background job by taking the script, adding Add-PSSnapin (because background jobs start in their own runspace) and keeping the job handle in a global variable (so we don’t lose the job when we leave the node):
$global:OnlineComputersJob = Start-Job {
Add-PSSnapin 'Quest.ActiveRoles.ADManagement'
Get-QADComputer -Location 'EMEA/RU/St.Petersburg' |
ForEach-Object {
if (Test-Connection -ComputerName $_.Name -Quiet)
{ $_ }
}
}
This will start the job and it will keep running in the background while you can use PowerGUI for other tasks.
Now I will just change it a little bit to add actual output both to the grid and Output window. I will add yet another global variable $global:OnlineComputersResults and use it to keep all the results.
# if no job started yet, start a new one, else output old results
if ( $global:OnlineComputersJob -eq $null ) {
# this is the collection in which we will keep job results
$global:OnlineComputersResults = @()
$global:OnlineComputersJob = Start-Job {
Add-PSSnapin 'Quest.ActiveRoles.ADManagement'
Get-QADComputer -Location 'EMEA/RU/St.Petersburg' |
ForEach-Object {
if (Test-Connection -ComputerName $_.Name -Quiet)
{ $_ }
}
}
} else {
$global:OnlineComputersResults
}
# Now let's keep looping and adding new results to the grid
while ( $global:OnlineComputersJob.JobStateInfo.State -ne 'Completed' ) {
# get new results, add them to old ones, and output them to the grid
# @() required to make sure PowerShell knows it is a collection
$results = @(Receive-Job $global:OnlineComputersJob)
$global:OnlineComputersResults += $results
$results
Write-Host "Added $($results.count) records. More on the way."
Start-Sleep -Seconds 5
}
# When the job is completed, output whatever remains
$results = Receive-Job $global:OnlineComputersJob
$global:OnlineComputersResults += $results
$results
Write-Host "Added $results records."
Write-Host "Job completed."
That is it. This node will start running and adding new results to the grid as they appear.
Now let’s make it really shine by adding an action to reset the computer list, and make other computer-related actions show-up in the right-hand pane.
To add the reset action, we first add a category: right-click the right-hand pane and select New / Category – then name it something – e.g. “Job Control”.
After that, right click the new category and select New / Script Action – then create a new action called Reset computer list with the following code:
$global:OnlineComputersResults = $null
Remove-Job $global:OnlineComputersJob -Force
$global:OnlineComputersJob = $null
When you click this action the computer list gets recompiled from scratch.
Finally, let’s modify the original node list to tell PowerGUI that the objects in the grid can be used as computer objects. We need to do this because background jobs return not real objects but their deserialized versions (only data fields) – hence the type is also changed to “Deserialized.Quest.ActiveRoles.ArsPowerShellSnapIn.Data.ArsComputerObject”.
If we want to tell PowerGUI that these objects can be treated as regular computer objects we have two options to do that:
1. (Safer) Go through the regular actions displayed for computers and, for the ones which are not using any methods, add ‘Deserialized.Quest.ActiveRoles.ArsPowerShellSnapIn.Data.ArsComputerObject’ in Properties / Display Configuration / Associate the action with these types.
2. (Easier) Modify the original node code so that the first element we output contains the regular computer type name: ‘Quest.ActiveRoles.ArsPowerShellSnapIn.Data.ArsComputerObject’.
So with that easier option, the node code will look like:
# if no job started yet, start a new one, else output old results
if ( $global:OnlineComputersJob -eq $null ) {
# this is the collection in which we will keep job results
$global:OnlineComputersResults = @()
$global:OnlineComputersJob = Start-Job {
Add-PSSnapin 'Quest.ActiveRoles.ADManagement'
Get-QADComputer -Location 'EMEA/RU/St.Petersburg' |
ForEach-Object {
if (Test-Connection -ComputerName $_.Name -Quiet)
{ $_ }
}
}
} else {
$global:OnlineComputersResults
}
# Now let's keep looping and adding new results to the grid
while ( $global:OnlineComputersJob.JobStateInfo.State -ne 'Completed' ) {
# get new results, add them to old ones, and output them to the grid
# @() required to make sure PowerShell knows it is a collection
$results = @(Receive-Job $global:OnlineComputersJob)
#for the first object in the collection, set object type
if (( $global:OnlineComputersResults.Count -eq 0 ) -and
( $results.count -gt 0 )) {
$results[0].PSObject.TypeNames.Insert(0,`
'Quest.ActiveRoles.ArsPowerShellSnapIn.Data.ArsComputerObject')
}
$global:OnlineComputersResults += $results
$results
Write-Host "Added $($results.count) records. More on the way."
Start-Sleep -Seconds 5
}
# When the job is completed, output whatever remains
$results = Receive-Job $global:OnlineComputersJob
$global:OnlineComputersResults += $results
$results
Write-Host "Added $results records."
Write-Host "Job completed."
That is it. Now you have your Online Computers node which:
A. Execute in the background asynchronously,
B. Outputs results as they get in,
C. Has all the actions for its results plus a new one to restart it.
This is how your console would probably look like:
And, yes, PowerGUI at some point will probably have a lot of this just built into the framework. Meanwhile, I hope that you find this workaround useful. π
Big thanks to Karl for the idea of this blog post.