PowerShell Multithreading: A Deep Dive


The Best WordPress plugins!

1. WP Reset

2. WP 301 Redirects

3. WP Force SSL

As we have all seen, PowerShell scripting is the tool of choice for automation in almost every IT environment. However, there are many reasons why parallelization cannot be achieved with a single thread. In this blog post I’ll cover common issues that cause multithreading bottlenecks and how to efficiently use multiple threads.

The “powershell 5.1 multi-threading” is a new feature in PowerShell that allows for easier multithreading with the help of its asynchronous job queue system.

PowerShell Multithreading: A Deep Dive

Most individuals will eventually come across a situation that a simple PowerShell script can’t fix quickly enough. This may include gathering data from a large number of machines on your network or mass-creating new users in Active Directory. These are two excellent instances of how greater processing power may help your code run quicker. Let’s look at how we can address this problem using PowerShell multithreading!

A single-threaded PowerShell session is the default. It executes one command and then continues on to the next command. This is advantageous since it ensures that everything is repeatable and that less resources are used. But what if the operations it’s carrying out aren’t interdependent and you have extra CPU resources? It’s time to start thinking about multithreading in such situation.

In this tutorial, you’ll learn how to comprehend and apply several PowerShell multithreading methods to handle numerous data streams at the same time while keeping everything under one console.

Multithreading in PowerShell: An Overview

Multithreading allows you to perform many commands at the same time. Whereas PowerShell generally utilizes a single thread, there are several techniques to parallelize your function using multiple threads.

The fundamental advantage of multithreading is that it reduces the code’s runtime. This reduction in time comes at the cost of increased processing power. Many activities are executed at once during multithreading, necessitating greater system resources.

What if you wanted to add a single new user to Active Directory? Because just one command is being executed in this case, there is no need to multithread. When you need to generate 1000 new users, everything changes.

You’d have to execute the New-ADUser command 1000 times to create all of the users if you didn’t utilize multithreading. It’s possible that creating a new user takes three seconds. It would take just under an hour to generate all 1000 users. Instead of utilizing one thread to execute 1000 instructions, you may employ 100 threads, each of which executes ten commands. Instead of taking roughly 50 minutes, it now takes less than a minute!

Please keep in mind that the scaling will not be perfect. It will take a long time to spin up and dismantle down elements in code. PowerShell just needs to execute the code on a single thread, and it’s done. When working with several threads, the initial thread that was used to execute your console will be utilized to handle the others. At some point, merely maintaining all of the other threads in line will exhaust the initial thread.

PowerShell Multithreading Prerequisites

This post will show you how PowerShell multithreading works in practice. If you want to follow along, there are a few items you’ll need as well as some information on the surroundings.

  • Windows PowerShell version 3 or above — Unless otherwise specified, all code shown will run in Windows PowerShell version 3 or higher. For the examples, Windows PowerShell version 5.1 will be used.
  • Spare CPU and memory — To parallelize using PowerShell, you’ll need at least a little more CPU and memory. If you don’t have this, you may not see any improvements in performance.

First and foremost, fix your code!

Before you dig into using PowerShell multithreading to speed up your scripts, there are a few things you need do first. The first step is to make your code more efficient.

While you may throw additional resources at your code to make it run quicker, multithreading adds a lot of complication to the equation. If you can speed up your code without using multithreading, you should do so first.

Locate bottlenecks.

Finding out what is slowing down your code is one of the first stages towards parallelizing it. It’s possible that the code is sluggish due to faulty logic or unnecessary loops, in which case you may make some changes to allow for speedier execution before using multithreading.

Shifting your filtering to the left is a typical approach to speed up your code. If you’re dealing with a lot of data, whatever filtering you wish to conduct to reduce the quantity of data you’re dealing with should be done as soon as feasible. The code to retrieve the amount of CPU consumed by the svchost process is shown below.

In the sample below, all running processes are read and then a single process is filtered out (svchost). The CPU property is then selected, and the value is checked to ensure it is not null.

PS51> Get-Process | Where-Object {$_.ProcessName -eq ‘svchost’} | Select-Object CPU | Where-Object {$_.CPU -ne $null}

Compare the code above with the example below. Another example of code that produces the same result but is organized differently is shown below. The code below is simpler, as it moves all potential logic to the left of the pipe symbol. Get-Process will no longer provide processes that you aren’t interested in.

PS51> Get-Process -Name ‘svchost’ | Where-Object {$_.CPU -ne $null} | Select-Object CPU

The time difference between running the two lines from above is shown below. If you just execute this code once, the 117ms difference won’t be visible, but if you run it thousands of times, it will mount up.

time difference between executing the two lines from the previous paragraphtime difference between executing the two lines from the previous paragraph

Thread-Safe Code is a technique for writing code that is safe for many threads.

Next, double-check that your code is “thread-safe.” The phrase “thread-safe” refers to the fact that while one thread is executing code, another thread may execute the same code without causing a conflict.

Writing to the same file in two threads, for example, is not thread-safe since it won’t know what to put to the file first. Two threads reading from a file, on the other hand, are thread-safe since the file is not being altered. Both threads provide the same result.

The issue with non-thread-safe PowerShell multithreading code is that it may provide inconsistent results. It may function OK in certain cases because the threads simply happen to be in the correct place at the appropriate moment to avoid causing a dispute. Sometimes there may be a disagreement, which can make debugging the problem difficult owing to inconsistent errors.

If you’re just running two or three tasks at a time, they can simply happen to line up perfectly so that they’re all writing to the same file at the same time. When you grow the code up to 20 or 30 jobs, the chances of at least two of them trying to write at the same moment drop dramatically.

PSJobs and Parallel Execution

PSJobs is one of the simplest methods to multithread a script. Microsoft has cmdlets integrated into PSJobs. PowerShell. This is the main module. Microsoft is a company that makes software. PowerShell. Since version 3, the Core module has been included in all versions of PowerShell. This module’s commands enable you to execute code in the background while running other code in the forefront. All of the possible commands are listed here.

output from Get-Command *-Joboutput from Get-Command *-Job

Keeping a Record of Your Work

PSJobs are all located in one of eleven states. These are the states in which PowerShell handles jobs.

The most prevalent states in which a job may be found are shown below.

  • Completed – The task is complete, and the output data may be obtained or the job deleted.
  • Running — The work is presently in progress and cannot be stopped without causing it to halt. It is also not possible to obtain the output at this time.
  • Blocked — The task is still operating, but it is requesting information from the host before it can continue.
  • Failed – During the job’s execution, a termination error occurred.

The Get-Task command is used to determine the status of a job that has been begun. This command retrieves all of your jobs’ properties.

The output for a task with the status Completed is shown below. The code Start-Sleep 5 is executed inside a task using the Start-Job command in the example below. The Get-Job command is then used to get the job’s status.

PS51> Start-Job -Scriptblock {Start-Sleep 5} PS51> Get-Job

Output from the Get-Job commandOutput from the Get-Job command

When the job status changes to Completed, the code in the scriptblock has completed its execution. The HasMoreData attribute is likewise False, as you can see. This indicates that there was no output to offer after the operation was completed.

Some of the various statuses used to describe employment are listed below. You can see from the Command column that anything as simple as attempting to sleep for abc seconds resulted in an unsuccessful task.

All jobsAll jobs

New Job Creation

As you can see, the Start-Task command enables you to establish a new job that immediately begins running code. When you create a job, you provide a scriptblock that will be used for it. PSJob then generates a job with a unique ID number and begins to execute it.

The biggest advantage is that running the Start-Job command takes less time than running the scriptblock we’re using. Instead of requiring five seconds to finish the order, it only took.15 seconds to start the work, as seen in the figure below.

How long does it take to make a job?How long does it take to make a job?

Because it was operating in the background as a PSJob, it was able to perform the identical code in a fraction of the time. Instead of executing the code in the front and sleeping for five seconds, it took.15 seconds to set up and start running it in the background.

Getting Job Results

Occasionally, the job’s code generates output. The Receive-Job command may be used to get the output of that code. The Receive-Job command takes a PSJob as input and outputs the job’s output to the console. Everything that the job produced while it was running has been saved, thus when the task is retrieved, it produces everything that was saved at the time.

Running the code below is an example of this. This will create and start a task that will produce the message Hello World. The job’s output is then retrieved and sent to the console.

$Job = Start-Job -ScriptBlock; $Job = Start-Job -ScriptBlock; $Job = ‘Hello World,’ write-output Receive-Job $Job

Output from Receive-JobOutput from Receive-Job

Making Scheduled Tasks

A scheduled job is another another method to interact with PSJobs. Scheduled jobs are analogous to the Windows Task Scheduler-configurable scheduled tasks. Scheduled jobs allow you to simply schedule PowerShell scriptblocks in a scheduled task. You may execute a PSJob in the background depending on triggers using a scheduled job.

Job-related triggers

Job-related triggers can be things like a specific time, when a user logs on, when the system boots and many others. You can also have the triggers repeat at intervals. All of these triggers are defined with the New-JobTrigger command. This command is used to specify a trigger that will run the scheduled job. A scheduled job without a trigger has to be run manually, but each job can have many triggers.

You’d still have a script block, just as you would with a regular PSJob, in addition to the trigger. You’d use the Register-ScheduledJob command to create the task after you had both the trigger and script block, as described in the following section. This command is used to define the scheduled job’s properties, such as the scriptblock that will be executed and triggers generated using the New-JobTrigger command.


Maybe you need some PowerShell code that runs every time a user connects onto a PC. This may be done as a scheduled task.

To do so, first create a trigger using New-JobTrigger and then create the scheduled task as shown below. Every time someone signs in, this scheduled process will write a line to a log file.

New $Trigger -AtLogon -JobTrigger $Script = “User $env:USERNAME logged in at $(Get-Date -Format ‘y-M-d H:mm:ss’)” $Script = “User $env:USERNAME logged in at $(Get-Date -Format ‘y-M-d H:mm:ss’)” | Out-File -FilePath C:TempLogin.log -Append | Out-File -FilePath C:TempLogin.log -Append ScriptBlock -Name Log Login -Register-ScheduledJob -Trigger $Script $Trigger

When you execute the following commands, you’ll receive a similar output to when you create a new job, including the job ID, scriptblock, and a few other properties, as seen below.

Adding a scheduled task to your calendarAdding a scheduled task to your calendar

The image below shows that it has recorded the login attempts after a few tries.

Login.log is an example of a text file.Login.log is an example of a text file.

Taking Advantage of the AsJob Parameter

The AsJob argument, which is incorporated into many PowerShell tasks, is another method to utilize jobs. Because there are so many distinct commands, you may use Get-Command to locate them all, as seen below.

PS51> Get-Command -ParameterName AsJob

Invoke-Command is one of the most commonly used commands. Normally, when you perform this command, it will instantly begin running a command. Some commands will return immediately, enabling you to continue doing what you were doing, while others will wait until the command is completed before returning.

The AsJob option does exactly what it says it does: it executes the executed command as a job rather than synchronously in the terminal.

While AsJob may be used on the local system most of the time, Invoke-Command does not have a native option to execute on the local machine. There is a solution by setting the ComputerName parameter value to Localhost. This workaround is shown in the example below.

PS51> Invoke-Command -ScriptBlock {Start-Sleep 5} -ComputerName localhost

To see the AsJob argument in action, the sample below utilizes Invoke-Command to sleep for five seconds before repeating the command with AsJob to demonstrate the difference in execution timings.

PS51> Measure-Command {Invoke-Command -ScriptBlock {Start-Sleep 5}} PS51> Measure-Command {Invoke-Command -ScriptBlock {Start-Sleep 5} -AsJob -ComputerName localhost}

comparing job speedscomparing job speeds

Runspaces are a lot like jobs, except they’re a lot faster!

Until now, you’ve only learned about how to utilize PowerShell’s built-in commands to employ extra threads. Using a distinct runspace is another way to multithread your script.

Runspaces are the confined areas in which the PowerShell thread(s) execute. While the PowerShell console’s runspace is limited to a single thread, you may utilize extra runspaces to enable for the usage of multiple threads.

PSJobs vs. Runspace

While there are many similarities between a runspace and a PSJob, there are also significant performance variations. The most significant distinction between runspaces and PSjobs is the amount of time it takes to build up and tear down each one.

The PSjob that was generated in the previous part took roughly 150ms to load. Because the task’s scriptblock didn’t include much code and no other variables were supplied to the process, this is a best-case scenario.

A runspace is generated ahead of time, in contrast to the PSJob creation. Before any code is introduced, the bulk of the time it takes to spin up a runspace task is handled.

Here’s an example of executing the same command in the runspace that we used for the PSjob.

Counting the number of jobs created in a certain amount of timeCounting the number of jobs created in a certain amount of time

The code for the runspace version, on the other hand, is shown below. As you can see, there is a lot more code to do the same thing. However, the additional code saves approximately 3/4 of the time, enabling the command to start in 36ms instead of 148ms.

$Runspace = [runspacefactory]::CreateRunspace; $Runspace = [runspacefactory]::CreateRunspace; $Runspace = () $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $PowerShell.Runspace = $PowerShell.Runspace = $PowerShell.Run $Runspace $Runspace.Open() $PowerShell.AddScript(Start-Sleep 5) $PowerShell.AddScript(Start-Sleep 5) $PowerShell.AddScript(Start- $PowerShell.BeginInvoke()

calculating the time it takes to create a runspacecalculating the time it takes to create a runspace

A Walkthrough of Running Runspaces

Because there is no longer any PowerShell command hand-holding, using runspaces might be a difficult undertaking at first. You’ll have to work directly with.NET classes. Let’s have a look at how to build a runspace with PowerShell in this part.

In this tutorial, you’ll learn how to build a distinct runspace and PowerShell instance from your PowerShell console. After that, you’ll assign the new runspace to the new PowerShell instance and start writing code for it.

Construct the Runspace

The first thing you should do is set up your new runspace. The runspacefactory class is used to do this. Save this to a variable, like the one below, so you can refer to it later.

$Runspace = [runspacefactory]::CreateRunspace; $Runspace = [runspacefactory]::CreateRunspace; $Runspace = ()

Assign the runspace to a PowerShell instance to run PowerShell code now that it’s been created. You’ll need the powershell class for this, and just as with the runspace, you’ll need to save it to a variable like the one seen below.

$PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = ()

After that, add the runspace to your PowerShell instance, open it so you can run code in it, and then add your scriptblock. This is seen below with a five-second sleep scriptblock.

$Runspace = $PowerShell.Runspace $PowerShell.AddScript(Start-Sleep 5) $Runspace.Open()

Run the Runspace command.

The scriptblock has not yet been executed. So far, all that has been accomplished is the definition of the runspace. You have two choices for starting the scriptblock.

  • Invoke() – The Invoke() function executes the scriptblock in the runspace but does not return to the console until the runspace has finished. This is useful for testing to ensure that your code is running correctly before releasing it.
  • BeginInvoke() — If you want to notice a speed boost, you’ll want to use the BeginInvoke() function. This will start the scriptblock in the runspace and return you to the terminal right away.

Store the output of BeginInvoke() to a variable since it will be needed to monitor the state of the scriptblock in the runspace, as seen below.

$Job = $PowerShell.BeginInvoke; $Job = $PowerShell.BeginInvoke; $Job = ()

Once you’ve saved the output of BeginInvoke() to a variable, you can use that variable to verify the job’s status, as seen in the IsCompleted property below.

the task has been finishedthe task has been finished

Another reason to save the result in a variable is because, unlike the Invoke() function, BeginInvoke() does not return the output automatically after the procedure is done. To do so, use the EndInvoke() function after the process is finished.

There would be no output in this case, but you would use the command below to stop the invocation.


You should always end the runspace after all of the tasks you queued in it have completed. PowerShell’s automatic trash collection procedure will be able to clear away any unneeded resources. The command you’d use to do this is listed below.

Taking Advantage of Pool of Runspaces

While employing a runspace improves speed, it does so at the expense of a single thread’s main constraint. The usage of many threads by Pool of Runspaces shines here.

You were only utilizing two runspaces in the previous part. You only utilized one for the PowerShell console itself, as well as the one you manually built. Multiple runspaces may be controlled in the background using a single variable using Pool of Runspaces.

While numerous runspace objects may be used to achieve this multi-runspace functionality, having a Pool of Runspace makes maintenance considerably simpler.

Pool of Runspaces are set up differently than single runspaces. One of the main distinctions is that you may specify the maximum number of threads that the Pool of Runspace can employ. A single thread can only be used in a single runspace, but with a pool, you may set the maximum number of threads the pool can handle.

The number of threads in a Pool of Runspace that is suggested is determined by the number of tasks being done and the machine that is executing the code. While raising the maximum number of threads will not slow you down in most circumstances, it may also provide little advantage.

Demonstration of Pool of Runspace Speed

To demonstrate how a Pool of Runspace may outperform a single runspace, consider creating 10 new files. If you did this work in a single runspace, you’d start with the first file, then go on to the second, then the third, and so on until all 10 were produced. For this example, the scriptblock may look like this. This scriptblock would be fed 10 file names in a loop, and they would all be created.

$Scriptblock = param($Name) New-Item -Name $Scriptblock = param($Name) New-Item -Name $Scriptblock = param($Name) New -ItemType File $Name

A script block is described in the example below, which has a small script that receives a name and produces a file with that name. A Pool of Runspace with a maximum of 5 threads is generated.

The loop then repeats 10 times, each time assigning the number of iterations to $ . So the first iteration would have 1, the second would have 2, and so on.

The loop generates a PowerShell object, assigns the script block and the script parameter, and then begins the procedure.

Finally, it will wait for all of the queue jobs to complete before exiting the loop.

$Scriptblock = param($Name) New-Item -Name $Scriptblock = param($Name) New-Item -Name $Scriptblock = param($Name) New -ItemType File $Name $MaxThreads = 5 $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads) $RunspacePool.Open() $Jobs = @() 1..10 | Foreach-Object { $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $PowerShell.RunspacePool = $RunspacePool $PowerShell.AddScript($ScriptBlock).AddArgument($_) $Jobs += $PowerShell.BeginInvoke() } while ($Jobs.IsCompleted -contains $false) { Start-Sleep 1 }

It will now produce five threads at a time, rather than one at a time. You’d have to construct and maintain five distinct runspaces and Powershell instances if you didn’t use Pool of Runspaces. This management rapidly devolves into a shambles.

Instead, you may use the same code block and loop to construct a Pool of Runspace and a PowerShell instance. The runspace, on the other hand, will scale up to employ all five of those threads independently.

Creating Pool of Runspaces is a process that involves putting together a collection of

The process of creating a Pool of Runspace is quite similar to the process of creating a runspace in the previous section. An example of how to accomplish it is shown below. The method of adding a scriptblock and invoking it is similar to that of a runspace. The Pool of Runspace is established with a maximum of five threads, as you can see below.

$MaxThreads = 5 $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads) $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $PowerShell.RunspacePool = $RunspacePool $RunspacePool.Open()

Speed Comparison between Runspaces and Pool of Runspaces

Create a runspace and use the Start-Sleep command to demonstrate the difference between a runspace and a Pool of Runspace. However, it must be repeated ten times this time. A runspace is established, as you can see in the code below, that will sleep for 5 seconds.

$Runspace = [runspacefactory]::CreateRunspace; $Runspace = [runspacefactory]::CreateRunspace; $Runspace = () $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $Runspace = $PowerShell.Runspace $PowerShell.AddScript(Start-Sleep 5) $Runspace.Open() 1..10 | Foreach-Object { $Job = $PowerShell.BeginInvoke; $Job = $PowerShell.BeginInvoke; $Job = () while ($Job.IsCompleted -eq $false) {Start-Sleep -Milliseconds 100} }

Note that since you are using a single runspace, that you will have to wait until it is completed before another invoke can be started. This is why there is a 100ms sleep added until the task has been finished. While this can be reduced, you will see diminishing returns as you will be spending more time checking if the job is done than waiting for the job to finish.

It took roughly 51 seconds to complete 10 sets of 5 second naps, as seen in the sample below.

The performance of constructing runspaces is being measured.The performance of constructing runspaces is being measured.

Switch to a Pool of Runspace instead of employing a single runspace. The code that will be executed is listed below. When utilizing a Pool of Runspace, you can see that there are a few changes between the two in the code below.

$RunspacePool = [runspacefactory]::CreateRunspacePool(1, 5) $RunspacePool.Open() $Jobs = @() 1..10 | Foreach-Object { $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $PowerShell.RunspacePool = $RunspacePool $PowerShell.AddScript({Start-Sleep 5}) $Jobs += $PowerShell.BeginInvoke() } while ($Jobs.IsCompleted -contains $false) {Start-Sleep -Milliseconds 100}

As you can see in the screenshot below, this takes just over 10 seconds to finish, which is a significant improvement over the 51 seconds required by the single runspace.

When it comes to runspaces vs. Pool of Runspaces,When it comes to runspaces vs. Pool of Runspaces,

In these situations, the distinction between a runspace and a Pool of Runspace is summarized below.

Property Runspace Pool of Runspace
Delay Delay Before moving on to the next assignment, I have to wait for each one to complete. Begin all of the tasks and then wait until they are all completed.
Number of Threads One Five
Runtime Time: 50.8 seconds Time: 10.1 seconds

PoshRSJob Makes It Easier to Enter Runspaces

When programming, it’s common to do what seems more comfortable and tolerate a slight performance loss. This might be due to the fact that it makes the code simpler to write or understand, or it could just be a personal choice.

The similar situation exists with PowerShell, where some individuals choose to utilize PSJobs over runspaces due to their simplicity. There are a few things that may be done to bridge the gap and get improved performance without making it too difficult to use.

There is a widely used module called PoshRSJob that contains modules that match the style of normal PSJobs but with the added benefit of using runspaces. Instead of having to specify all of the code to Construct the Runspace and the powershell object, the PoshRSJob module handles doing all of that when you run the commands.

Run the command below in an administrator PowerShell session to install the module.

After installing the module, you’ll see that the commands are the same as PSJob commands with an RS prefix. It’s Start-RSJob instead of Start-Job. It’s Get-RSJob instead of Get-Job.

The example below shows how to perform the identical command in a PSJob and subsequently in an RSJob. As you can see, their grammar and output are quite similar, yet they are not identical.

Run the identical command in both a PSJob and an RSJob.Run the identical command in both a PSJob and an RSJob.

The code below may be used to compare the speed differences between a PSJob and an RSJob.

Start-Job -ScriptBlock Start-Sleep 5 Measure-Command Start-RSJob -ScriptBlock Start-Sleep 5 Measure-Command

Because the RSJobs are still utilizing runspaces behind the covers, there is a significant performance difference, as seen below.

Because the RSJobs are still utilizing runspaces behind the covers, there is a significant performance difference.Because the RSJobs are still utilizing runspaces behind the covers, there is a significant performance difference.

Foreach-Object -Parallel is a command that returns a list of objects.

PowerShell users have been clamoring for a simpler, built-in solution to multithread a process. That’s where the parallel switch came from.

PowerShell 7 is still in preview as of this writing, however they have introduced a Parallel argument to the Foreach-Object function. This procedure parallelizes the code using runspaces, with the Foreach-Object scriptblock serving as the runspace scriptblock.

While the finer points are still being ironed out, this might be a more convenient approach to utilize runspaces in the future. You may swiftly loop over multiple sets of sleeps, as seen below.

Measure-Command {1..10 | Foreach-Object {Start-Sleep 5}} Measure-Command {1..10 | Foreach-Object -Parallel is a command that returns a list of objects. {Start-Sleep 5}}

Commands are looped through and measured.Commands are looped through and measured.

Multi-Challenges Threading’s

While multithreading has sounded fantastic up until this point, it isn’t completely true. There are several difficulties that occur with multithreading any programming.

Variables in Action

One of the most apparent problems with multithreading is that variables cannot be shared without being sent as arguments. A synchronized hashtable is the one exception, but that’s a topic for another day.

PSJobs and runspaces don’t have access to existing variables, and there’s no way to interact with variables across runspaces from your console.

This makes dynamically supplying information to these processes very difficult. Depending on the kind of multithreading you’re using, the response will be different.

The ArgumentList argument may be used with the PoshRSJob module’s Start-Job and Start-RSJob functions to give a list of objects that will be supplied as parameters to the scriptblock in the order you specify them. Here are some samples of PSJobs and RSJobs commands.


Start-Job -Scriptblock $Text $Text$Text$Text$Text$Text$Text$Text$Text$Text$Tex “Hello world!” Write-Output $Text -ArgumentList


-Scriptblock -Start-RSJob -param ($Text) “Hello world!” Write-Output $Text -ArgumentList

Navtive runspaces do not provide the same level of comfort. Instead, you must utilize the PowerShell object’s AddArgument() function. Here’s an example of how it may appear for each.


$Runspace = [runspacefactory]::CreateRunspace; $Runspace = [runspacefactory]::CreateRunspace; $Runspace = () $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $PowerShell.Runspace = $Runspace $Runspace.Open() $PowerShell.AddScript({param ($Text) Write-Output $Text}) $PowerShell.AddArgument(“Hello world!”) $PowerShell.BeginInvoke()

While Pool of Runspaces work the same, below is an example of how to add an argument to a Pool of Runspace.

$MaxThreads = 5 $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads) $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = () $PowerShell.RunspacePool = $RunspacePool $RunspacePool.Open() $PowerShell.AddScript({param ($Text) Write-Output $Text}) $PowerShell.AddArgument(“Hello world!”) $PowerShell.BeginInvoke()


Multithreading comes with its own set of logging issues. Because one thread runs independently of the others, they can’t all log in to the same location. If you tried to log to a file with several threads, you’d find that only one thread could write to the file at a time. This might cause your code to slow down or even crash.

As an example, below is some code to attempt to log 100 times to a single file using 5 threads in a Pool of Runspace.

$RunspacePool = [runspacefactory]::CreateRunspacePool(1, 5) $RunspacePool.Open() 1..100 | Foreach-Object { $PowerShell = [powershell]::Create; $PowerShell = [powershell]::Create; $PowerShell = ().AddScript({‘Hello’ | Out-File -Append -FilePath .Test.txt}) $PowerShell.RunspacePool = $RunspacePool $PowerShell.BeginInvoke() } $RunspacePool.Close()

There are no problems in the output, but if you look at the size of the text file, you can see that not all 100 tasks completed satisfactorily.

Get-Content -Path .Test.txt).Count” class=”wp-image-3241″/>(Get-Content -Path .Test.txt).Count

Logging to distinct files is one approach to get around this. This solves the file locking issue, but you’ll end up with a lot of log files to comb through to find out what went wrong.

Another option is to let part of the output’s timing be off and only report what a process performed after it has completed. This enables you to keep everything serialized throughout your original session, but you lose certain information since you don’t know what happened in what sequence.


While multithreading may result in significant speed advantages, it can also result in hassles. While certain workloads will benefit considerably, others may not. There are numerous advantages and disadvantages to utilizing multithreading, but when done properly, it may substantially decrease the time it takes for your code to function.

Additional Reading

The “powershell runspace example” is a script that showcases how to use multithreading in PowerShell. It is an excellent resource for those who are looking to learn more about the topic.

Frequently Asked Questions

Is multithreading possible in PowerShell?

A: Multithreading is possible with PowerShell, although its not a feature that has been implemented yet.

Is it possible to perform multi threading in bash or PowerShell?

A: You can multi-thread in C, Java or Python.

What is Runspace in PowerShell?

A: A runspace is a PowerShell instance with its own hosting process. Its the environment you are running your scripts in, so it can have different files and folders for each of these individual processes.

Related Tags

  • powershell threading
  • powershell start-job
  • powershell multi thread foreach
  • powershell parallel execution
  • powershell foreach parallel

Table of Content