Using Jobs in PowerShell 7

PowerShell

PowerShell jobs are an integral part of PowerShell. This handy feature allows administrators to run commands asynchronously. Asynchronous commands allow for parallel execution cutting down on time and fully leveraging compute power.

With version 7, PowerShell continues its support of jobs giving scripters an efficient way to build efficient, speedy code.

Demonstrating Jobs

To demonstrate how jobs work in PowerShell 7, let’s start off with a simple example.

Perhaps you have a script that pings a remote server. If that server returns a ping response, you know it’s online and can then perform some other kind of action. For this example, let’s assume it’s simply placing a file on the remote system.

If you’ve performed a ping sweep on many remote servers at once, you’d know that some servers will return a response quickly while some will seemingly take forever. If running a script like this asynchronously, the quick-to-respond servers may have to wait for the slow-to-respond servers.

There’s no reason for one server to wait on another since there are no dependencies between the tasks. This is a perfect use case for jobs.

Let’s start with the example script below. This script allows the user to provide one or more server names as input via the ServerName parameter. It then attempts to ping each one using the Test-Connection cmdlet. If the ping is successful, it then creates a file called newfile.txt in the root of the C drive on the remote system.

param (
    [Parameter(Mandatory)]
    [string[]]$ServerName
)

$ServerName | ForEach-Object {
    if (Test-Connection -ComputerName $_ -Quiet -Count 1) {
        $null = New-Item -Path "\\$_\c$\newfile.txt" -ItemType File
    }
}

When you run this script, it’s going to process each remote system one by one. This process may take a long time if you pass hundreds of remote system names to it.

If the script were called New-ServerFile.ps1, you could run the script like the example below.

PS7> .\New-ServerFile.ps1 -ServerName SRV1,SRV2,SRV3

Instead, let’s modify it to use PowerShell 7’s jobs feature.

The first step is to figure out what code needs to be run inside of each job and when to create a new job. Technically, you could encapsulate this entire script in a single job. That wouldn’t do much good because each task would still be performed asynchronously.

Instead, you should create a new job for each remote server. This will prevent the script from hanging up on any slow-to-respond servers. To do that, place the Start-Job cmdlet inside of the Foreach loop to execute one job per server.

param (
    [Parameter(Mandatory)]
    [string[]]$ServerName
)

$ServerName | ForEach-Object {
    $block = {
        if (Test-Connection -ComputerName $_ -Quiet -Count 1) {
            $null = New-Item -Path "\\$_\c$\newfile.txt" -ItemType File
        }
    }
    Start-Job -Scriptblock $block
}

As-is, this code will not work though. When you create a job, you create a new thread that’s not aware of any variables created in the current thread. You must pass the server name to the job’s code. To do so, you’ll have to use the ArgumentList parameter.

Along with the usage of ArgumentList with the Start-Job cmdlet, the script now uses $args[0] instead of the local variable $_. This is how PowerShell knows to look for the first value passed to the ArgumentList parameter.

param (
    [Parameter(Mandatory)]
    [string[]]$ServerName
)

$ServerName | ForEach-Object {
    $block = {
        if (Test-Connection -ComputerName $args[0] -Quiet -Count 1) {
            $null = New-Item -Path "\\$($args[0])\c$\newfile.txt" -ItemType File
        }
    }
    Start-Job -Scriptblock $block -ArgumentList $_
}

When you run this script, you’ll now immediately see a few jobs returned depending on how many server names you’ve provided.

PS7> .\New-ServerFile.ps1 -ServerName SRV1,SRV2,SRV3

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
1      Job1            BackgroundJob   Running       True            localhost            ....
2      Job2            BackgroundJob   Running       True            localhost            ....
3      Job3            BackgroundJob   Running       True            localhost            ....

There will now be a job running for each server. During the running time, you can now use the Get-Job cmdlet to inspect the status of each job.

PS> Get-Job

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
1      Job1            BackgroundJob   Completed     True            localhost            ....
2      Job2            BackgroundJob   Running       True            localhost            ....
3      Job3            BackgroundJob   Running       True            localhost            ....

Each server will be processed as soon as PowerShell can bring up a new job.

In this example, the script built does not return any output. If it would have, you could have used the Receive-Job cmdlet to return any output from the script.

PS> Get-Job | Receive-Job

Summary

In this article, you learned the basics of PowerShell jobs with a real-world use case. But, there’s a whole lot more to using jobs in PowerShell 7. Make sure to check out on PowerShell hub on Petri for more PowerShell tutorials.