1

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:

  1. 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 $results to store the results, $missingScriptServers for servers missing the script, and counters for success and failure ($successCount and $failureCount).

  2. Checking Script Existence:
    The function Check-ScriptExists checks if the specified script ($scriptPath) exists on each remote server. It uses PowerShell remoting (New-PSSession and Invoke-Command) to check if the script file exists on the remote server. If the script exists, the function returns $true; otherwise, it returns $false.

  3. Invoking the Script Remotely:
    The Invoke-ScriptOnServer function is responsible for executing the script remotely on the server. If the script exists, this function executes it using Invoke-Command. If an error occurs during the execution, it returns 1 to indicate failure.

  4. Parallel Execution of Remote Commands:
    The script checks each server in the list. For servers where the script exists, it creates a background job using Start-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.

  5. Collecting and Displaying Results:
    After starting the jobs for remote execution, the script waits for each job to complete using Wait-Job and retrieves the result with Receive-Job. It then removes the job with Remove-Job to 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."
    }
    
    
3
  • 2
    What is the actual question? I. e. what part of the script have you identified to be the performance bottleneck and what particular problem did you encounter, while trying to optimize your script? StackOverflow works best when you have already narrowed down the problem and ask a very specific question, showing your own attempt at solving the problem, in code. Commented Dec 20, 2024 at 10:04
  • invoke-command $servers script.ps1 Try not to do +=. Commented Dec 20, 2024 at 13:39
  • Try to I avoid using the increase assignment operator (+=) to create a collection. For the iterations you want to do in parallel: move the foreach statement into the ForEach-Object -Parallel cmdlet. Commented Dec 20, 2024 at 19:31

1 Answer 1

0

As a general rule, avoid using +=

Invoke-Command already runs in parallel. Additionally, there's no need to check if the script exists as a separate call, just try to run it and parse the output. You can also use Invoke-Command -FilePath to execute the script directly with the command

# 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"


$icmParams = @{
    ComputerName = $servers
    FilePath = $scriptPath
}
$results = Invoke-Command @icmParams

# Format, filter, and display the $results to your liking
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.