341 lines
14 KiB
PowerShell
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 "" |