Background Jobs in PowerGUI

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:

background

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.

2 Responses to “Background Jobs in PowerGUI”


  1. 1 Sune Alexandersen December 16, 2011 at 9:18 am

    Thanks for an excellent and descriptive blogpost!
    I get results when I run the first bare-bones snippet, but when I run the final completed scripti simply get “Added records” (notice the extra space telling me that $results is empty) and “Job completed” but nothing in the grid.

    Any idea?


  1. 1 Episode 86 – Jason Shirk from the PowerShell team « PowerScripting Podcast Trackback on October 5, 2009 at 3:22 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




My Recent Tweets

Legal

The posts on this blog are provided “as is” with no warranties and confer no rights. The opinions expressed on this site are mine and mine alone, and do not necessarily represent those of my employer - WSO2 or anyone else for that matter. All trademarks acknowledged.

© 2007-2014 Dmitry Sotnikov

September 2009
M T W T F S S
« Aug   Oct »
 123456
78910111213
14151617181920
21222324252627
282930  

%d bloggers like this: