If you execute a long-running command or script as a PowerShell background job, the command prompt will return instantly while the command continues to run in its own thread.
Latest posts by Timothy Warner (see all)

PowerShell logoLet’s say you sat down at your administrative workstation with a cup of coffee, and you planned to run a series of administration tasks by using PowerShell. The first few tasks complete quickly—no big deal. However, when you run a particularly long-running task (perhaps a remoting job that queries a dozen servers in parallel), your PowerShell session hangs and you find yourself waiting... and waiting... and waiting.

Sure, you can spawn another PowerShell console session, but then you lose all the variables and other goodies that you loaded into your original PowerShell runspace. What to do?

The short answer is to take advantage of the PowerShell background jobs architecture. From now on, we can send long-running tasks to the background and continue our current PowerShell session uninterrupted. Let’s see how to do this.

Starting a new background job

Fire up an elevated PowerShell console session and run the following command to retrieve a list of job-related cmdlets:

PS C:\> Get-Command -Noun Job | Select-Object -Property Name

Name
----
Debug-Job
Get-Job
Receive-Job
Remove-Job
Resume-Job
Start-Job
Stop-Job
Suspend-Job
Wait-Job

To create a new background job, we use Start-Job (frankly, I originally looked for a New-Job command, but the PowerShell team must have thought that Start-Job was more action-oriented).

As usual, we’ll first run Update-Help to make sure we have the latest and greatest PowerShell documentation, and then we’ll examine the help for Start-Job, paying particular attention to the examples.

Get-Help -Name Start-Job -ShowWindow

Let’s say that we needed to scour our computer’s D: drive for PowerShell .ps1 script files. Depending on how clogged your file system is, a recursive search such as the following could take a while:

Get-ChildItem -Path D:\ -Filter *.ps1 -Recurse

Let’s instead define a new job named “ScriptSearch” that puts the work into the background of our console session:

PS C:\> Start-Job -Name "ScriptSearch" -ScriptBlock { Get-ChildItem -Path D:\ -Filter *.ps1 -Recurse }

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------
2      ScriptSearch    BackgroundJob   Running       True            localhost

Let me break down the job details table for you:

  • Id: Jobs receive a unique identification number. The PowerShell job engine often breaks a “parent” job into one or more “child” jobs. You see this a lot when you use remoting.
  • Name: This is the optional “friendly” name that you provide with the -Name parameter.
  • PSJobTypeName: Besides BackgroundJob, PowerShell also has RemoteJob, PSWorkflowJob, and PSScheduledJob job types.
  • State: This tells you whether the job is running, finished, or in an error state.
  • HasMoreData: If this is True, then the job results are still in memory.
  • Location: This tells you on which computer(s) the job task is executing or has executed.

Retrieving job results

We use Receive-Job to check on job status. The big “gotcha” here is that, if you forget to include the -Keep parameter, PowerShell dumps the job results from memory! Watch this:

PS C:\> Receive-Job -Id 2


 Directory: D:\


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 3/16/2015 10:23 AM 687 InstallGoogleChrome.ps1

Now, I’ll try to receive the job results a second time:

PS C:\> Receive-Job -Id 2
PS C:\>

Ouch! Let’s run Get-Job to see if the job object still persists in memory:

PS C:\> Get-Job

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------
2      ScriptSearch    BackgroundJob   Completed     False           localhost

Yeah, the ScriptSearch job shows as completed, and the HasMoreData value of False says that the job results data flew out the proverbial window. We can either close our PowerShell console session to dump the job object from memory, or we can use the PowerShell pipeline and the Remove-Job cmdlet:

Get-Job -Name "ScriptSearch" | Remove-Job

Adding scripts to the mix

The Start-Job cmdlet has a -FilePath parameter that accepts .ps1 script files. Let’s imagine we wrote a script called EventScrape.ps1 that pulled warning and error messages from the System event log from two computers. Here’s the code:

Invoke-Command -ComputerName dc1,adfs1,vpnclient1 -ScriptBlock {Get-EventLog -LogName System -EntryType Warning }

To run the script as a background job, we simply use the appropriate parameter:

PS C:\> Start-Job -Name "EventScrape" -FilePath "C:\EventScrape.ps1"

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------
2      EventScrape     BackgroundJob   Running       True            localhost

My job ID 2 shows as “localhost” for its location because the script (job) itself is managed on the local computer, which, in my case, is a Windows Server 2012 R2 domain controller named dc1. Within the script code, of course, we’re touching three boxes simultaneously, thanks to the magic of PowerShell remoting.

When fetching results, we’ll be sure to include -Keep:

PS C:\> Receive-Job -Name "EventScrape" -Keep | Select-Object -First 1 | Format-List

Index              : 1458
EntryType          : Warning
InstanceId         : 40961
Message            : The Security System could not establish a secured
                      connection with the server
                      ldap/dc1/company.pri@COMPANY.PRI. No authentication
                      protocol was available.
Category           : (0)
CategoryNumber     : 0
ReplacementStrings : {ldap/dc1/company.pri@COMPANY.PRI}
Source             : LsaSrv
TimeGenerated      : 3/25/2015 10:43:55 AM
TimeWritten        : 3/25/2015 10:43:55 AM
UserName           : NT AUTHORITY\SYSTEM
PSComputerName     : localhost

The PSComputerName property is particularly handy when you use PowerShell remoting because you’ll learn on which remote computer the message arose. Let’s verify that the job’s data still exists in our runspace:

PS C:\> Get-Job

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------
2      EventScrape     BackgroundJob   Completed     True            localhost

Remember, the True value for the HasMoreData property means that we are free to receive the job results again, provided we include the -Keep parameter.

Understanding parent and child jobs

To demonstrate how PowerShell implements parent and child jobs, let’s start a new remoting job that again targets my three lab computers:

PS C:\> Start-Job -Name "wmi-os" -ScriptBlock { Get-WmiObject -Class Win32_operatingsystem -ComputerName dc1,adfs1,vpnclient1 }

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------
4      wmi-os          BackgroundJob   Running       True            localhost

If you’ve been watching my code output closely, you’ll observe that my wmi-os job has job ID 4, while the previous job, EventScrape, has job ID 2. What’s the deal with job ID 3? Look here:

PS C:\> Get-Job -Name "EventScrape" -IncludeChildJob

Id     Name            PSJobTypeName   State         HasMoreData     Location
--     ----            -------------   -----         -----------     --------
2      EventScrape     BackgroundJob   Completed     True            localhost
3      Job3                            Completed     True            localhost

PowerShell always defines a parent, or top-level, job, and then does the actual work in one or more child jobs. As you can see, the -IncludeChildJob parameter allows you to see the full family relationship, as it were, between parent and child.

We can also selectively retrieve job results data from a child job directly. Practically speaking, I always query the parent job when I’m fetching results; examining the child jobs is useful when you’re troubleshooting errors.

PS C:\> Receive-Job -id 4 -Keep

SystemDirectory : C:\Windows\system32
Organization    :
BuildNumber     : 9600
RegisteredUser  : Windows User
SerialNumber    : 00252-80027-07122-AA034
Version         : 6.3.9600

# additional output omitted

One bummer about the above example is that using -ComputerName with Get-WMIObject doesn’t return a PSComputerName value like “true” PowerShell remoting does. A better approach would be to run the Get-WMIObject call from the context of an Invoke-Command statement. Live and learn!

Using the -AsJob parameter

Some Windows PowerShell commands can be shoehorned into a PowerShell job by using the -AsJob parameter. Let’s investigate which commands can do this in PowerShell v5 preview:

PS C:\> Get-Command -ParameterName AsJob | Select-Object -Property Name

Name
----
Get-WmiObject
Invoke-Command
Invoke-WmiMethod
Remove-WmiObject
Restart-Computer
Set-WmiInstance
Stop-Computer
Test-Connection

Cool—Invoke-Command is on the list. Let’s capture a list of stopped services on our three computers as a job:

PS C:\> Invoke-Command -ComputerName dc1,adfs1,vpnclient1 -ScriptBlock { Get-Service | Where-Object { $_.Status -eq "Stopped" } } -AsJob

Id     Name      PSJobTypeName   State         HasMoreData     Location
--     ----      -------------   -----         -----------     --------
4      Job4      RemoteJob       Running       True            dc1,adf...

The -AsJob parameter is great for when you decide in the middle of your PowerShell statement that, “Hey, this task would make for a great background job!”

For further study

I hope you found this article useful. Please don’t forget about PowerShell’s built-in conceptual help library. Run the following command to get the filenames; I’ve hyperlinked each one to its online version for your studying convenience:

Subscribe to 4sysops newsletter!

PS C:\> Get-Help -Name about_* | Select-Object -Property Name, Synopsis | Where-Object {$_.Name -Like "*job*" } | Format-Table -AutoSize
0 Comments

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account