Here is a PowerShell script that contains a function that I use to remotely execute scripts on multiple machines at the same time.
The script was originally developed by Ryan Witschger @ www.get-blog.com, but that website doesn’t exist anymore so I’m publishing my modified version of it.
Invoke-Multithreader.ps1
Function Invoke-Multithreader {
Param($Command = $(Read-Host "Enter the script file"),
[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()
$Jobs = @()
}
Process{
Write-Progress -Activity "Preloading threads" -Status "Starting Job $($jobs.count)"
ForEach ($Object in $ObjectList) { $PowershellThread = [powershell]::Create().AddCommand($Command)
If ($InputParam -ne $Null) { $PowershellThread.AddParameter($InputParam, $Object.ToString()) | out-null }
Else { $PowershellThread.AddArgument($Object.ToString()) | out-null }
ForEach($Key in $AddParam.Keys) { $PowershellThread.AddParameter($Key, $AddParam.$key) | out-null }
ForEach($Switch in $AddSwitch) {
$Switch
$PowershellThread.AddParameter($Switch) | out-null
}
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = "" | Select-Object Handle, 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 -gt 0) {
$Remaining = "$($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).object)"
If ($Remaining.Length -gt 60){ $Remaining = $Remaining.Substring(0,60) + "..." }
Write-Progress `
-Activity "Waiting for Jobs - $($MaxThreads - $($RunspacePool.GetAvailableRunspaces())) of $MaxThreads threads running" `
-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 ($Job in $($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 "Child script appears to be frozen, try increasing MaxResultTime"
Exit
}
Start-Sleep -Milliseconds $SleepTimer
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
}
Usage:
$Computers = “Server01”, “Server02”, “Server03”
Invoke-Multithreader -Command “script.ps1” -ObjectList $Computers -MaxThreads 4 -InputParam Computer -AddParam @{“Parameter1” = $Parameter1Value}
The -InputParam and -AddParam parameters are optional, but required if the script you try to execute have parameters you want to pass along.