Home > OS >  Powershell SSH / run on multiple machine simultaneously
Powershell SSH / run on multiple machine simultaneously

Time:07-30

I have this script ; Now I the script and it goes through each machine one by one. How can I make to run script simultaneously for all ips is in 'ip.txt' Thank you.

$Computers = $Computers = Get-Content -Path .\ip.txt | Where-Object { $_ -match '\S' }

foreach($Computer in $Computers){
Write-Host $Computer

    $User = "-"
    $Password = "-"
    $Command = 'hostname'

    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)

Get-SSHTrustedHost | Remove-SSHTrustedHost

$SessionID = New-SSHSession -ComputerName $Computer -Credential $Credentials -AcceptKey:$true

Invoke-SSHCommand -Index $sessionid.sessionid -Command $Command | Select -Expand Output | Add-Content -Path result.txt
}

CodePudding user response:

First, put the code inside your foreach loop into a scriptblock. You may need to make some subtle tweaks.

You can then pass this code block to the -ScriptBlock parameter of Invoke-Command, along with the array of $Computers to the -ComputerName parameter. This will allow you to run the scriptblock in parallel against multiple computers at the same time. If you need to throttle how many at once, you can use the -ThrottleLimit parameter.

Example 13 on the Invoke-Command documentation also shows an alternative method of running a 'script' on multiple computers at the same time, if you wanted to avoid using a script block.

CodePudding user response:

I assume that Invoke-SSHCommand is from the PoshSSH module. If so, the help documentation for that does not have a "-AsJob" parameter or anything that would cause it to run in the background.

Therefore you need to use PowerShell jobs in your script to invoke multiple commands at once. This is a standard PowerShell jobs kind of question and as such it may be kind of redundant to answer it here.

$scriptBlock = {
    # You must pass any parameters that you want to use in your script block
    param(
        [String]$Computer,
        [System.Management.Automation.PSCredential]$Credentials,
        [String]$Command
    )
    $SessionID = New-SSHSession -ComputerName $Computer -Credential $Credentials -AcceptKey:$true

    if ($SessionID)
    {
        # Any output that the Invoke-SSHCommand outputs will be allowed
        # to escape to the pipeline here because we don't assign the output
        # to a variable.  When you call Receive-Job, you will then get all of
        # the output that escaped to the pipeline (i.e. you will get the return
        # value of this command)
        Invoke-SSHCommand -Index $sessionid.sessionid -Command $Command
    }
    else
    {
        # Or provide your own error handling
        return "Could not create Session"
    }
}

# I'm confused what this does and if you need to call it each loop, or just once
Get-SSHTrustedHost | Remove-SSHTrustedHost

$Computers = Get-Content -Path .\ip.txt | Where-Object { $_ -match '\S' }

$hashTableOfJobs= @{}
foreach($Computer in $Computers) 
{
    Write-Host $Computer

    $User = "-" #ToDo, add username
    $Password = "-" #ToDo, add password
    $Command = 'hostname'

    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $Credentials = New-Object System.Management.Automation.PSCredential($User, $secpasswd)

    # This next line would have a problem if you had duplicates in your list 
    # of computers (because the HashTable needs keys to be unique)

    # Also note that the "Argument List" needs to provide the exact
    # arguments defined in your Script Block in the same order.
    $hashTableOfJobs["$computer"] = Start-Job -ScriptBlock $scriptBlock -ArgumentList @($Computer, $Credentials, $Command)

    # Name is just a string for you to identify the job; I find it helpful later
    $hashTableOfJobs["$computer"].Name = "$Computer"
}

# At this point, $hashTableOfJobs contains a bunch of jobs that have been 
# run (or are still running).  We need to let all the jobs finish before
# we continue.

# A more sophisticated approach could loop through $listOfJobs and check
# the "State" of the job, and then write status and decide to wait longer
# To do that, check if ($_.State -like "*Running*")

# Option 1 for waiting for jobs
# Wait with a timeout of 10 seconds
$finishedJobs = $hashTableOfJobs.Values | Wait-Job -Timeout 10

# Option 2 for waiting for jobs
#     Left as a coding exercise for you =)

if ($finishedJobs.Count -ne $hashTableOfJobs.Count)
{
    # ToDo, some of your jobs didn't finish. Add error handling.
    Write-Warning "$($finishedJobs.Count) out of $($hashTableOfJobs.Count) jobs finished."
}

# For below, you have to choose between Option 1 and Option 2.
# If you call "Receive-Job" twice, then you will only get output
# the first time you call it.

# Option 1 for receiving jobs: Look at only the finished jobs
foreach ($job in $finishedJobs)
{
    Write-Host "Receiving output for the $($job.Name) job"
    $thisJobOutput = Receive-Job -Job $job
    Write-Host "$thisJobOutput"
    Write-Host ""
}

# Option 2 for receiving jobs: Look at all jobs
foreach ($computer in $hashTableOfJobs.Keys)
{
    $job = $hashTableOfJobs[$computer]
    Write-Host "Receiving output for the $computer ($($job.Name)) job (state = $($job.State))"
    if ($job.State -like "*Running*")
    {
        Write-Warning "Oops, $($job.Name) on computer $computer didn't finish"
    }
    else
    {
        $thisJobOutput = Receive-Job -Job $job
        # Note, if you've already called Receive-Job, then you won't
        # get any more output, and this will be blank.  (We called 
        # "Receive-Job" above.  Delete that call and this will work)
        Write-Host "$thisJobOutput"
        Write-Host ""
    }
}
  • Related