<# .SYNOPSIS A parallel ForEach that uses runspaces .PARAMETER ScriptBlock ScriptBlock to execute for each InputObject .PARAMETER ScriptFile Script file to execute for each InputObject .PARAMETER InputObject Object(s) to run script against in parallel .PARAMETER Throttle Maximum number of threads to run at one time. Default: 5 .PARAMETER Timeout Stop each thread after this many minutes. Default: 0 (Do not timeout) WARNING: This parameter should be used as a failsafe only Set it for roughly the entire duration you expect for all threads to complete .PARAMETER SleepTimer When looping through open threads, wait this many milliseconds before looping again. Default: 200 .PARAMETER ProgressId Identity of the progress bar to use for display. Default: 0 .PARAMETER Activity String to pass to the progress bar for the -Activity parameter. No progress is displayed unless this is specified. Default: Blank and do not show progress. .EXAMPLE (1..50) | ForEach-Parallel -Throttle 4 -ScriptBlock { $this; Start-Sleep -Seconds (Get-Random -Minimum 0 -Maximum 5) } -Activity "One to fifty" Send the number 1 through 50 to the scriptblock. For each, display the number and then sleep for 0 to 5 seconds. Display a progress bar. Only execute 4 threads concurrently. .EXAMPLE Get-Content .\servers.txt | Foreach-Parallel -Throttle 20 -Timeout 10 -SleepTimer 200 -ScriptFile .\query.ps1 -Verbose Run query.ps1 against each computer in the servers.txt file. Run 20 concurrently, timeout a thread if it takes longer than 10 minutes to run, give verbose output. .FUNCTIONALITY PowerShell Language .NOTES Credit to Tome Tanasovski for the original ForEach-Parallel, from which this script has been modified. http://powertoe.wordpress.com/2012/05/03/foreach-parallel/ Added: - Progress bar - Verbose output - Changed $_ to $this within the scriptblock to avoid pipeline contention #> [cmdletbinding()] PARAM ( [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')] [scriptblock]$ScriptBlock, [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')] [ValidateScript({test-path $_ -pathtype leaf})] $ScriptFile, [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [PSObject]$InputObject, [int]$Throttle = 5, [double]$SleepTimer = 200, [double]$Timeout = 0, [int]$ProgressId = 0, [string]$Activity = 'Default' ) BEGIN { $stopWatch = [Diagnostics.Stopwatch]::StartNew() if ($Activity -ne 'Default') { Write-Progress -Id $ProgressId -Activity $Activity -Status "Preparing" -PercentComplete 0 -CurrentOperation "Elapsed time: $($stopWatch.Elapsed.toString().subString(0,8))" } #Build the scriptblock depending on the parameter used switch ($PSCmdlet.ParameterSetName) { 'ScriptBlock' {$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param(`$this)`r`n" + $Scriptblock.ToString())} 'ScriptFile' {$scriptblock = [scriptblock]::Create($(get-content $ScriptFile | out-string))} Default {Write-Error ("Must provide ScriptBlock or ScriptFile"); Return} } #Define the initial sessionstate, create the runspacepool Write-Verbose "Creating runspace pool with $Throttle threads" $sessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault() $pool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionState, $host) $pool.open() #array to hold details on each thread $threads = @() #If inputObject is bound get a total count and set bound to true $bound = $false if( $PSBoundParameters.ContainsKey("inputObject") ) { $bound = $true $totalCount = $inputObject.count } if ($Activity -ne 'Default') { Write-Progress -Id $ProgressId -Activity $Activity -Status "Creating threads" -CurrentOperation "Elapsed time: $($stopWatch.Elapsed.toString().subString(0,8))" } [int]$ProgressTotal = 0 # Total number of threads created [int]$ProgressCount = 0 # Number of threads completed $runtemplate = @' #For each pipeline object, create a new powershell instance, add to runspacepool $powershell = [powershell]::Create().AddScript($ScriptBlock).AddArgument($InputObject) $powershell.runspacepool=$pool $startTime = Get-Date #add references to inputobject, instance, handle and startTime to threads array $threads += New-Object psobject -Property @{ Object = $inputObject; instance = $powershell; handle = $powershell.begininvoke(); startTime = $startTime } $ProgressTotal++ Write-Verbose "Added $inputobject to the runspacepool at $startTime" if ($Activity -ne 'Default') { Write-Progress -Id $ProgressId -Activity $Activity -Status "$ProgressTotal threads created" -CurrentOperation "Elapsed time: $($stopWatch.Elapsed.toString().subString(0,8))" -PercentComplete ([math]::Min($ProgressTotal,95)) } '@ } PROCESS { if($bound) { $run = $runtemplate -replace 'inputObject', 'object' foreach($object in $inputObject) { Invoke-Expression -command $run } } else { Invoke-Expression -command $run } } END { $notdone = $true $ProgressTotal = $threads.count; $ProgressCount = 0 if ($Activity -ne 'Default') { Write-Progress -Id $ProgressId -Activity $Activity -Status "$ProgressCount of $ProgressTotal threads completed" -CurrentOperation "Elapsed time: $($stopWatch.Elapsed.toString().subString(0,8))" -PercentComplete (100*$ProgressCount/$ProgressTotal) } #Loop through threads. while ($notdone) { $notdone = $false for ($i=0; $i -lt $threads.count; $i++) { $thread = $threads[$i] if ($thread) { #If thread is complete, dispose of it. if ($thread.handle.iscompleted) { Write-verbose "Closing thread for $($thread.Object)" $thread.instance.endinvoke($thread.handle) $thread.instance.dispose() $threads[$i] = $null $ProgressCount++ } #Thread exceeded maxruntime timeout threshold elseif( $Timeout -ne 0 -and ( (get-date) - $thread.startTime ).totalminutes -gt $Timeout ) { Write-Error "Closing thread for $($thread.Object): Thread exceeded $Timeout minute limit" -TargetObject $thread.inputObject $thread.instance.dispose() $threads[$i] = $null $ProgressCount++ } #Thread is running, loop again! else { $notdone = $true } } } if ($Activity -ne 'Default') {Write-Progress -Id $ProgressId -Activity $Activity -Status "$ProgressCount of $ProgressTotal threads completed" -CurrentOperation "Elapsed time: $($stopWatch.Elapsed.toString().subString(0,8))" -PercentComplete (100*$ProgressCount/$ProgressTotal) } Start-Sleep -Milliseconds $SleepTimer } if ($Activity -ne 'Default') { Write-Progress -Id $ProgressId -Activity $Activity -Status "Completed" -CurrentOperation "Elapsed time: $($stopWatch.Elapsed.toString().subString(0,8))" -Completed } }