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