Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis
Thu Aug 18 2022 12:12:55 GMT+0000 (Coordinated Universal Time)
Saved by @pirate
<# .SYNOPSIS Verifies that a virtual machine's files are all stored together. .DESCRIPTION Verifies that a virtual machine's files are all stored together. Reports any inconsistencies in locations. .PARAMETER VM The virtual machine to check. Accepts objects of type: * String: A name of a virtual machine. * VirtualMachine: An object from Get-VM * System.GUID: A virtual machine ID. MUST be of type System.GUID to match. * ManagementObject: A WMI object of type Msvm_ComputerSystem * ManagementObject: A WMI object of type MSCluster_Resource .PARAMETER ComputerName The name of the computer that hosts the virtual machine to remove. If not specified, uses the local computer. Ignored if VM is of type VirtualMachine or ManagementObject. .PARAMETER DisksOnly Set to true if you only care if data resides on different physical disks/LUNs. Otherwise, a VM will be marked inconsistent if components exist in different folders. .PARAMETER IgnoreVHDFolder Set to true if you want to ignore the 'Virtual Hard Disks' subfolder for VHD/X files. Example: If set, then VHDXs in C:VMsVirtual Hard Disks will be treated as though they are in C:VMs Ignored when DisksOnly is set .NOTES Author: Eric Siron Version 1.0 Authored Date: October 2, 2017 .EXAMPLE Get-VMStorageConsistency -VM vm01 Reports the consistency of storage for the virtual machine named "vm01" on the local host. .EXAMPLE Get-VMStorageConsistency -VM vm01 -ComputerName hv01 Reports the consistency of storage for the virtual machine named "vm01" on the host named "vm01". .EXAMPLE Get-VM | Get-VMStorageConsistency Reports the consistency of storage for all local virtual machines. .EXAMPLE Get-VMStorageConsistency -VM vm01 -DisksOnly Reports the consistency of storage for the virtual machine named "vm01" on the local host. Only checks that components reside on the same physical storage. .EXAMPLE Get-VMStorageConsistency -VM vm01 -IgnoreVHDFolder Reports the consistency of storage for the virtual machine named "vm01" on the local host. If VHDXs reside in a Virtual Hard Disks subfolder, that will be ignored. So, if the VM's components are in \smbstoreVMs but the VHDXs are in \smbstoreVMsVirtual Hard Disks, the locations will be treated as consistent. However, if the VM's components are in \smbstoreVMsVirtual Machines while the VHDXs are in \smbstoreVMsVirtual Hard Disks, that will be inconsistent. #> #requires -Version 4 # function Get-VMStorageConsistency # Uncomment this line to use as a dot-sourced function or in a profile. Also next line and last line #{ # Uncomment this line to use as a dot-sourced function or in a profile. Also preceding line and last line [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=1)] [Alias('VMName', 'Name')] [Object[]] $VM, [Parameter(Position = 2)][String]$ComputerName = $env:COMPUTERNAME, [Parameter()][Switch]$DisksOnly, [Parameter()][Switch]$IgnoreVHDFolder ) BEGIN { $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Set-StrictMode -Version Latest function New-LocationObject { <# .SYNOPSIS Defines/creates an object matching a VM's component to its location. #> $LocationObject = New-Object -TypeName psobject Add-Member -InputObject $LocationObject -MemberType NoteProperty -Name 'Component' -Value ([System.String]::Empty) Add-Member -InputObject $LocationObject -MemberType NoteProperty -Name 'Location' -Value ([System.String]::Empty) $LocationObject } function New-StorageConsistencyReport { <# .SYNOPSIS Defines/creates a VM's storage consistency report object. #> $Report = New-Object -TypeName psobject Add-Member -InputObject $Report -MemberType NoteProperty -Name 'Name' -Value ([System.String]::Empty) Add-Member -InputObject $Report -MemberType NoteProperty -Name 'ComputerName' -Value ([System.String]::Empty) Add-Member -InputObject $Report -MemberType NoteProperty -Name 'VMId' -Value ([System.String]::Empty) Add-Member -InputObject $Report -MemberType NoteProperty -Name 'Consistent' -Value $false Add-Member -InputObject $Report -MemberType NoteProperty -Name 'Locations' -Value @() $Report } function Parse-Location { <# .SYNOPSIS Extracts the location information from a component's path. .PARAMETER Path The path to parse .PARAMETER DisksOnly If specified, returns only the drive portion of the path. If a CSV is detected, returns the mount point name. .PARAMETER TrimFile If specified, assumes that Path includes a file name. Use with VHDXs and ISOs. .PARAMETER IgnoreVHDFolder If specified, will remove any trailing 'Virtual Hard Disks' subfolder #> param( [Parameter()][String]$Path, [Parameter()][bool]$DisksOnly, [Parameter()][bool]$TrimFile, [Parameter()][bool]$IgnoreVHDFolder ) if ($DisksOnly) { if ($Path -match '([A-Za-z]:\ClusterStorage\.+?)(\|z)') { $Path = $Matches[1] } else { $Path = [System.IO.Path]::GetPathRoot($Path) } } else { if ($TrimFile) { $Path = [System.IO.Path]::GetDirectoryName($Path) } if ($IgnoreVHDFolder) { $Path = $Path -replace '\?Virtual Hard Disks\?$', '' } } $Path -replace '\$', '' } function Process-Location { param( [Parameter()][ref]$Report, [Parameter()][String]$Component, [Parameter()][String]$Location, [Parameter()][bool]$DisksOnly, [Parameter()][String]$RootLocation, [Parameter()][bool]$TrimFile = $false, [Parameter()][bool]$IgnoreVHDFolder = $false ) $ThisLocation = New-LocationObject $ThisLocation.Component = $Component $ThisLocation.Location = $Location $Report.Value.Locations += $ThisLocation $CurrentObservedLocation = Parse-Location -Path $Location -DisksOnly $DisksOnly -TrimFile $TrimFile -IgnoreVHDFolder $IgnoreVHDFolder if ($Report.Value.Consistent) { if ($CurrentObservedLocation -ne $RootLocation) { $Report.Value.Consistent = $false Write-Verbose -Message ("VM {0} on {1} failed consistency on component {2}.`r`n`tRoot component location: {3}`r`n`t{2} location: {4}" -f $Report.Value.Name, $Report.Value.ComputerName, $Component, $RootLocation, $CurrentObservedLocation) } } } } PROCESS { foreach ($VMItem in $VM) { $VMObject = $null try { switch ($VMItem.GetType().FullName) { 'Microsoft.HyperV.PowerShell.VirtualMachine' { $VMObject = Get-WmiObject -ComputerName $VM.ComputerName -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('Name="{0}"' -f $VMItem.Id) -ErrorAction Stop } 'System.GUID' { $VMObject = Get-WmiObject -ComputerName $ComputerName -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('Name="{0}"' -f $VMItem) -ErrorAction Stop } 'System.Management.ManagementObject' { switch ($VMItem.ClassPath.ClassName) { 'Msvm_ComputerSystem' { $VMObject = $VMItem } 'MSCluster_Resource' { $VMObject = Get-WmiObject -ComputerName $VMItem.ClassPath.Server -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('Name="{0}"' -f $VMItem.PrivateProprties.VmID) -ErrorAction Stop } default { $ArgEx = New-Object System.ArgumentException(('Cannot accept objects of type {0}' -f $VM.ClassPath.ClassName), 'VM') throw($ArgEx) } } } 'System.String' { if ($VMItem -ne $ComputerName -and $VMItem -ne $env:COMPUTERNAME) { $VMObject = Get-WmiObject -ComputerName $ComputerName -Namespace 'rootvirtualizationv2' -Class 'Msvm_ComputerSystem' -Filter ('ElementName="{0}"' -f $VMItem) -ErrorAction Stop | select -First 1 } } default { $ArgEx = New-Object System.ArgumentException(('Unable to process objects of type {0}' -f $VMItem.GetType().FullName), 'VM') throw($ArgEx) } } if (-not $VMObject) { throw('Unable to process input object {0}' -f $VMItem.ToString()) } } catch { Write-Error -Exception $_.Exception -ErrorAction Continue continue } $VMObjectComputerName = $VMObject.__SERVER $RelatedVMSettings = $VMObject.GetRelated('Msvm_VirtualSystemSettingData') | select -Unique $VMSettings = $RelatedVMSettings | where -Property VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' $VMHardDisks = $null $VMHardDisks = $RelatedVMSettings.GetRelated() | where -Property ResourceSubType -eq 'Microsoft:Hyper-V:Virtual Hard Disk' -ErrorAction SilentlyContinue $VMRemovableDisks = $null $VMRemovableDisks = $RelatedVMSettings.GetRelated() | where -Property ResourceSubType -match 'Microsoft:Hyper-V:Virtual (CD/DVD|Floppy) Disk' -ErrorAction SilentlyContinue $RootLocation = Parse-Location -Path $VMSettings.ConfigurationDataRoot -DisksOnly $DisksOnly $Report = New-StorageConsistencyReport $Report.Name = $VMObject.ElementName $Report.VMId = $VMObject.Name $Report.ComputerName = $VMObjectComputerName $Report.Consistent = $true Process-Location -Report ([ref]$Report) -Component 'Configuration' -Location $VMSettings.ConfigurationDataRoot -DisksOnly $DisksOnly -RootLocation $RootLocation Process-Location -Report ([ref]$Report) -Component 'Checkpoints' -Location $VMSettings.SnapshotDataRoot -DisksOnly $DisksOnly -RootLocation $RootLocation Process-Location -Report ([ref]$Report) -Component 'SecondLevelPaging' -Location $VMSettings.SwapFileDataRoot -DisksOnly $DisksOnly -RootLocation $RootLocation foreach ($VMHardDisk in $VMHardDisks) { Process-Location -Report ([ref]$Report) -Component 'Virtual Hard Disk' -Location $VMHardDisks.HostResource[0] -DisksOnly $DisksOnly -RootLocation $RootLocation -TrimFile $true -IgnoreVHDFolder $IgnoreVHDFolder.ToBool() } foreach ($VMRemovableDisk in $VMRemovableDisks) { Process-Location -Report ([ref]$Report) -Component 'CD/DVD Image' -Location $VMRemovableDisk.HostResource[0] -DisksOnly $DisksOnly -RootLocation $RootLocation -TrimFile $true -IgnoreVHDFolder $IgnoreVHDFolder.ToBool() } $Report } } #} # Uncomment this line to use as a dot-sourced function or in a profile. Also "function" and opening brace lines near top of script
Comments