PowerShell Jobs Week: Introduction to PowerShell Jobs

Doctor Scripto

Summary: Richard Siddaway introduces you to Windows PowerShell jobs.

Hey, Scripting Guy! Question Hey, Scripting Guy! I’ve just starting learning Windows PowerShell, and I have some long running tasks to perform. What’s the best way of running these tasks?


Hey, Scripting Guy! Answer Hello LJ,

Honorary Scripting Guy, Richard Siddaway, here today filling in for my good friend, The Scripting Guy. LJ, you need to use Windows PowerShell jobs for your long running tasks. Jobs are an area of Windows PowerShell that I feel is neglected. The headline piece of functionality in Windows PowerShell 2.0 was remoting. In many respects, Windows PowerShell jobs are just as important and beneficial, to your automation efforts as remoting has been.

This is the first of a series of posts that, hopefully, will shine the spotlight on Windows PowerShell jobs, remind people of their capabilities, and encourage their greater adoption. The full series comprises:

  1. Introduction to PowerShell jobs (this post)
  2. WMI and CIM Jobs
  3. Remote Jobs
  4. Scheduled Jobs
  5. Jobs and Workflows
  6. Job Processes
  7. Jobs in the Enterprise

This introductory post will cover the basics of using Windows PowerShell jobs.

Jobs were introduced in Windows PowerShell 2.0 and helped to solve a problem inherent in the command-line tools. That is, if you start a long running task, your prompt is unavailable until the task finishes. As an example of a long running task, think of this simple Windows PowerShell command:

Get-ChildItem -Path c:\ -Recurse

If you run the command, you will get a full directory list of your C: drive. That will take some time. As an aside, if you are experimenting with performance counters in Windows PowerShell, running this command in a couple of consoles is a good way to give you CPU a work out.

Back when I was using VBScript, I would have multiple command prompts open—each running a script.  That can get confusing and annoying.  With Windows PowerShell, you can also run multiple consoles or instances of the Windows PowerShell ISE. But a better approach is to use a Windows PowerShell job.

To create a Windows PowerShell job, simply put your code into the script block of the Start-Job cmdlet:

Start-Job -ScriptBlock {Get-ChildItem -Path c:\ -Recurse}

The cmdlet will return information about the job as shown in the following screenshot.

Image of command output

Windows PowerShell will assign a name and numeric ID. You can see that the job type is a Background job (job types are explained in the Help file about_Job_Details), and that it’s running on the local machine. The final column shows (at least some of) the command that is running.

Note  The numeric IDs that you see on your machine may vary from those displayed in this post. The important point is the relationship between the jobs and their IDs.

You can check on the progress of your jobs using Get-Job:

Image of command output

The same information is presented as when the job started.

You can use the job name or ID to pull back information on an individual job:

Get-Job -Id 4

Get-Job -Name Job4

Both commands return the same information about your job. It is interesting to use the –IncludeChildJob parameter, as shown in the next screenshot.

Image of command output

The job with an ID of 4 is the data you’ve been seeing all along. Now a job with an ID of 5 has suddenly appeared. Actually, it’s been there all the time because Windows PowerShell jobs created through Start-Job always consist of a parent job and a child job. The child job does the actual work. If you were running the job against a number of remote machines by using Invoke-Command and its –AsJob parameter, you would get one child job per remote machine.

When you manage jobs, anything you do to the parent job is automatically applied to any child jobs. Removing or stopping the parent job performs the same action on the child jobs. Getting the results of the parent job means you get the results of all the child jobs. As you will see later, you can access the child jobs directly to retrieve their data.

If you start another job as shown here…

Image of command output

…you’ll see that it is given an ID of 6. Windows PowerShell automatically assigns the next ID number, taking child job IDs into account. This can cause confusion to people new to Windows PowerShell jobs.

When a job has completed, you can retrieve the data stored in the job.

Image of command output

You can see that the job consists of a parent job (ID 6) and a child job (ID 7). You can use Receive-Job to get the data from the job. I added the –Keep parameter to prevent the default action of the data being deleted from the job. In a simple job, as in the example, you can access the data through the parent or child jobs:

Receive-Job -Id 6 -Keep

Receive-Job -Id 7 –Keep

When you have multiple child jobs, its usually easier to access the child jobs in turn:

$jobs = Get-Job -Name Job6 | select -ExpandProperty ChildJobs

foreach ($job in $jobs){Receive-Job -Job $job -Keep}

You’ve seen some of the cmdlets that are associated with Windows PowerShell jobs. The full set is:

  • Get-Job
    Gets Windows PowerShell background jobs that are running in the current session
  • Receive-Job
    Gets the results of the Windows PowerShell background jobs in the current session
  • Remove-Job
    Deletes a Windows PowerShell background job
  • Resume-Job
    Restarts a suspended job
  • Start-Job
    Starts a Windows PowerShell background job
  • Stop-Job
    Stops a Windows PowerShell background job
  • Suspend-Job
    Temporarily stops workflow jobs
  • Wait-Job
    Suppresses the command prompt until one or all of the Windows PowerShell background jobs that are running in the session are complete

Suspend-Job and Resume-Job only apply to workflow jobs. You’ll learn more about them on day 5.

Each cmdlet has an associated Help file (remember that you can use Update-Help in Windows PowerShell 4.0 and Windows PowerShell 3.0). There is also an extensive set of about files:

  • about_Jobs
  • about_Job_Details
  • about_Remote_Jobs
  • about_Scheduled_Jobs
  • about_Scheduled_Jobs_Advanced
  • about_Scheduled_Jobs_Basics
  • about_Scheduled_Jobs_Troubleshooting

One important point to note is that the simple Windows PowerShell jobs that you’ve seen so far are isolated to the Windows PowerShell session in which you started the job. If you close the session that contains the jobs, you will lose the jobs and your results unless you’ve saved the data to disk.

Another way to work with jobs is to create a variable that holds the job object:

Image of command output

You create the variable when you start the job:

$myjob = Start-Job -ScriptBlock {Get-ChildItem }

Notice in the screenshot that there is no output as the job starts. You can use the variable to display job information or any of the techniques you’ve seen already:

Get-Job -Id 10

Get-Job -Name Job10

You can also do this:

$myjob | Get-Job

The variable can be used directly when retrieving job data:

Receive-Job -Job $myjob –Keep

Many people like to name their jobs rather than allowing Windows PowerShell to name them:

Start-Job -ScriptBlock {Get-ChildItem } -Name myjob

Get-Job -Name myjob

Receive-Job -Name myjob -Keep

You can see the start and end times of your jobs:

Image of command output

If you want to see the execution time, you need to do a little bit of work:

Get-Job | select PSBeginTime, PSEndTime, @{N='ExecutionTime'; E={$_.PSEndTime – $_.PSBeginTime}}

The execution time is calculated by subtracting the begin time from the end time, which results in a timespan object:

Image of command output

Notice that the first job run, which was Get-ChildItem, runs recursively against the C: drive, and it took over 52 minutes to run. That’s a long time to have your Windows PowerShell prompt locked up by a single task. Use a job and you can carry on working.

You should always aim to clean up your Windows PowerShell environment and remove any lingering artifacts such as jobs, remoting, or CIM sessions before shutting Windows PowerShell. In the case of jobs, they will be removed for you if you don’t do it, but I think that is a sloppy approach. Especially when you can remove all old jobs in one pass:

Get-Job | Remove-Job

Or you can remove individual jobs:

Get-Job -Id 12  | Remove-Job

Remember that Name can also be used as an identifier.

Alternatively, you can filter on job completion:

Get-Job -State Completed | Remove-Job

You need to ensure that your jobs have all completed before you delete them and close Windows PowerShell:

Get-Job | where State -ne 'Completed'

One common mistake when people start working with jobs is to forget to retrieve the data before deleting the job. Try to make using jobs a process: Run the job, get the data, delete the job. That way, you clean up as you work with the jobs, and you don’t forget to retrieve your data.

That’s it for today. Tomorrow you’ll learn about WMI and CIM jobs. Bye for now.


Thanks, Richard.

Richard Siddaway is based out of the UK, and he spends his time automating anything and everything, for Kelway, Ltd. A six-year Windows PowerShell MVP, Richard is a prolific blogger, mainly about Windows PowerShell (Richard Siddaway's Blog: Of PowerShell and Other Things) and a frequent speaker at user groups and Windows PowerShell conferences. He has written a number of Windows PowerShell books: PowerShell in Practice; PowerShell and WMI, PowerShell in Depth (co-author); PowerShell Dive (co-editor), and he is currently finishing Learn Active Directory Management in a Month of Lunches, which features lots of Windows PowerShell. All of the books are available from Manning Publications.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 


Discussion is closed.

Feedback usabilla icon