Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis

PHOTO EMBED

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
content_copyCOPY

https://www.altaro.com/hyper-v/free-script-vm-storage-diagnosis/