Search code examples
powershellpowercli

PWSH | Parallelize Invoke-VMScript


I'm trying to parallelize a portion of a bigger PWSH script, basically I need to cycle through a list of VMs and execute some commands on them, and to speed up the process, I need this to be executed in parallel:


#Utils.ps1 contains all the functions
.(".\src\utils.ps1")

# List of VMs on which we execute the space reclaim
$VMs = Get-Content .\vmnames.txt

# Check if directory ./Results exists, if not it will be created
CheckDirResults

# Check if dir temp exists, if not it will be created
CheckDirTemp

# Asks for vCenter credentials
GetVServerCred

# Asks for VM credentials
GetVMCred

# Connects to vCenter
ConnectVcenter

# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$InvokeScript = @'
#! /bin/bash
pwd
'@

foreach ($vm in $VMs) {
    $vms = Get-VM -Name $vm
    
    $vms | ForEach-Object -Parallel {
        Write-Output "Testing on $_"
        Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $InvokeScript -Confirm:$false
    }
}

I also tried to simply execute the Invoke directly on $VMs like this:

$VMs | ForEach-Object -Parallel {
   Write-Output "Testing on $_"
   Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}

In both cases, the Invoke-VMScript can't either connect to the VMs or can't find the chunk of code to execute on the VMs.

Errors:

"test.ps1" 44L, 934C                                                                                                                                                                                                                        36,0-1        81%
----                           ----  ----
evl6800756.sys.ntt.eu          443   VSPHERE.LOCAL\Administrator
Testing on euczdrpoc03
Testing on euczdrpoc30
Invoke-VMScript:
Line |
   3 |     Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
     |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | 1/17/2023 10:42:02 AM    Invoke-VMScript     You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
   3 |     Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
     |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | 1/17/2023 10:42:02 AM    Invoke-VMScript     Value cannot be found for the mandatory parameter ScriptText
Invoke-VMScript:
Line |
   3 |     Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
     |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | 1/17/2023 10:42:02 AM    Invoke-VMScript     You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
   3 |     Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
     |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | 1/17/2023 10:42:02 AM    Invoke-VMScript     Value cannot be found for the mandatory parameter ScriptText

Solution

  • I managed to do it this way:

    # Commands that will be executed on VMs on line 94 (Invoke-VMScript)
    $commands = @'
    #! /bin/bash
    hostname
    '@
    
    # Loads the VMs names from the file vmnames.txt
    $vms = Get-Content ".\vmnames.txt"
    
    # Uses the content of the file vmnames.txt to get the VM objects
    $vmnames = Get-VM -Name $vms
    
    # Executes the commands on the VMs
    $vmnames | ForEach-Object -Parallel {
        # Loads the variables in the parallel session
        $commands = $using:commands
        $VMCred = $using:VMCred
        Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
    }
    

    For some reasons, piping directly $vms (so the content of the file to ForEach-Object -Parallel won't pass correctly the value (in this case the VM name), while using the Get-VM -Name $vms will do.

    Then, once this is solved the ForEach-Object -Parallel will finally get the correct VM name assigned to $_.