Here is my take on an operation like this that I have used to move multi-TB SQL servers on clusters that have multiple storage arrays separated using datastore clusters. It also relocates the config file to the same datastore as "Hard disk 1".
Adapt it as you like. It has some specific use case coding for my use.
####################
# NEW BY DISK METHOD
$ScriptBlock = {
Function Move-VMConfig {
Param(
[Parameter(Mandatory, ValueFromPipeline)][VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]$VM,
[Parameter(Mandatory)][String]$DatastoreName
)
$hds = Get-HardDisk -VM $VM
$spec = New-Object VMware.Vim.VirtualMachineRelocateSpec
$spec.datastore = (Get-Datastore -Name $DatastoreName).Extensiondata.MoRef
$hds | ForEach-Object {
$disk = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator
$disk.diskId = $_.Extensiondata.Key
$disk.datastore = $_.Extensiondata.Backing.Datastore
$spec.disk += $disk
}
$VM.Extensiondata.RelocateVM_Task($spec, 'defaultPriority')
}
$definitions = [PSCustomObject]@{
vCenterName = 'louvcslpa30'
SourceDSCName = 'DSC01'
DestDSCName = 'DCS02'
MinVMSizeGB = (4 * 1024)
VMName_regex = 'W[BPS][CLM]\d+S[0-9][2468]$'
}
$Host.UI.RawUI.WindowTitle = $definitions.DestDSCName
Import-Module -Name VMware.PowerCLI -Force -EA Ignore
$VIServer = Connect-VIServer -Server $definitions.vCenterName -Credential $ViCredential
$SourceDSC = Get-DatastoreCluster -Name $definitions.SourceDSCName
$DestDSC = Get-DatastoreCluster -Name $definitions.DestDSCName
$DatastoreList = $DestDSC | Get-Datastore -Refresh
do {
$VM = $SourceDSC | Get-VM | `
Where-Object Name -Match $definitions.VMName_regex | `
Where-Object Name -NotMatch '^Z-VRA' | `
Where-Object ProvisionedSpaceGB -GE $definitions.MinVMSizeGB | `
Sort-Object ProvisionedSpaceGB -Descending | Select-Object -First 1
if (-not $VM) { break }
Write-Host "[$(Get-Date -Format s)] $($VM.Name)"
# Move all disks excluding disks on "PURNAS" arrays
if ($HDs = $VM | Get-HardDisk | Where-Object { $_.Filename -notmatch 'PURNAS' -and ($_.ExtensionData.Backing.Datastore | Get-VIObjectByVIView) -notin $DatastoreList } | Sort-Object CapacityGB -Descending) {
$Datastore = $DestDSC | Get-Datastore | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1
#get most disks that can be moved at once
$DiskGroup = @()
$HDs | ForEach-Object {
#Add disks to the group until the total exceeds 15% free space on the destination datastore
if ((Measure-Object -InputObject [decimal]($DiskGroup.CapacityGB + $_.CapacityGB) -Sum).Sum -lt ($Datastore.CapacityGB * 0.75)) {
$DiskGroup += $_
} else { break }
}
Move-HardDisk -HardDisk $DiskGroup -Datastore $Datastore -Confirm:$false
} elseif (-not($VM | Get-HardDisk | Where-Object { $_.Filename -notmatch 'PURNAS' -and ($_.ExtensionData.Backing.Datastore | Get-VIObjectByVIView) -notin $DatastoreList })) {
Move-VMConfig -VM $VM -DatastoreName ($VM | Get-HardDisk -Name 'Hard Disk 1' | Get-Datastore).Name | Out-Null
}
$DestDSC | Get-Datastore -Refresh | Out-Null
# Start-Sleep -Seconds 10
$DestDSC = Get-DatastoreCluster -Name $DestDSC.Name
} while (
$DestDSC.FreeSpaceGB / $DestDSC.CapacityGB -gt 0.10
)
$VIServer | Disconnect-VIServer -Force -Confirm:$false
Pause
}
Start-Process pwsh -ArgumentList "-Command $ScriptBlock"
return