Laba Ningombam
Laba Ningombam

Reputation: 163

How to execute a Powershell script in parallel

I have a powershell script which is run to connect to remote servers and execute a script. I am passing an argument as well. Is there a way to run this in parallel, passing the correct ids responding to each STORES. The powershell version is 5 in the system.

$storeids = Get-Content 'D:\StoreMap.json'  -Raw | ConvertFrom-Json
$storeids | Select-Object -Property STORE, ID | ForEach-Object {
    $computer = 'IN' + $_.STORE + 'test.org'
    $ID = $_.ID
    $script = 'D:\test.ps1'
    Write-Host "Started running the script on Store" + $computer

    try {
        Invoke-Command -ComputerName $computer -FilePath $script -ArgumentList $ID
    }
    catch {
        Write-Host $_.Exception.Message
    }
}

Upvotes: 1

Views: 1270

Answers (3)

Youssef Bouha
Youssef Bouha

Reputation: 131

function BatchParallel($items, $scriptBlock) {
    $pool = [runspacefactory]::CreateRunspacePool(1, 5)
    $pool.Open()
    
    $results = @()
    [System.Collections.ArrayList]$tasks = @()

    $totalTasks = $items.Count
    $completedTasks = 0

    $items | ForEach-Object {
        $item = $_
        $ps = [powershell]::Create().AddScript($scriptBlock).AddArgument($item)
        $ps.RunspacePool = $pool
        $task = $ps.BeginInvoke()

        $tasks += [PSCustomObject]@{
            PowerShell = $ps
            Task       = $task
        }
    }

    $tasks | ForEach-Object {
        # Wait for task completion
        $_.PowerShell.EndInvoke($_.Task)
        $_.PowerShell.Dispose()

        $completedTasks++
    }

    $pool.Close()
    $pool.Dispose()

    return $results
}

Upvotes: 1

Doug Maurer
Doug Maurer

Reputation: 8868

You could create a hash table to lookup the ID based on the remote computers name and then call them in parallel with Invoke-Command.

Two things to note

  1. Update the hostname property if the assumption is incorrect (make it match each $env:computername)
  2. Update D:\test.ps1 to use the lookup table instead of the passed in argument

also see inline comments for details.

$storeids = Get-Content 'D:\StoreMap.json'  -Raw | ConvertFrom-Json

# The assumption is IN + $_.Store is exactly the remote systems computer name, stored as Hostname property
# The ComputerName property is the fqdn for the Invoke-Command call
$selectprops = '*',
                @{n='Hostname';e={"IN$($_.Store)"}},
                @{n='ComputerName';e={"IN$($_.Store)test.org"}}

# Create a lookup table. Each remote system will be able to extract their own ID from this table via it's computername
$storetable = $storeids |
    Select-Object $selectprops |
        Group-Object -Property Hostname -AsHashTable

# Instead of using a passed in argument, use $using:storetable to grab a local copy of the table and then
# reference it with $localtable[$env:computername]
# Silence errors to prevent interruption and capture any errors to $errors (appended due to +)
$icmparams = @{
    ComputerName  = $storetable.Name
    FilePath      = 'D:\test.ps1'
    ErrorAction   = 'SilentlyContinue'
    ErrorVariable = '+errors'
    ThrottleLimit = 64 # 32 by default
}

$results = Invoke-Command @icmparams

# output collected in $results
$results 

# check any errors
$errors

<# D:\test.ps1

$localtable = $using:storetable
$id = $localtable[$env:computername].ID
"Computer: $env:Computername  ID: $id"

#>

Upvotes: 1

Civette
Civette

Reputation: 538

I would propose this

Get-Content 'D:\StoreMap.json' -Raw |
    ConvertFrom-Json |
    Select-Object -Property STORE, ID |
    ForEach-Object {
        $computer = 'IN'+$_.STORE+'test.org'
        $ID= $_.ID
    
        Write-Host "Started running the script on Store " + $computer

        $ScriptBlock = {
            Param([string] $ID)

            #Your remote code here, most likely content of D:\test.ps1
        }
        
        Start-Job -Name "Script on $($computer)" -ScriptBlock $ScriptBlock -Args $ID
    }
Get-Job | Wait-Job
$return += Get-Job | Receive-Job 

Upvotes: 0

Related Questions