How to multithread scripts in powershell

One of the greatest things about powershell is it's ability to save you hours or manual labour administering servers and domain computers by creating automation scrips. Once we have mastered the art of automation in powershell we soon realise we could make the automation a thousand times faster if only we could multithread our scripts, and of course, we can.

Import the 'Start_Multithread' module

Rather than spend time trying to create the mechanics for the multithreading the first task we have is to install a module called 'start-multithread' by Ryan Witschger. You can get the module from Ryan's blog here or cut and paste it from the window below and save it as a .PSM1 file and import it into powershell using the 'import-module' command. If you use the multi-thread script a lot (I am sure you will) please give thanks on Ryans blog page.


Param($Command=$(Read-Host"Enterthescriptfile"),
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]$ObjectList,
$InputParam=$Null,
$MaxThreads=20,
$SleepTimer=200,
$MaxResultTime=120,
[HashTable]$AddParam=@{},
[Array]$AddSwitch=@()
)

Begin{
$ISS=[system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RunspacePool=[runspacefactory]::CreateRunspacePool(1,$MaxThreads,$ISS,$Host)
$RunspacePool.Open()

If($(Get-Command|Select-ObjectName)-match$Command){
$Code=$Null
}Else{
$OFS="`r`n"
$Code=[ScriptBlock]::Create($(Get-Content$Command))
Remove-VariableOFS
}
$Jobs=@()
}

Process{
Write-Progress-Activity"Preloadingthreads"-Status"StartingJob$($jobs.count)"
ForEach($Objectin$ObjectList){
If($Code-eq$Null){
$PowershellThread=[powershell]::Create().AddCommand($Command)
}Else{
$PowershellThread=[powershell]::Create().AddScript($Code)
}
If($InputParam-ne$Null){
$PowershellThread.AddParameter($InputParam,$Object.ToString())|out-null
}Else{
$PowershellThread.AddArgument($Object.ToString())|out-null
}
ForEach($Keyin$AddParam.Keys){
$PowershellThread.AddParameter($Key,$AddParam.$key)|out-null
}
ForEach($Switchin$AddSwitch){
$Switch
$PowershellThread.AddParameter($Switch)|out-null
}
$PowershellThread.RunspacePool=$RunspacePool
$Handle=$PowershellThread.BeginInvoke()
$Job=""|Select-ObjectHandle,Thread,object
$Job.Handle=$Handle
$Job.Thread=$PowershellThread
$Job.Object=$Object.ToString()
$Jobs+=$Job
}

}

End{
$ResultTimer=Get-Date
While(@($Jobs|Where-Object{$_.Handle-ne$Null}).count-gt0){

$Remaining="$($($Jobs|Where-Object{$_.Handle.IsCompleted-eq$False}).object)"
If($Remaining.Length-gt60){
$Remaining=$Remaining.Substring(0,60)+"..."
}
Write-Progress`
-Activity"WaitingforJobs-$($MaxThreads-$($RunspacePool.GetAvailableRunspaces()))of$MaxThreadsthreadsrunning"`
-PercentComplete(($Jobs.count-$($($Jobs|Where-Object{$_.Handle.IsCompleted-eq$False}).count))/$Jobs.Count*100)`
-Status"$(@($($Jobs|Where-Object{$_.Handle.IsCompleted-eq$False})).count)remaining-$remaining"

ForEach($Jobin$($Jobs|Where-Object{$_.Handle.IsCompleted-eq$True})){
$Job.Thread.EndInvoke($Job.Handle)
$Job.Thread.Dispose()
$Job.Thread=$Null
$Job.Handle=$Null
$ResultTimer=Get-Date
}
If(($(Get-Date)-$ResultTimer).totalseconds-gt$MaxResultTime){
Write-Error"Childscriptappearstobefrozen,tryincreasingMaxResultTime"
Exit
}
Start-Sleep-Milliseconds$SleepTimer

}
$RunspacePool.Close()|Out-Null
$RunspacePool.Dispose()|Out-Null
}


Create an array of computers

Now we have our 'Start-Mutlithread' module in place we can test how the multi threading works. In this example we will create a list of computers to perform the task on


$list = "server1","server2","server3"


Start the multithread script

The first line of our script starts the multithread operation. The ($computername) variable is going to pull the name from our list, it is important to know this in order to script this correctly


Start-Multithread -Script {param ($computername)


Transfer the computer name to a new variable

I prefer to get the name from the $computer variable into a new variable but this isn't neccesary, you can go ahead and use the $computer variable as you wish but just know that in each thread the $computer variable will be unique to every other running thread


$computer = $computername


Create the script tp perform on each machine in the list

Here we compose the script we would like to perform on each computer in our list, I have just used a simple start-service command here, but you can make the script as complex as you like


Get-Service -Name WinRM -ComputerName $computer | Start-Service


The final piece

The final piece to the puzzle is to close the script, it requires two further pieces of information here.
1. Tell the script where we will be getting out list of computers from, so in this example $list
2. How many threads we want to run at any one time.
I have tried this with 50 threads and it worked perfectly, I am not sure how many it can technically cope with but Ryan's example showed 200 threads!.


} -ComputerName $list -MaxThreads 200


Comments
// Collect comments ''
Search