## 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`.  
``` powershell
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"`

!!! info "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"`

!!! info "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.
    ```