function Invoke-ParallelServers { <# .SYNOPSIS Executes a script block against a list of servers in parallel with PSJobs + remote sessions. Can return results if the ReturnObjects switch is supplied .PARAMETER Servers [string[]] List of fully qualified server addresses to apply this across .PARAMETER Script [ScriptBlock] The scriptblock to execute .PARAMETER Arguments [object[]] The arguments to be passed into the inner scriptblock .PARAMETER NumThreads [int] Defualts to 30 .PARAMETER ReturnObjects [switch] Do we return results after the run? Legacy functionality is to swallow the Write-Output and return $XYZ values. Other "return" values like Write-Host streams continue to be returned in either option. .NOTES The arguments are passed to each job globally. If you want to pass different arguments to different jobs, format it into the object[] list argument. #> param( [Parameter(Mandatory=$true)] [string[]]$Servers, [Parameter(Mandatory=$true)] [object]$Script, [Parameter(Mandatory=$false)] [object[]]$Arguments = $null, [Parameter(Mandatory=$false)] [int]$NumThreads = 30, [Parameter(Mandatory=$false)] [switch]$ReturnObjects ) process { $jobs = @() # Define script block to create remote session, and execute the script block parameter on the remote host. $ScriptBlock = { param($server, $innerScript, $passedArguments) process { $remoteBlock = [scriptblock]::Create($innerScript) $session = New-PSSession $server -ErrorAction SilentlyContinue if($session.ComputerName -ne $server) { throw "Could not connect to $server" } return Invoke-Command -Session $session -ScriptBlock $remoteBlock -ArgumentList $passedArguments } end { Remove-PSSession $session } } $logLead = (Get-LogLeadName); # For each server. foreach($server in $Servers) { Write-Verbose ("$logLead : Starting job for host {0}" -f $server) $jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList $server,$Script,$Arguments $running = @($jobs | Where-Object {$_.State -in ('Running','NotStarted')}) while ($running.Count -ge $NumThreads -and $running.Count -ne 0) { (Wait-Job -Job $jobs -Any) | Out-Null $running = @($jobs | Where-Object {$_.State -in ('Running','NotStarted')}) } } (Wait-Job -Job $jobs) | Out-Null $results = @() # Receive-Job to output the logs. # SRE-13225 - Receive-Job dumps all of the output streams from a job to the current PS "host" # When one of those is an "error" it stops because our $ErrorActionPreference is "Stop" # To get all of the output from these parallelized jobs, we need to "Continue" # All the jobs have already finished executing at this point. This does not alter that. # This merely allows us to see all of the output (unless there's a throw?) $jobs | ForEach-Object { $results += (Receive-Job -Job $_ -ErrorAction Continue) } if($ReturnObjects) { return $results } } }