Files
hyperv-backup-scripts/HyperVDiag.ps1

341 lines
14 KiB
PowerShell

# Hyper-V Performance Diagnostic Script
# Diagnoses issues after checkpoint/merge operations
param(
[switch]$Detailed,
[switch]$ExportReport
)
$ReportPath = "C:\Temp\HyperV-Diagnostic-$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
function Write-Output-And-Log {
param([string]$Message, [string]$Color = "White")
Write-Host $Message -ForegroundColor $Color
if ($ExportReport) {
Add-Content -Path $ReportPath -Value $Message
}
}
function Write-Section {
param([string]$Title)
Write-Output-And-Log ""
Write-Output-And-Log "============================================" "Cyan"
Write-Output-And-Log $Title "Cyan"
Write-Output-And-Log "============================================" "Cyan"
}
try {
if ($ExportReport) {
New-Item -ItemType Directory -Path (Split-Path $ReportPath -Parent) -Force -ErrorAction SilentlyContinue | Out-Null
Write-Output-And-Log "Diagnostic Report will be saved to: $ReportPath" "Yellow"
}
Write-Section "HYPER-V PERFORMANCE DIAGNOSTIC - $(Get-Date)"
# 1. Check for active merge operations
Write-Section "1. CHECKING FOR ACTIVE MERGE OPERATIONS"
$VMs = Get-VM
$MergingVMs = @()
foreach ($VM in $VMs) {
$Status = $VM.Status
if ($Status -like "*Merging*" -or $Status -like "*Deleting*") {
$MergingVMs += $VM
Write-Output-And-Log "⚠️ $($VM.Name): $Status" "Yellow"
} else {
Write-Output-And-Log "$($VM.Name): $Status" "Green"
}
}
if ($MergingVMs.Count -gt 0) {
Write-Output-And-Log ""
Write-Output-And-Log "⚠️ WARNING: $($MergingVMs.Count) VM(s) still have merge operations in progress!" "Red"
Write-Output-And-Log "This is likely causing the slowness. Wait for merges to complete." "Red"
} else {
Write-Output-And-Log ""
Write-Output-And-Log "✓ No active merge operations detected" "Green"
}
# 2. Check disk space
Write-Section "2. DISK SPACE CHECK"
$Drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 }
foreach ($Drive in $Drives) {
$UsedGB = [math]::Round($Drive.Used / 1GB, 2)
$FreeGB = [math]::Round($Drive.Free / 1GB, 2)
$TotalGB = $UsedGB + $FreeGB
$PercentFree = [math]::Round(($FreeGB / $TotalGB) * 100, 1)
$Color = "Green"
$Warning = ""
if ($PercentFree -lt 10) {
$Color = "Red"
$Warning = " ⚠️ CRITICAL - Less than 10% free!"
} elseif ($PercentFree -lt 20) {
$Color = "Yellow"
$Warning = " ⚠️ WARNING - Less than 20% free"
}
Write-Output-And-Log "$($Drive.Name) - Free: $FreeGB GB / Total: $TotalGB GB ($PercentFree% free)$Warning" $Color
}
# 3. Check checkpoint status for all VMs
Write-Section "3. CHECKPOINT STATUS FOR ALL VMs"
foreach ($VM in $VMs) {
$Checkpoints = Get-VMCheckpoint -VMName $VM.Name
$CheckpointCount = $Checkpoints.Count
if ($CheckpointCount -eq 0) {
Write-Output-And-Log "$($VM.Name): No checkpoints" "Green"
} else {
Write-Output-And-Log "$($VM.Name): $CheckpointCount checkpoint(s)" "Yellow"
foreach ($Checkpoint in $Checkpoints | Sort-Object CreationTime) {
$Age = [math]::Round((New-TimeSpan -Start $Checkpoint.CreationTime -End (Get-Date)).TotalDays, 1)
Write-Output-And-Log " - $($Checkpoint.Name) (Age: $Age days, Created: $($Checkpoint.CreationTime))"
}
}
}
# 4. Check for AVHDX files and their sizes
Write-Section "4. DIFFERENCING DISK (AVHDX) FILES"
$TotalAVHDXSize = 0
$AVHDXFiles = @()
foreach ($VM in $VMs) {
$VMHardDisks = Get-VMHardDiskDrive -VMName $VM.Name
foreach ($Disk in $VMHardDisks) {
if ($Disk.Path -match "\.avhdx$") {
if (Test-Path $Disk.Path) {
$FileInfo = Get-Item $Disk.Path
$SizeGB = [math]::Round($FileInfo.Length / 1GB, 2)
$TotalAVHDXSize += $FileInfo.Length
$AVHDXFiles += [PSCustomObject]@{
VM = $VM.Name
Path = $Disk.Path
SizeGB = $SizeGB
LastModified = $FileInfo.LastWriteTime
}
}
}
}
}
if ($AVHDXFiles.Count -eq 0) {
Write-Output-And-Log "✓ No AVHDX files found (no active checkpoints)" "Green"
} else {
$TotalAVHDXGB = [math]::Round($TotalAVHDXSize / 1GB, 2)
Write-Output-And-Log "⚠️ Found $($AVHDXFiles.Count) AVHDX files (Total: $TotalAVHDXGB GB)" "Yellow"
Write-Output-And-Log ""
foreach ($File in $AVHDXFiles | Sort-Object SizeGB -Descending) {
Write-Output-And-Log "$($File.VM): $($File.SizeGB) GB - $(Split-Path $File.Path -Leaf)"
Write-Output-And-Log " Last Modified: $($File.LastModified)"
}
}
# 5. Check disk I/O performance
Write-Section "5. CURRENT DISK I/O ACTIVITY"
Write-Output-And-Log "Sampling disk activity for 5 seconds..."
$DiskCounters = Get-Counter '\PhysicalDisk(*)\Disk Writes/sec', '\PhysicalDisk(*)\Disk Reads/sec' -SampleInterval 1 -MaxSamples 5
$Writes = $DiskCounters.CounterSamples | Where-Object { $_.Path -like "*writes*" } |
Group-Object { $_.InstanceName } |
ForEach-Object { [PSCustomObject]@{ Disk = $_.Name; AvgWrites = [math]::Round(($_.Group.CookedValue | Measure-Object -Average).Average, 1) }}
$Reads = $DiskCounters.CounterSamples | Where-Object { $_.Path -like "*reads*" } |
Group-Object { $_.InstanceName } |
ForEach-Object { [PSCustomObject]@{ Disk = $_.Name; AvgReads = [math]::Round(($_.Group.CookedValue | Measure-Object -Average).Average, 1) }}
Write-Output-And-Log ""
Write-Output-And-Log "Average Disk Activity (5 second sample):"
foreach ($Disk in $Writes) {
if ($Disk.Disk -ne "_total") {
$ReadInfo = $Reads | Where-Object { $_.Disk -eq $Disk.Disk }
$TotalIO = $Disk.AvgWrites + $ReadInfo.AvgReads
$Color = "Green"
if ($TotalIO -gt 100) { $Color = "Yellow" }
if ($TotalIO -gt 500) { $Color = "Red" }
Write-Output-And-Log "$($Disk.Disk): Reads: $($ReadInfo.AvgReads)/sec, Writes: $($Disk.AvgWrites)/sec" $Color
}
}
# 6. Check CPU and Memory usage
Write-Section "6. SYSTEM RESOURCE USAGE"
$CPU = Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 5
$AvgCPU = [math]::Round(($CPU.CounterSamples.CookedValue | Measure-Object -Average).Average, 1)
$CPUColor = "Green"
if ($AvgCPU -gt 70) { $CPUColor = "Yellow" }
if ($AvgCPU -gt 90) { $CPUColor = "Red" }
Write-Output-And-Log "Average CPU Usage: $AvgCPU%" $CPUColor
$OS = Get-CimInstance -ClassName Win32_OperatingSystem
$TotalRAM = [math]::Round($OS.TotalVisibleMemorySize / 1MB, 2)
$FreeRAM = [math]::Round($OS.FreePhysicalMemory / 1MB, 2)
$UsedRAM = $TotalRAM - $FreeRAM
$PercentUsed = [math]::Round(($UsedRAM / $TotalRAM) * 100, 1)
$RAMColor = "Green"
if ($PercentUsed -gt 80) { $RAMColor = "Yellow" }
if ($PercentUsed -gt 95) { $RAMColor = "Red" }
Write-Output-And-Log "Memory Usage: $UsedRAM GB / $TotalRAM GB ($PercentUsed% used)" $RAMColor
# 7. Check VM Storage Locations
Write-Section "7. VM STORAGE LOCATIONS"
foreach ($VM in $VMs) {
Write-Output-And-Log ""
Write-Output-And-Log "$($VM.Name):"
Write-Output-And-Log " Config: $($VM.ConfigurationLocation)"
Write-Output-And-Log " Snapshots: $($VM.SnapshotFileLocation)"
$VMHardDisks = Get-VMHardDiskDrive -VMName $VM.Name
foreach ($Disk in $VMHardDisks) {
Write-Output-And-Log " Disk: $($Disk.Path)"
}
}
# 8. Check recent Hyper-V errors
Write-Section "8. RECENT HYPER-V ERRORS (Last 24 hours)"
$Yesterday = (Get-Date).AddDays(-1)
$HyperVErrors = Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-Hyper-V-*'
Level = 2,3 # Error and Warning
StartTime = $Yesterday
} -ErrorAction SilentlyContinue | Select-Object -First 10
if ($HyperVErrors) {
Write-Output-And-Log "⚠️ Found $($HyperVErrors.Count) recent error/warning entries (showing first 10):" "Yellow"
foreach ($Error in $HyperVErrors) {
Write-Output-And-Log ""
Write-Output-And-Log "[$($Error.TimeCreated)] $($Error.LevelDisplayName): $($Error.Message)" "Yellow"
}
} else {
Write-Output-And-Log "✓ No recent errors found in Hyper-V event logs" "Green"
}
# 9. Detailed checkpoint analysis if requested
if ($Detailed) {
Write-Section "9. DETAILED CHECKPOINT ANALYSIS"
foreach ($VM in $VMs) {
$Checkpoints = Get-VMCheckpoint -VMName $VM.Name
if ($Checkpoints.Count -gt 0) {
Write-Output-And-Log ""
Write-Output-And-Log "$($VM.Name) - Detailed Checkpoint Info:"
foreach ($Checkpoint in $Checkpoints) {
Write-Output-And-Log " Checkpoint: $($Checkpoint.Name)"
Write-Output-And-Log " ID: $($Checkpoint.Id)"
Write-Output-And-Log " Type: $($Checkpoint.CheckpointType)"
Write-Output-And-Log " Created: $($Checkpoint.CreationTime)"
Write-Output-And-Log " Parent: $($Checkpoint.ParentCheckpointName)"
# Try to get AVHDX sizes for this checkpoint
$CheckpointDisks = Get-VMHardDiskDrive -VMCheckpoint $Checkpoint -ErrorAction SilentlyContinue
if ($CheckpointDisks) {
foreach ($Disk in $CheckpointDisks) {
if (Test-Path $Disk.Path) {
$Size = [math]::Round((Get-Item $Disk.Path).Length / 1GB, 2)
Write-Output-And-Log " Disk: $Size GB - $(Split-Path $Disk.Path -Leaf)"
}
}
}
}
}
}
}
# Summary and Recommendations
Write-Section "SUMMARY AND RECOMMENDATIONS"
$Issues = @()
if ($MergingVMs.Count -gt 0) {
$Issues += "⚠️ CRITICAL: Merge operations still in progress on $($MergingVMs.Count) VM(s)"
$Issues += " Action: Wait for merges to complete or restart Hyper-V service (risky)"
}
if ($AVHDXFiles.Count -gt 0) {
$Issues += "⚠️ WARNING: $($AVHDXFiles.Count) checkpoint differencing disks (AVHDX files) exist"
$Issues += " Action: Remove old checkpoints to merge these files"
}
$LowDiskDrives = $Drives | Where-Object { ($_.Free / ($_.Used + $_.Free)) -lt 0.20 }
if ($LowDiskDrives) {
$Issues += "⚠️ WARNING: Low disk space on $($LowDiskDrives.Count) drive(s)"
$Issues += " Action: Free up disk space or move VMs to larger drives"
}
if ($AvgCPU -gt 80) {
$Issues += "⚠️ WARNING: High CPU usage ($AvgCPU%)"
$Issues += " Action: Check for runaway processes or reduce VM load"
}
if ($PercentUsed -gt 90) {
$Issues += "⚠️ WARNING: High memory usage ($PercentUsed%)"
$Issues += " Action: Reduce VM memory allocations or add more RAM"
}
if ($Issues.Count -eq 0) {
Write-Output-And-Log "✓ No major issues detected!" "Green"
Write-Output-And-Log ""
Write-Output-And-Log "Your slowness might be due to:"
Write-Output-And-Log "1. Background merge operations that completed but haven't settled"
Write-Output-And-Log "2. Disk fragmentation after large merge operations"
Write-Output-And-Log "3. Windows Search or other background services indexing new data"
Write-Output-And-Log ""
Write-Output-And-Log "Recommendations:"
Write-Output-And-Log "- Restart the Hyper-V Host to clear any stuck states"
Write-Output-And-Log "- Defragment drives containing VM files"
Write-Output-And-Log "- Check Windows Task Manager for background processes"
} else {
foreach ($Issue in $Issues) {
Write-Output-And-Log $Issue "Red"
}
}
Write-Output-And-Log ""
Write-Output-And-Log "============================================" "Cyan"
Write-Output-And-Log "DIAGNOSTIC COMPLETE - $(Get-Date)" "Cyan"
Write-Output-And-Log "============================================" "Cyan"
if ($ExportReport) {
Write-Output-And-Log ""
Write-Output-And-Log "Report saved to: $ReportPath" "Green"
}
}
catch {
Write-Output-And-Log "Error running diagnostic: $_" "Red"
}
# Quick commands to try
Write-Output-And-Log ""
Write-Output-And-Log "============================================" "Yellow"
Write-Output-And-Log "QUICK DIAGNOSTIC COMMANDS" "Yellow"
Write-Output-And-Log "============================================" "Yellow"
Write-Output-And-Log ""
Write-Output-And-Log "Check for stuck merge operations:"
Write-Output-And-Log " Get-VM | Where-Object {`$_.Status -like '*Merging*'}" "Cyan"
Write-Output-And-Log ""
Write-Output-And-Log "Check disk queue length (high = problem):"
Write-Output-And-Log " Get-Counter '\PhysicalDisk(*)\Avg. Disk Queue Length'" "Cyan"
Write-Output-And-Log ""
Write-Output-And-Log "View all checkpoints:"
Write-Output-And-Log " Get-VM | ForEach-Object { Get-VMCheckpoint -VMName `$_.Name }" "Cyan"
Write-Output-And-Log ""
Write-Output-And-Log "Restart Hyper-V Management Service (last resort):"
Write-Output-And-Log " Restart-Service vmms -Force" "Cyan"
Write-Output-And-Log ""