Additional Doc Restructure
All checks were successful
GitOps Automatic Documentation Deployment / Sync Docs to https://kb.bunny-lab.io (push) Successful in 4s
GitOps Automatic Documentation Deployment / Sync Docs to https://docs.bunny-lab.io (push) Successful in 6s

This commit is contained in:
2026-01-27 05:57:50 -07:00
parent e73bb0376f
commit 886fd0db07
78 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
## 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.
```

View File

@@ -0,0 +1,9 @@
**Purpose**:
You may find that you cannot delete a VHDX file for a virtual machine you removed from Hyper-V and/or Hyper-V Failover Cluster, and either cannot afford to, or do not want to reboot your virtualization host(s) to unlock the file locked by `SYSTEM`.
Run the following commands to unlock the file and delete it:
```powershell
Dismount-VHD -Path "C:\Path\To\Disk.vhdx" -ErrorAction SilentlyContinue
Remove-Item -Path "C:\Path\To\Disk.vhdx" -Force
```

View File

@@ -0,0 +1,59 @@
**Purpose**: Sometimes a Hyper-V Failover Cluster node does not want to shut down, or is having issues preventing you from migrating VMs to another node in the cluster, etc. In these situations, you can run this script to force a cluster node to reboot itself.
!!! warning "Run from a Different Server"
You absolutely do not want to run the script locally on the node that is having the issues. There are commands that can only take place if the script is ran on another node in the cluster (or another domain-joined device) logged-in with a domain administrator account.
```powershell
# PowerShell Script to Reboot a Hyper-V Failover Cluster Node and Kill clussvc
# Prompt for the hostname
$hostName = Read-Host -Prompt "Enter the hostname of the Hyper-V Failover Cluster Node"
# Output the step
Try{
Write-Host "Sending reboot command to $hostName..."
# Send the reboot command
Restart-Computer -ComputerName $hostName -Force -ErrorAction Stop
}
Catch{
Write-Host "Reboot already in queue"
}
# Output waiting
Write-Host "Waiting for 120 seconds..."
# Wait for 120 seconds
Start-Sleep -Seconds 120
# Output stoping clussvc
Write-Host "Checking if Cluster Service needs to be stopped"
# Kill the clussvc service
Invoke-Command -ComputerName $hostName -ScriptBlock {
try {
$service = Get-Service -Name clussvc
$process = Get-Process -Name $service.Name
Stop-Process -Id $process.Id -Force
} catch {
Write-Host "Error stopping clussvc: $_"
}
}
# Output the step
Write-Host "Waiting for 60 seconds..."
Start-Sleep -Seconds 60
# Kill the VMMS service
Invoke-Command -ComputerName $hostName -ScriptBlock {
try {
$service = Get-Service -Name vmms
$process = Get-Process -Name $service.Name
Stop-Process -Id $process.Id -Force
} catch {
Write-Host "Error stopping VMMS: $_"
}
}
# Output the completion
Write-Host "Reboot for $hostName should now be underway."
```

View File

@@ -0,0 +1,69 @@
**Purpose**:
This script *bumps* any replication that has entered a paused state due to a replication error. The script will record failed attempts at restarting the replication. The logs will rotate out every 5-days.
``` powershell
# Define the directory to store the log files
$logDir = "C:\ClusterStorage\Volume1\Scripts\Logs"
if (-not (Test-Path $logDir)) {
New-Item -Path $logDir -ItemType Directory
}
# Get today's date and format it for the log file name
$today = Get-Date -Format "yyyyMMdd"
$logFile = Join-Path -Path $logDir -ChildPath "ReplicationLog_$today.txt"
# Manually create the log file if it doesn't exist
if (-not (Test-Path $logFile)) {
Write-Host "Log file does not exist. Attempting creation..."
try {
New-Item -Path $logFile -ItemType File
Write-Host "Log file $logFile created successfully."
} catch {
Write-Error "Failed to create log file. Error: $_"
}
}
# Delete log files older than 5 days
Get-ChildItem -Path $logDir -Filter "ReplicationLog_*.txt" | Where-Object {
$_.CreationTime -lt (Get-Date).AddDays(-5)
} | Remove-Item
# Get a list of all nodes in the cluster
$clusterNodes = Get-ClusterNode
# Iterate over each cluster node
foreach ($node in $clusterNodes) {
try {
# Get VMs with Critical ReplicationHealth from the current node
$vmsInCriticalState = Get-VMReplication -ComputerName $node.Name | Where-Object { $_.ReplicationHealth -eq 'Critical' }
} catch {
Write-Error "Failed to retrieve VMs from Node: $($node.Name). Error: $_"
# Log the error and continue to the next node
Add-Content -Path $logFile -Value "Failed to retrieve VMs from Node: $($node.Name) at $(Get-Date)"
continue
}
foreach ($vm in $vmsInCriticalState) {
Write-Host "Checking VM: $($vm.Name) on Node: $($node.Name) for replication issues."
Write-Host "Replication State for VM: $($vm.Name) is $($vm.ReplicationState)"
# Check if the replication state is valid to resume
if ($vm.ReplicationState -eq 'Resynchronization required' -or $vm.ReplicationState -eq 'WaitingForStartResynchronize') {
Write-Warning "Replication for VM: $($vm.Name) on Node: $($node.Name) is in '$($vm.ReplicationState)' state. Skipping..."
# Log the VM that is in 'Resynchronization required' or 'WaitingForStartResynchronize' state
Add-Content -Path $logFile -Value "Replication for VM: $($vm.Name) on Node: $($node.Name) is in '$($vm.ReplicationState)' state at $(Get-Date)"
continue
}
try {
# Try to resume replication for the VM
Resume-VMReplication -VMName $vm.Name -ComputerName $node.Name
Write-Host "Resumed replication for VM: $($vm.Name) on Node: $($node.Name)"
} catch {
Write-Error "Failed to resume replication for VM: $($vm.Name) on Node: $($node.Name) - $_"
# Write the failed VM name to the log file
Add-Content -Path $logFile -Value "Failed to resume replication for VM: $($vm.Name) on Node: $($node.Name) at $(Get-Date)"
}
}
}
```