Collapse Differencing Disk Chains
Purpose¶
Sometimes things go awry with backup servers and Hyper-V and a bunch of extra .avhdx
virtual differencing disks are created, taking up a ton of space. This can be problematic because if you run out of space, the virtual machines running on that underlying storage will stop working. Sometimes this can involve dozens or even hundreds of differencing disks in rare cases that need to be manually merged or "collapsed" down to reclaim the lost space.
This script automatically iterates through the entire differencing disk chain all the way back to the base disk / parent, and automatically collapses the chain downward from the newest checkpoint (provided as an argument to the script) to the original (non-differencing) base disk. This can automate a huge amount of work when this issue happens due to backup servers or other unexplainable anomalies.
Powershell Script¶
You need to copy the contents of the following somewhere on your computer and save it as Get-HyperVParentDisks.ps1
.
param (
[Parameter(Mandatory=$true, HelpMessage="Specify the path to the AVHDX file.")]
[string]$AVHDXPath,
[Parameter(Mandatory=$false, HelpMessage="Specify this flag to merge disks into their parents.")]
[switch]$MergeIntoParents,
[Parameter(Mandatory=$false, HelpMessage="Specify this flag to simulate the merging process without actually performing it.")]
[switch]$DryRun
)
function Get-ParentDisk {
param (
[string]$ChildDisk
)
$diskInfo = Get-VHD -Path $ChildDisk
return $diskInfo.ParentPath
}
function Get-AllParentDisksChain {
param ([string]$CurrentDisk)
$parentDiskChain = @()
while ($CurrentDisk) {
$parentDisk = Get-ParentDisk -ChildDisk $CurrentDisk
if ($parentDisk) {
$parentDiskChain += $CurrentDisk # Add the current disk to the chain before moving to the parent
$CurrentDisk = $parentDisk
} else {
break
}
}
$parentDiskChain += $CurrentDisk # Add the base disk at the end of the chain
return $parentDiskChain
}
function Merge-DiskIntoParent {
param ([string]$ChildDisk, [string]$ParentDisk, [int]$DiskNumber, [int]$TotalDisks)
if ($DryRun) {
Write-Output "[Differential Disk $DiskNumber of $TotalDisks]"
Write-Output "Child: $ChildDisk"
Write-Output "Parent: $ParentDisk"
Write-Output "[Dry Run] Would Merge Child into Parent"
} else {
Write-Output "[Differential Disk $DiskNumber of $TotalDisks]"
Write-Output "Child: $ChildDisk"
Write-Output "Parent: $ParentDisk"
try {
$childDiskInfo = Get-VHD -Path $ChildDisk
if ($childDiskInfo.VhdFormat -ne 'VHDX' -or $childDiskInfo.VhdType -ne 'Differencing') {
Write-Output "Error: $ChildDisk is not a valid differencing disk (AVHDX) and cannot be merged."
throw "Invalid Disk Type for Merging."
}
Merge-VHD -Path $ChildDisk -DestinationPath $ParentDisk -Confirm:$false
Write-Output "Successfully Merged Child into Parent"
} catch {
Write-Output "Failed to Merge ${ChildDisk} into ${ParentDisk}: $_"
Restart-Service -Name vmms
throw "Merge failed. Halting Script."
}
}
}
Write-Output "Starting Parent Disk Chain Search for: $AVHDXPath"
$parentDiskChain = Get-AllParentDisksChain -CurrentDisk $AVHDXPath
$totalDisks = $parentDiskChain.Count
Write-Output "Total Parent Disks Found: $totalDisks"
if ($MergeIntoParents) {
Write-Output "`nStarting Merge Process..."
for ($i = 0; $i -lt ($totalDisks - 1); $i++) {
$currentDisk = $parentDiskChain[$i]
$nextDisk = $parentDiskChain[$i + 1]
Merge-DiskIntoParent -ChildDisk $currentDisk -ParentDisk $nextDisk -DiskNumber ($i + 1) -TotalDisks $totalDisks
}
Write-Output "Merge Process Completed."
} elseif ($DryRun) {
Write-Output "[Dry Run] Merge Simulation Completed."
}
Script Usage Syntax¶
You can run the script in a few different ways, seen below:
Merge Disks: .\Get-HyperVParentDisks.ps1 -MergeIntoParents -AVHDXPath "Z:\Example\Virtual Hard Disks\Example.avhdx"
Example Output
Starting parent disk search for: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
Total parent disks found: 6
Starting merge process...
[Differential Disk 1 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_8B9EDF27-6B7D-4766-AE60-ED67BF3055AE.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 2 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_8B9EDF27-6B7D-4766-AE60-ED67BF3055AE.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_6607B03C-E3F8-49CC-A69B-68BA3DACE81F.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 3 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_6607B03C-E3F8-49CC-A69B-68BA3DACE81F.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_BB68092D-626C-47AA-A20D-93DB0FEB4167.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 4 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_BB68092D-626C-47AA-A20D-93DB0FEB4167.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_2E6147A8-1C6E-4A07-ABA8-6DE3AFB79974.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 5 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_2E6147A8-1C6E-4A07-ABA8-6DE3AFB79974.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER.vhdx
[Dry Run] Would merge child into parent
Merge process completed
Dry Run (Non-Destructive): .\Get-HyperVParentDisks.ps1 -MergeIntoParents -DryRun -AVHDXPath "Z:\Example\Virtual Hard Disks\Example.avhdx"
Example Output
Starting parent disk search for: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
Total parent disks found: 6
Starting merge process...
[Differential Disk 1 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_E5F78673-3DAD-4211-AC0A-A3BDEB763B63.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_8B9EDF27-6B7D-4766-AE60-ED67BF3055AE.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 2 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_8B9EDF27-6B7D-4766-AE60-ED67BF3055AE.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_6607B03C-E3F8-49CC-A69B-68BA3DACE81F.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 3 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_6607B03C-E3F8-49CC-A69B-68BA3DACE81F.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_BB68092D-626C-47AA-A20D-93DB0FEB4167.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 4 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_BB68092D-626C-47AA-A20D-93DB0FEB4167.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_2E6147A8-1C6E-4A07-ABA8-6DE3AFB79974.avhdx
[Dry Run] Would merge child into parent
[Differential Disk 5 of 6]
Child: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER_2E6147A8-1C6E-4A07-ABA8-6DE3AFB79974.avhdx
Parent: Z:\DISK-MERGE-TESTER\Virtual Hard Disks\DISK-MERGE-TESTER.vhdx
[Dry Run] Would merge child into parent
Merge process completed.