Search code examples
powershelljobsparallel.foreachinvoke-command

Invoke-Command one to Many without timeouts


I am trying to run series of commands on remote Windows Servers.

Some of these servers are up and some of them not. To some I have access and to some of them I do not have access.

Options to address this:

I can go with:

$Computername = PC1, PC2, PC3 

Invoke-Command -ComputerName $computerName -FilePath .\script

Now what bugs me is that the invoke-command timeout takes too long and cannot made shorter.

So what I was thinking about is to use PowerShell 7 Parallel loops + Invoke-Command + Powershell Jobs:


$server_list = Import-Csv .\server-list.csv
$list = $server_list.Name

$my_array = $list | Foreach-Object -ThrottleLimit 32 -Parallel {

    $x = start-job -name $_ -ScriptBlock { invoke-command -ComputerName $_ -FilePath .\script.ps1}
    $x | Wait-Job -Timeout 5
    $x | Where-Object {$_.State -ne "Completed"} | Stop-Job
    $zz = Receive-Job -Name $_
    $zz
}

$my_array

Error I am getting is:

Invoke-Command: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
Invoke-Command: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
Invoke-Command: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.

Any idea how to address this?


Solution

  • This is how you can apply the logic you're looking for without so much overhead, you only really need a loop to enumerate each computer and Start-ThreadJob which comes pre-installed in PowerShell Core.

    As for the error, you can use the $using: scope modifier when referring to variable defined in the local scope. Example 9 from the docs has a good explanation on this.

    $jobs = foreach($computer in (Import-Csv .\server-list.csv).Name) {
        Start-ThreadJob {
            Invoke-Command -ComputerName $using:computer -FilePath .\script.ps1
        } -ThrottleLimit 32
    }
    
    # Wait 5 seconds for them
    $jobs | Wait-Job -Timeout 5
    # Split the array between Completed and the other states // Running, Failed, etc.
    $completed, $failed = $jobs.Where({ $_.State -eq 'Completed' }, 'Split')
    # get the output from the completed ones
    $completed | Receive-Job -Wait -AutoRemoveJob
    
    # These maybe you want to output to console before removing?
    $failed | Stop-Job -PassThru | Remove-Job