top of page

Leveraging PowerShell Runspaces to Create Faster Scripts

In today's fast-paced digital world, speed and efficiency are key to getting the job done. This is particularly true in the world of scripting, where every second counts when executing complex tasks. While PowerShell is an incredibly powerful scripting language, even the most optimized scripts can be slowed down by resource-intensive operations. This is where PowerShell Runspaces comes into play. By leveraging the power of Runspaces, PowerShell developers can create lightning-fast scripts that can handle multiple tasks simultaneously without sacrificing performance or stability.


Brief Overview of PowerShell Runspaces

Runspaces allow you to develop multi-threaded processes that greatly increase the efficiency of your scripts. While Runspaces can provide improvements to your scripts, they are not something you want to use in all your scripts. Runspaces are best saved for scripts that are long-running and have unrelated components. Using Runspaces does however, use more resources as each Runspace is effectively its own PowerShell process. Runspace pools, which is what we will be covering in the following sections, allow better management of many Runspaces. Instead of trying to keep track of many single Runspaces, you must only keep track of the Runspace pool.


What Makes Up a Runspace Pool?

By using a Runspace pool, PowerShell commands can be executed simultaneously, with each Runspace running in its own thread. The components of a Runspace can be found below.


1. Initialize the Runspace pool, specifying the minimum and maximum number of Runspaces that should exist in the Runspace pool. We also define the list of servers we want to iterate over for each Runspace.

$RunspacePool = [runspacefactory]::CreateRunspacePool(2,10)  # Set up runspace pool
$RunspacePool.Open()

$ServerList = @(
    "Server1", "Server2", "Server3", "Server4", "Server5"
)

2. Next, create a variable and iterate through each item in the list of items that need to be run asynchronously. Each iteration will create a PowerShell script, define the Runspace pool for the script, then start the execution for the script and store the output of the script in the results key.

$Runspaces = foreach ($Server in $ServerList) { 
    $PSInstance = [powershell]::Create().AddScript({  # Create new PowerShell instance
        param($Param1)

        [ INSERT CODE HERE ]
    }).AddParameter('Param1', $Param1)  # Add required parameters

    
    $Instance.RunspacePool = $RunspacePool  # Add the instance to the pool


    New-Object psobject -Property @{
        Instance = $Instance
        Result = $PSInstance.BeginInvoke() # Start executing
    }
}

3. Once all the Runspaces have been created, run a while loop to check when they are finished running.

while($Runspaces | ? { -not $_.Result.IsCompleted }) {  # While the jobs haven’t completed
    Start-Sleep -Seconds 1
}

4. Once all Runspaces have finished running, get the results and dispose of the Runspace pool to clean up the resources.

$Results = $Runspaces | % { # Iterate through the results
    $_.Instance.EndInvoke($_.Result) 
}
$RunspacePool.Dispose() # Terminate the runspace pool

Code Example of Where Runspaces Can Be Used

As mentioned previously, Runspaces have many uses, so we’ll only cover one for this article. Below is a snippet for testing the connection to systems in an environment; this can use either IPs or hostnames.

$RunspacePool = [runspacefactory]::CreateRunspacePool(2,10)  # Set up runspace pool
$RunspacePool.Open()

$ServerList = @(
    "Server1", "Server2", "Server3", "Server4", "Server5"
)

$Runspaces = foreach ($Server in $ServerList) { 
    $PSInstance = [powershell]::Create().AddScript({  # Create new PowerShell instance
        param($Server)

        $Status = Test-Connection -ComputerName $Server -Count 1 -Quiet

        if (!$status) { 
            @{
                "Name" = $Server
                "Status" = 0
            }
        }
        else {
            @{
                "Name" = $Server
                "Status" = 1
            }
        } 
    }).AddParameter('Server', $Server)  # Add required parameters

    
    $Instance.RunspacePool = $RunspacePool  # Add the instance to the pool


    New-Object psobject -Property @{
        Instance = $Instance
        Result = $PSInstance.BeginInvoke() # Start executing
    }
}

while($Runspaces | ? { -not $_.Result.IsCompleted }) {  # While the jobs haven’t completed
    Start-Sleep -Seconds 1
}

$Results = $Runspaces | % { # Iterate through the results
    $_.Instance.EndInvoke($_.Result) 
} | % { New-Object object | Add-Member -NotePropertyMembers $_ -PassThru }

$RunspacePool.Dispose()  # Terminate the runspace pool

$Results  # Output the results

As you can see, we put all the pieces previously talked about together to create a single script. This is a simple script that will iterate through a list of servers and run a test-connection against each one. These connection attempts will run asynchronously and will output the results as seen below.

After the Runspaces are finished executing, each one is iteratively parsed for the output and creates a new PSObject from the Hashtable data output in each instance.


How Much Time Can Using Runspaces Save?

The amount of time saved using Runspaces will depend on the complexity of the script and what is trying to be improved. We have taken the example script above and run the measure-command cmdlet to determine what the time savings would be on just this simple script. Just to clarify, the system names used in this example script do not exist and each connection attempt will effectively timeout. So, we are measuring how long it takes to test a connection against 5 systems that don’t exist. However, you could encounter an actual scenario where the systems do exist, but are offline, which would simulate the same results. Below you can see the results of each test.


  • The output below was using Runspaces:

  • This output was not using Runspaces, which just used the same script that was executed inside of the Runspaces

Comparing the two, there is about a 9-second difference or about a 56% increase in speed. This is just a small sample size of 5 systems, but as the sample size grows, you will see much greater speed increases.


Conclusion

Do you have something in mind that you think you can apply Runspaces to yet? As we learned, PowerShell Runspaces are an incredibly powerful tool for optimizing the speed and efficiency of your scripts. By taking advantage of the multi-threading capabilities provided by Runspaces, PowerShell developers can drastically reduce script execution times, increase performance, and improve overall stability. With what we've covered in this article, you'll be well on your way to creating lightning-fast scripts that can handle even the most complex tasks with ease.


Still have questions or want to discuss your environment reach out to us at CDA. We’d love to discuss how we can help you with your business needs!


bottom of page