I have a PowerShell script that performs remote execution of a script on multiple servers. The script checks whether a particular script exists on each server, and if it does, it invokes that script remotely. I want to optimize the script for performance and memory usage, particularly when dealing with a large number of servers. Below is the code I'm currently using.
Explanation of Code Sections:
Server List and Initialization:
The list of servers ($servers) is defined at the beginning of the script. It holds the names or IP addresses of the remote servers where the script will be executed. We also initialize several variables such as$resultsto store the results,$missingScriptServersfor servers missing the script, and counters for success and failure ($successCountand$failureCount).Checking Script Existence:
The functionCheck-ScriptExistschecks if the specified script ($scriptPath) exists on each remote server. It uses PowerShell remoting (New-PSSessionandInvoke-Command) to check if the script file exists on the remote server. If the script exists, the function returns$true; otherwise, it returns$false.Invoking the Script Remotely:
TheInvoke-ScriptOnServerfunction is responsible for executing the script remotely on the server. If the script exists, this function executes it usingInvoke-Command. If an error occurs during the execution, it returns1to indicate failure.Parallel Execution of Remote Commands:
The script checks each server in the list. For servers where the script exists, it creates a background job usingStart-Job. This allows the script to invoke the remote script in parallel on multiple servers, which improves performance by not waiting for each server to finish before moving on to the next.Collecting and Displaying Results:
After starting the jobs for remote execution, the script waits for each job to complete usingWait-Joband retrieves the result withReceive-Job. It then removes the job withRemove-Jobto free up resources. The results are stored in$results, and the success and failure counts are updated accordingly. Finally, the script outputs the results in a table format and displays the success/failure counts.# Define the list of server names or IP addresses $servers = @( "server1", "server2", "server3" # Add more servers as needed ) # Path to the script on the servers $scriptPath = "D:\your_script.ps1" # Initialize arrays to hold result objects and counts for success and failure $results = @() $missingScriptServers = @() $successCount = 0 $failureCount = 0 # Function to check if the script exists on the server function Check-ScriptExists { param ( [string]$Server, [string]$ScriptPath ) try { $session = New-PSSession -ComputerName $Server $exists = Invoke-Command -Session $session -ScriptBlock { param($ScriptPath) Test-Path -Path $ScriptPath } -ArgumentList $ScriptPath Remove-PSSession -Session $session return $exists } catch { return $false } } # Function to invoke the script on a remote server function Invoke-ScriptOnServer { param ( [string]$Server, [string]$ScriptPath ) try { $session = New-PSSession -ComputerName $Server $response = Invoke-Command -Session $session -ScriptBlock { param($ScriptPath) & $ScriptPath return $LASTEXITCODE } -ArgumentList $ScriptPath Remove-PSSession -Session $session return $response } catch { return 1 # Return failure exit code in case of error } } # Check for script existence and start jobs in parallel $jobs = @() foreach ($server in $servers) { $scriptExists = Check-ScriptExists -Server $server -ScriptPath $scriptPath if ($scriptExists) { $jobs += Start-Job -ScriptBlock { param($server, $scriptPath) Invoke-ScriptOnServer -Server $server -ScriptPath $scriptPath } -ArgumentList $server, $scriptPath } else { $missingScriptServers += $server } } # Wait for all jobs to complete and collect results foreach ($job in $jobs) { $job | Wait-Job $response = $job | Receive-Job $job | Remove-Job # Update success/failure count based on the response code if ($response -eq 0) { $successCount++ } else { $failureCount++ } $results += [pscustomobject]@{ Server = $server; Response = $response } } # Display the results in a table format $results | Format-Table -AutoSize # Display the success and failure counts Write-Output "Number of servers succeeded: $successCount" Write-Output "Number of servers failed: $failureCount" # Display the servers where the script is missing if ($missingScriptServers.Count -gt 0) { Write-Output "The script is missing on the following servers:" $missingScriptServers | Format-Table -AutoSize } else { Write-Output "The script is available on all specified servers." } # Define the list of server names or IP addresses $servers = @( "server1", "server2", "server3" # Add more servers as needed ) # Path to the script on the servers $scriptPath = "D:\your_script.ps1" # Initialize arrays to hold result objects and counts for success and failure $results = @() $missingScriptServers = @() $successCount = 0 $failureCount = 0 # Function to check if the script exists on the server function Check-ScriptExists { param ( [string]$Server, [string]$ScriptPath ) try { $session = New-PSSession -ComputerName $Server $exists = Invoke-Command -Session $session -ScriptBlock { param($ScriptPath) Test-Path -Path $ScriptPath } -ArgumentList $ScriptPath Remove-PSSession -Session $session return $exists } catch { return $false } } # Function to invoke the script on a remote server function Invoke-ScriptOnServer { param ( [string]$Server, [string]$ScriptPath ) try { $session = New-PSSession -ComputerName $Server $response = Invoke-Command -Session $session -ScriptBlock { param($ScriptPath) & $ScriptPath return $LASTEXITCODE } -ArgumentList $ScriptPath Remove-PSSession -Session $session return $response } catch { return 1 # Return failure exit code in case of error } } # Check for script existence and start jobs in parallel $jobs = @() foreach ($server in $servers) { $scriptExists = Check-ScriptExists -Server $server -ScriptPath $scriptPath if ($scriptExists) { $jobs += Start-Job -ScriptBlock { param($server, $scriptPath) Invoke-ScriptOnServer -Server $server -ScriptPath $scriptPath } -ArgumentList $server, $scriptPath } else { $missingScriptServers += $server } } # Wait for all jobs to complete and collect results foreach ($job in $jobs) { $job | Wait-Job $response = $job | Receive-Job $job | Remove-Job # Update success/failure count based on the response code if ($response -eq 0) { $successCount++ } else { $failureCount++ } $results += [pscustomobject]@{ Server = $server; Response = $response } } # Display the results in a table format $results | Format-Table -AutoSize # Display the success and failure counts Write-Output "Number of servers succeeded: $successCount" Write-Output "Number of servers failed: $failureCount" # Display the servers where the script is missing if ($missingScriptServers.Count -gt 0) { Write-Output "The script is missing on the following servers:" $missingScriptServers | Format-Table -AutoSize } else { Write-Output "The script is available on all specified servers." }
invoke-command $servers script.ps1Try not to do +=.+=) to create a collection. For the iterations you want to do in parallel: move theforeachstatement into theForEach-Object -Parallelcmdlet.