PowerShell: Moving Guest VMs in Hyper-V in Conjunction with Microsoft Failover Cluster

This code is outdated. Refactor before reuse..

###############################################################################################################
# HyperV-MoveVM.ps1
# Assumption: Hyper-V has been setup with Microsoft Failover Cluster
###############################################################################################################

# Set variables
$windowsName="CONCU.intranet.kimconnect.com"
$sourceVmName="CONCU";
$interimDestination="C:\ClusterStorage\Volume1\VHD";
$finalDestination="C:\ClusterStorage\Volume1\VHD";
$targetClusterName="HYPERV-CLUSTER01"
$resizeDiskIndex=0;
$resizeVolumeLetter="C";
$resizeTo="160GB";
$backupSourceFolder="C:\ClusterStorage\Volume2\VHD\CONCU"
$backupDestinationFolder="C:\ClusterStorage\Volume2\VHD\CONCU-BACKUP"

# Function to Create Template
# Need to add feature to check whether target cluster node has access to the $destinationFolder
function exportVM{
    param(
        $sourceVmName="Windows 2016 Template",
        $destinationFolder="C:\ClusterStorage\Volume1\VHD"
        )
    
    # Remove all snapshots prior to performing disks expansion
    try {
        Get-VMSnapshot -VMName $sourceVmName | %{Remove-VMSnapshot -VMName $sourceVmName -Name $_.Name}
        }
    catch{
        write-host "$Error`r`nUnable to remove all snapshots. Aborting Remove-VMSnapshot commands."
        break;
        }

    # Gather source volumes
    $sourceVolumes=(get-vm $sourceVmName | select -expand HardDrives).Path

    # Calculate volume sizes
    $totalSize=0;
    $sourceVolumes|%{$totalSize+=(get-item $_).Length}
    $totalSizeInGB=$totalSize/1GB

    # Perform export
    try{
        $exportDuration=Measure-Command {Export-VM -Name $sourceVmName -Path $destinationFolder}
        $exportHours=[math]::round($exportDuration.TotalHours,2);
        $gbPerHour=[math]::round($totalSizeInGB/$exportHours,2);
        write-host "$sourceVmName has been exported to $destinationFolder at the speed of $gbPerHour GB/Hour ($totalSizeInGB GB in $exportHours hours)";
        return $true
        }catch{
            return $false
            }
    }

# Helper functions
function addVmToCluster{
    param($vmName="Windows 2016 Template")
    if(get-cluster -ea SilentlyContinue){Add-ClusterVirtualMachineRole -VirtualMachine $vmName}else{write-host "No clusters found."}
    }

function removeVmFromCluster{
    param($vmName="Windows 2016 Template")
    $clusterName=(get-cluster -ea SilentlyContinue).Name
    if($clusterName){
        try{
            Remove-ClusterGroup -VMId (Get-VM -Name $vmName).VMId -RemoveResources -Force;
            write-host "$vmName has been removed from the cluster $clusterName";
            }catch{
                write-host $Error
                }
        }else{write-host "No clusters found."}
    }

function moveItemToRecycleBin{
    param($item="C:\Temp\helloworld.py")
    Add-Type -AssemblyName Microsoft.VisualBasic
    $itemType=(get-item $item).Attributes    
    switch ($itemType){
        "Directory" {[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory("$item",'OnlyErrorDialogs','SendToRecycleBin')}
        "Archive" {[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile("$item",'OnlyErrorDialogs','SendToRecycleBin')}
    }   

    <#
    $shell = new-object -comobject "Shell.Application"
    $item = $shell.Namespace(0).ParseName("$path")
    $item.InvokeVerb("delete")
    #>
}

# Function to gracefully shutdown remote computer
function gracefulShutdown{
    param(
        $windowsName,
        $vmName
        )
    # Enable VMIntegration for Shutdown Commands
    function ensureVMIntegrationForShutdown{
        param($vmName)
        # Ensure that the VM Integration Service for Shutdown method was enabled
        $vmIntegrationShutdownEnabled=(Get-VMIntegrationService -VMName $vmName | ?{$_.Name -eq "Shutdown"}).Enabled
        if (!($vmIntegrationShutdownEnabled)){Enable-VMIntegrationService -VMName $vmName -Name "Shutdown"}
        }

    # Ping computer, retry until wait-time expires
    function pingHostUntilDown{
        param($computername="localhost",$waitTime=300)    
        $t=0;
        write-host "Pinging $ComputerName"
        do {            
            $hostIsPingable=if(Test-Connection $ComputerName -Count 1 -Delay 1 -ErrorAction SilentlyContinue){$true}else{$false}
            if($hostIsPingable){
                write-host -nonewline ".";
                $t+=5;
                sleep 5;
                }        
        }while ($hostIsPingable -or $t -gt $waitTime)

        if($hostIsPingable){return $true}else{return $false}
        }
    
    function pingTest{
        Param([string]$node)
        try{
            Return Test-Connection $node -Count 1 -Quiet -ea Stop;
            }
        catch{Return $False}
    }

    function enableRemoteWinRM{
      Param([string]$computername)

      Write-Host "checking $computername..."

      if (pingTest $computername){
          if (!(Test-WSMan $computername -ea SilentlyContinue)){
            if(!(get-command psexec)){
                if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
                    }
                choco install sysinternals -y
                }
            psexec.exe \\$computername -s C:\Windows\system32\winrm.cmd qc -quiet
            }else{Write-Host "WinRM has been already enabled. No changes to WinRM have been made."}
        }
      Else{Write-Host "Unable to determine if WinRM is enabled on $computername`.`n Ping test has failed. Check if this computer is online and whether there's a firewall blocking of ICMP";}
    }

    # function to connect to remote machine and execute commands
    function connectWinRmToShutdown{
        param($remoteComputer)               
        do{
        $session = New-PSSession -ComputerName $RemoteComputer
        "Connecting to remote computer $computer..."
        sleep -seconds 5
        if ($session){"Connected."}
        } until ($session.state -match "Opened")
    
        invoke-command -session $session -scriptblock{
            # Gracefully stop SQL if it is present
            Get-service *SQL* | Where-Object {$_.status -eq   "Running"} | Stop-Service -Force
            #force in this context will enable the process to shutdown dependent services prior to stopping the target service

            # Gracefully stop Exchange if it is present
            $exchangeServices=Get-Service | Where-Object {$_.DisplayName –like "Microsoft Exchange *"};
            if($exchangeServices){
                net stop msexchangeadtopology /y;
                net stop msexchangefba /y;
                net stop msftesql-exchange /y;
                net stop msexchangeis /y;
                net stop msexchangesa /y;
                net stop iisadmin /y;
                net stop w3svc /y;
                $exchangeServices | Stop-Service -Force;
                }

            # Finally shutdown the OS via Windows native method
            Stop-Computer $env:computername -Force        
            }
        $pingResult=pingHostUntilDown -computername $RemoteComputer -waitTime 300
        if ($pingResult){write-host "$RemoteComputer is still online.";break}else{write-host "$RemoteComputer is now offline."}
        }

    # Attempt to connect to remote machine via WinRM 
    try{
        ensureVMIntegrationForShutdown -vmName $vmName
        if (pingTest $windowsName){
            enableRemoteWinRM -computername $windowsName
            connectWinRmToShutdown -remoteComputer $windowsName
            if((get-vm $vmName).Status -eq "Running"){stop-vm -Name $vmName -Force -ErrorAction SilentlyContinue;} #shutdown VM in case it is still online; #shutdown VM in case it is still online
            }else{
                write-host "$windowsName is currently unpingable. Considering it as already shut down.";
                if((get-vm $vmName).Status -eq "Running"){stop-vm -Name $vmName -Force -ErrorAction SilentlyContinue;}
                }
        }catch{
            write-host $Error;
            break;
            }            
    }

# Function to Clone from Template
# Future feature not currently available: Import to new destination with a new VM name and GUID
# importVM -vmName $sourceVmName -sourceDirectory $interimDestination -destinationDirectory $finalDestination;
function importVm{
    param(
        $vmName="Windows 2016 Template",
        $sourceDirectory="C:\ClusterStorage\Volume1\VHD",
        $destinationDirectory="C:\ClusterStorage\Volume1\VHD"       
        )

    # Sanity checks
    # Verify whether source VM already exists in Hyper-V
    $vmExists=get-vm $vmName -ErrorAction SilentlyContinue
    if ($vmExists){
        write-host "Cannot import $vmName because it already exists.";
        break;
        }
    # Verify whether Interim destination and Final Destination are the same, and whether the destination node has access to Final Destination
    $sameSourceAndDestination=$sourceDirectory -eq $destinationDirectory

    # Gather source volumes
    $sourceVolumes=(get-item "$sourceDirectory\$vmName\Virtual Hard Disks\*.vhdx").FullName

    # Calculate volume sizes
    $totalSize=0;
    $sourceVolumes|%{$totalSize+=(get-item $_).Length}
    $totalSizeInGB=$totalSize/1GB
    
    # Use this import sequence when Source Directory and Destination Directory are the same
	$vmcxFile=(get-item "$sourceDirectory\$vmName\Virtual Machines\*.vmcx").FullName
    if (!($vmcxFile)){
        write-host "Cannot import $vmName because a VMCX file does not exist at '$destinationFolder\$vmName\Virtual Machines\'";
        break;
        }else{
	        $importDuration=Measure-Command {
                if($sameSourceAndDestination){
                    # Use this import sequence when Source Directory and Destination Directory are the same
                    Import-VM -Path "$vmcxFile";
                    }else{
                        # Use this import sequence when Source Directory and Destination Directory are different
                        Import-VM -Path "$vmcxFile" -Copy -GenerateNewId;
                        }
                addVMToCluster -vmName $vmName;
                }
            $importHours=[math]::round($importDuration.TotalHours,2)
            $gbPerHour=[math]::round($totalSizeInGB/$importHours,2)
            write-host "$vmName has been imported to $destinationDirectory\$vmName at the speed of $gbPerHour GB/Hour ($totalSizeInGB GB in $importHours hours)"
            if(!($sameSourceAndDestination)){write-host "`r`nRecommendation: clean up $sourceDirectory\$vmName after validation"}
            }
    }

# Function to unregister guest VM from Cluster and Hyper-V
function deregisterVm{
    param([string]$vmName="Windows 2016 Template")
    try{
        $vmState=(get-vm $vmName).State      
        if($vmState -eq "Running"){gracefulShutdown -RemoteComputer $vmName}
        $vmStopped=(get-vm $vmName).State -eq "Off"
        if($vmStopped){
            removeVmFromCluster -vmName $vmName;
            Remove-VM -Name $vmName -Force;
            write-host "$vmName has been removed from Hyper-V."
            }
        }catch{
            write-host $Error
            write-host "Unable to deregister VM $vmName."
            }
    }

function getVmDisks{
    param($vmName)
    #write-host "Gathering disks information...";
    $disks=Get-VM $vmName | Select-Object VMId | Get-VHD
    return $disks
}

# Expand the targeted disk
# resizeVmDisk -vmName $vmName -diskIndex $diskIndex -newSizeGb $newSize;
function resizeVmDisk{
    param($vmName,$diskIndex,$newSizeGb)

    write-host "Removing all snapshots prior to performing disks expansion..."
    try {
        Get-VMSnapshot -VMName $vmName | %{Remove-VMSnapshot -VMName $vmName -Name $_.Name}
        }
    catch{
        write-host "unable to remove all snapshots. Aborting Remove-VMSnapshot commands."
        break;
        }

    write-host "Gathering disks information...";
    $disks=getVmDisks -vmName $vmName
    $before= $($disks | select VhdType,Path,@{label='currentSizeGb';expression={$_.FileSize/1gb -as [int]}},`
                                @{label='minimumSizeGbAllowed';expression={$_.MinimumSize/1gb -as [int]}},`
                                @{label='maxSizeGb';expression={$_.Size/1gb -as [int]}}`
                                | ft -AutoSize | Out-String)
    write-host "Before:`r`n$before" 
    $diskX=$disks | Select -Index $diskIndex # VhdType: Fixed, Dynamic, Differencing
    $diskMinimumAllowed=$diskX.MinimumSize;
    $diskCurrentSize=$diskX.FileSize;
    $diskMaxSize=$diskX.Size;
    $vmVolumeFile=$diskX.Path
    #$vmVolume=get-vm $vmName | select -expand HardDrives | select -Index $diskIndex
    #$vmVolumeFile=$vmVolume.Path

    # if $newSize is not specified, set disk value to double its original size
    if(!($newSizeGb)){
        $newSizeBytes=$diskMaxSize*2
        }else{
            $newSizeBytes=$newSizeGb/1;
            }
    
    if ($newSizeGb/1 -ge $diskMinimumAllowed){
        try{
            Resize-VHD -Path "$vmVolumeFile" -SizeBytes ($newSizeGb/1);
            $disksAfter=getVmDisks -vmName $vmName
            $after= $($disksAfter | select VhdType,Path,@{label='currentSizeGb';expression={$_.FileSize/1gb -as [int]}},`
                            @{label='minimumSizeGbAllowed';expression={$_.MinimumSize/1gb -as [int]}},`
                            @{label='maxSizeGb';expression={$_.Size/1gb -as [int]}}`
                            | ft -AutoSize | Out-String)
            write-host "After:`r`n$after"
            write-host "$vmVolumeFile has been resized to $newSizeGb successfully."                       
            }catch{
                write-host "Cannot resize $vmVolumeFile.";
                }
        }else{
            write-host "$newSizeGb GB is less than the minimum allowed of $($diskMinimumAllowed/1GB). Cannot resize this disk.";
            }
}

# Connect to Windows OS of Guest-VM & Resize volume to its available maximum
function expandWindowsVmVolume{
    param($windowsName,$vmName,$driveLetter="C")

    if((get-vm -name $vmName).State -ne "Running"){Start-VM -Name $vmName}
    $localComputerName=$env:computername
    $localIps=([System.Net.Dns]::GetHostAddresses($env:computername)|?{$_.AddressFamily -eq "InterNetwork"}|%{$_.IPAddressToString;}|out-string).Trim();
    $isLocal=if($windowsName -match "(localhost|127.0.0.1|$localComputerName)" -or $localIps -match $windowsName){$true}else{$false}

    # Ping computer, retry until wait-time expires
    function pingHostUntilUp{
        param($computername="localhost",$waitTime=300)    
        $t=0;
        write-host "Pinging $ComputerName"
        do {            
            $hostIsPingable=if(Test-Connection $ComputerName -Count 1 -Delay 1 -ErrorAction SilentlyContinue){$true}else{$false}
            if(!($hostIsPingable)){
                write-host -nonewline ".";
                $t+=5;
                sleep 5;
                }        
        }while (!($hostIsPingable) -and $t -le $waitTime)
        return $hostIsPingable
        }    

    function expandVolumeMax{
        param($driveLetter)
        Update-HostStorageCache
        # Before
        $before = (gwmi -Class win32_volume -Filter "DriveType!=5" -ea stop| ?{$_.DriveLetter -eq "$driveLetter`:"}|`
                    Select-object @{Name="Letter";Expression={$_.DriveLetter}},`
                    @{Name="Label";Expression={$_.Label}},`
                    @{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}},`
                    @{Name = "Available"; Expression = {"{0:N2} GiB" -f ($_.FreeSpace/1073741824)}},`
                    @{Name = "Utilization"; Expression = {"{0:N2} %" -f  ((($_.Capacity-$_.FreeSpace) / $_.Capacity)*100)}}`
                    | ft -autosize | Out-String).Trim()
        write-host "Before:`r`n$before"     
        
        $max=(Get-PartitionSupportedSize -DriveLetter $driveLetter).SizeMax;
        $maxGb=[math]::round($max/1GB,4);
        try{
            Resize-Partition -DriveLetter $driveLetter -Size $max;
            Update-HostStorageCache;
            $after = (gwmi -Class win32_volume -Filter "DriveType!=5" -ea stop| ?{$_.DriveLetter -eq "$driveLetter`:"}|`
                        Select-object @{Name="Letter";Expression={$_.DriveLetter}},`
                        @{Name="Label";Expression={$_.Label}},`
                        @{Name="Capacity";Expression={"{0:N2} GiB" -f ($_.Capacity/1073741824)}},`
                        @{Name = "Available"; Expression = {"{0:N2} GiB" -f ($_.FreeSpace/1073741824)}},`
                        @{Name = "Utilization"; Expression = {"{0:N2} %" -f  ((($_.Capacity-$_.FreeSpace) / $_.Capacity)*100)}}`
                        | ft -autosize | Out-String).Trim()
            write-host "After:`r`n$after" 
            Write-Host "`r`n$driveLetter has been resized to $maxGb GB successfully.";
            }catch{
                write-host $Error;
                Write-Host "`r`n$driveLetter has NOT been resized due to an error.";
                break;
                } 
    }

    function expandVolume{
        param($computerName,$isLocal)
        if ($isLocal){
            expandVolumeMax -driveLetter $driveLetter;
            }else{
                invoke-command -computername $computerName -ScriptBlock{
                    param($expandVolumeMax,$driveLetter)                
                    [ScriptBlock]::Create($expandVolumeMax).Invoke($driveLetter);        
                    }-Args ${function:expandVolumeMax},$driveLetter
                }
        }

    if(pingHostUntilUp -computername $windowsName){
        expandVolume -computerName $windowsName -isLocal $isLocal
        }else{
            Start-VM -Name $vmName
            $hostOnline=pingHostUntilUp -computername $windowsName
            if($hostOnline){
                expandVolume -computerName $windowsName -isLocal $isLocal
                }else{
                    write-host "Cannot bring $windowsName online to perform disk expansion.";
                    }
            }
}

function moveVM{
    param(
        [string]$windowsName="ISI-WSUS",
        [string]$sourceVmName="Windows 2016 Template",
        [string]$interimDestination="C:\ClusterStorage\Volume1\VHD",
        [string]$finalDestination="C:\ClusterStorage\Volume1\VHD"
        )
    $timer=[System.Diagnostics.Stopwatch]::StartNew();
    #gracefulShutdown -windowsName $windowsName -vmName $sourceVmName;
    $exportSuccess=exportVM -sourceVmName $sourceVmName -destinationFolder $interimDestination;     
    if($exportSuccess){
        deregisterVM -vmName $sourceVmName
        importVM -vmName $sourceVmName -sourceDirectory $interimDestination -destinationDirectory $finalDestination;
        $totalSeconds=$timer.Elapsed.TotalSeconds;
        $hours=[math]::round($totalSeconds/3600,2);
        write-host "The moveVM function has completed in $hours hours.";
        write-host "Once validation succeeds, manually delete this folder $interimDestination\$sourceVmName";
        $timer.stop();
        }else{
            write-host "The exportVM function has failed. Aborting moveVM function.";
            break;
            }
    }

function expandVmDiskAndVolume{
    param(
        [string]$windowsName="Windows2016Template",
        [string]$vmName="Windows 2016 Template",
        [int]$diskIndex=0,
        [string]$driveLetter="C",
        [string]$newSize="160GB"
        )
    resizeVmDisk -vmName $vmName -diskIndex $diskIndex -newSizeGb $newSize;
    expandWindowsVmVolume -windowsName $windowsName -vmName $vmName -driveLetter $driveLetter;
}

function copyOnlyFiles{
    param(
        [Parameter(Mandatory=$true)][string]$sourceFolder,
        [Parameter(Mandatory=$true)][string]$destinationFolder
        )
    $totalSizeBytes=0;
    # Get-ChildItem "C:\Temp"|?{!$_.PSIsContainer}|%{robocopy "C:\Temp" "C:\TempCopy" $_}
    # Get-ChildItem $sourceFolder|?{!$_.PSIsContainer}|%{robocopy $sourceFolder $destinationFolder $_}
    # New-Item -ItemType Directory -Force -Path $destinationFolder | Out-Null
    #$duration=measure-command {Get-ChildItem $sourceFolder|?{!$_.PSIsContainer}|Copy-Item -Destination $destinationFolder}
    $duration=measure-command {        
        $filesOnly=Get-ChildItem $sourceFolder|?{!$_.PSIsContainer}        
        $filesOnly|%{
            $totalSizeBytes+=$_.Length;
            write-host $_;
            robocopy $sourceFolder $destinationFolder $_;
            robocopy $sourceFolder $destinationFolder $_ | %{$data = $_.Split([char]9); if("$($data[4])" -ne "") { $file = "$($data[4])"} ;Write-Progress "Percentage $($data[0])" -Activity "Robocopy" -CurrentOperation "$($file)"  -ErrorAction SilentlyContinue; }
            }        
        }
    $totalsizeGb=$totalSizeBytes/1GB
    $totalMinutes=[math]::round($duration.TotalMinutes,2)
    $gbPerHour=[math]::round($totalsizeGb/(($duration.TotalMinutes)/60),2)
    write-host "-------------------------------------------------`r`n$([math]::round($totalsizeGb,2)) GB completed in $totalMinutes minutes. Speed was $gbPerHour GB/Hour."
}

function resizeVhdxChain{
    param(
        [Parameter(Mandatory=$true)][String]$vmName,
        [Parameter(Mandatory=$false)][int]$diskIndex=0,
        [Parameter(Mandatory=$false)][String]$newSizeGb="160GB"
        )
    
    function  resizeVhdx{
        param(
            [string]$vhdxFile,
            $newSizeGb
            )
        # Validation
        if($newsizegb.gettype().Name -eq "String"){
            $sizeBytes=$newSizeGb/1
            }else{
                write-host "There appears to be an error with the input size of $newSizeGb"
                break;
                }
        try{
            Resize-VHD -Path "$vhdxFile" -SizeBytes $sizeBytes;
            }catch{
                write-host $Error
                }
        }    

    $disks=getVmDisks -vmName $vmName
    $diskX=$disks | Select -Index $diskIndex
    $file=$diskX.Path
    $parent=(Get-VHD -Path '$file' -ea SilentlyContinue).ParentPath
    if ($parent){
        $vhdxFiles=while($file = (Get-VHD -Path '$file').ParentPath){$file} 
        }else{
            $vhdxFiles=$file
            }    
    
    if($vhdxFiles.Count -gt 1){
        $statements=$vhdxFiles|%{        
            "resizeVhdx -vhdxFile '$_' -newSizeGb '$newSizeGb'`r`n";
            }
        #$statements+="Merge-VHD -Path '$($vhdxFiles[0])' -DestinationPath '$($vhdxFiles[$vhdxFiles.length-1])'"
        write-host "Statements to confirm`r`n$statements"
        pause;
        $vhdxFiles|%{resizeVhdx -vhdxFile '$_' -newSizeGb '$newSizeGb'}
        #Merge-VHD -Path '$($vhdxFiles[0])' -DestinationPath '$($vhdxFiles[$vhdxFiles.length-1])'
        }else{
            write-host "There are not parent disks to resize."
            }    

}

function mergeVhdxChain{
    param(
    [Parameter(Mandatory=$true)][String]$vmName,
    [Parameter(Mandatory=$false)][int]$diskIndex=0
    )

    $disks=getVmDisks -vmName $vmName
    $diskX=$disks | Select -Index $diskIndex
    $file=$diskX.Path
    $parent=(Get-VHD -Path '$file' -ea SilentlyContinue).ParentPath
    if ($parent){
        $vhdxFiles=while($file = (Get-VHD -Path $file).ParentPath){$file} 
        }else{
            $vhdxFiles=$file
            }
    if($vhdxFiles.Count -gt 1){
        Merge-VHD -Path "$($vhdxFiles[0])" -DestinationPath "$($vhdxFiles[$vhdxFiles.length-1])"
        }else{
            write-host "There are not parent disks to merge."
            } 
    }

write-host "Commands loaded - copy and paste these lines when ready"
write-host "gracefulShutdown -windowsName `$windowsName -vmName `$sourceVmName"
write-host "copyOnlyFiles -sourceFolder `$backupSourceFolder -destinationFolder `$backupDestinationFolder"
write-host "moveVM -windowsName `$windowsName -sourceVmName `$sourceVmName -interimDestination `$interimDestination -finalDestination `$finalDestination"
write-host "getVmDisks -vmName `$sourceVmName"
write-host "resizeVhdxChain -vmName `$sourceVmName -diskIndex `$resizeDiskIndex -newSizeGb `$resizeTo"
write-host "expandVmDiskAndVolume -windowsName `$windowsName -vmName `$sourceVmName -diskIndex `$resizeDiskIndex -driveLetter `$resizeVolumeLetter -newSize `$resizeTo"

<# Sample Output:
PS C:\Windows\system32> migrateVM -sourceVmName "CONCU" -destinationDirectory "C:\ClusterStorage\Volume2\VHD"
checking CONCU...
WinRM has been already enabled. No changes to WinRM have been made.
Connecting to remote computer ...
Connected.
WARNING: Waiting for service 'Sage Fixed Assets Service (SQLANYs_Sage_FAS_Fixed_Assets)' to stop...
WARNING: Waiting for service 'Sage Fixed Assets Service (SQLANYs_Sage_FAS_Fixed_Assets)' to stop...
Pinging CONCU
...CONCU is now offline.
CONCU has been exported to C:\ClusterStorage\Volume2\VHD at the speed of 254.61 GB/Hour (38.19140625 GB in 0.15 hours)
CONCU has been removed from the cluster.
CONCU has been imported to C:\ClusterStorage\Volume2\VHD\CONCU at the speed of 3819.14 GB/Hour (38.19140625 GB in 0.01 hours)

PS C:\Windows\system32> expandVmDiskAndVolume -vmName "CONCU" -diskIndex 0 -driveLetter "C" -newSize "250GB"
Pinging CONCU
.Before:
Letter Label Capacity   Available Utilization
------ ----- --------   --------- -----------
C:           159.45 GiB 42.14 GiB 73.57 %
After:
Letter Label Capacity   Available  Utilization
------ ----- --------   ---------  -----------
C:           249.45 GiB 132.14 GiB 47.03 %

C has been resized to 249.4472 GB successfully.
#>

<#
Sample VM Migation Plan (time window = 3 hours):

Pre-emptively resolve disks merging errors prior to migrating VM, then expand C:\ volume after migration
1.	Gracefully shutdown VM (5 minutes)
2.	Make a file-level copy backup of VM (5 to 60 minutes)
3.	Export to Interim Storage and Import VM at New Storage Destination (5 to 60 minutes minutes)
4.	Resize C:\ volume (5 minutes)
5.  Perform Validation (20 minutes)
a.	Success - no further actions
b.	Failure - unregister VM > re-register VM from backup > re-validate (20 minutes)
#> 

Leave a Reply

Your email address will not be published. Required fields are marked *