PowerShell Tip: Execute a PowerShell script remotly with multiple threads

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.

Anders Rødland

Anders Rødland started his IT career in 2006. My main focus is MS Configuration Manager and client management, and I have passed 17 Microsoft certifications since then. My main expertise is on client management with Microsoft Endpoint Manager: Intune and Configuration Manager. I also do a lot of work on the security side with Microsoft Defender for Endpoint. In addition to my Microsoft certification, I also have an ITIL v3 Foundation certification. This is my private blog and do not represent my employer. I use this to share information that I find useful. Sharing is caring.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.