Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis

Hyper-V allows you to scatter a virtual machine’s files just about anywhere. That might be good; it might be bad. That all depends on your perspective and need. No good can come from losing track of your files’ sprawl, though. Additionally, the placement and migration tools don’t always work exactly as expected. Even if the tools work perfectly, sometimes administrators don’t. To help you sort out these difficulties, I’ve created a small PowerShell script that will report on the consistency of a virtual machine’s file placement.

Free Hyper-V Script from Altaro

How the Script Works

I designed the script to be quick and simple to use and understand. Under the hood, it does these things:

  1. Gathers the location of the virtual machine’s configuration files. It considers that location to be the root.
  2. Gathers the location of the virtual machine’s checkpoint files. It compares that to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  3. Gathers the location of the virtual machine’s second-level paging files. It compares that to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  4. Gathers the location of the virtual machine’s individual virtual hard disk files. It compares each to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  5. Gathers the location of any CD or DVD image files attached to the virtual machine. It compares each to the root. If the locations aren’t the same, it marks the VM’s storage as inconsistent.
  6. Emits a custom object that contains:
    1. The name of the virtual machine
    2. The name of the virtual machine’s Hyper-V host
    3. The virtual machine’s ID (a string-formatted GUID)
    4. A boolean ($true or $false) value that indicates if the virtual machine’s storage is consistent
    5. An array that contains a record of each location. Each entry in the array is itself a custom object with these two pieces:
      1. The component name(Configuration, Checkpoints, etc.)
      2. The location of the component

It doesn’t check for pass-through. A VM with pass-through disks would have inconsistent storage placement by definition, but this script completely ignores pass-through.

The script doesn’t go through a lot of validation to check if storage locations are truly unique. If you have a VM using “\systemVMs” and “\systemVMFiles” but they’re both the same physical folder, the VM will be marked as inconsistent.

Script Usage

I built the script to be named Get-VMStorageConsistency. All documentation and examples are built around that name. You must supply it with a virtual machine. All other parameters are optional.

Use Get-Help Get-VMStorageConsistency -Full to see the built-in help.

Parameters:

  • VM (aliases “VMName” and “Name”): The virtual machine(s) to check. The input type is an array, so it will accept one or more of any of the following:
    • A string that contains the virtual machine’s names
    • A VirtualMachine object (as output from Get-VM, etc.)
    • A GUID object that contains the virtual machine’s ID
    • A WMI Msvm_ComputerSystem object
    • A WMI MSCluster_Resource object
  • ComputerName: The name of the host that owns the virtual machine. If not specified, defaults to the local system. Ignored if VM is anything other than a String or GUID. Only accepts strings.
  • DisksOnly: A switch parameter (include if you want to use it, leave off otherwise). If specified, the script only checks at the physical storage level for consistency. Examples:
    • With this switch, C:VMs and C:VMCheckpoints are the same thing (simplifies to C:)
    • With this switch, C:ClusterStorageVMs1VMFiles and C:ClusterStorageVMs1VHDXs are the same thing (simplifies to C:ClusterStorageVMs1)
    • With this switch, \storage1VMsVMFiles and \storage1VMsVHDFiles are the same thing (simplifies to \storage1VMs)
    • Without this switch, all of the above are treated as unique locations
  • IgnoreVHDFolder: A switch parameter (include if you want to use it, leave off otherwise). If specified, ignores the final “Virtual Hard Disks” path for virtual hard disks. Notes:
    • With this switch, VHDXs in “C:VMsVirtual Hard Disks” will be treated as though they were found in C:VMs
    • With this switch, VHDXs in “C:VMsVirtual Hard DisksVHDXs” will not be treated specially. This is because there is a folder underneath the one named “Virtual Hard Disks”.
    • With this switch, a VM configured to hold its checkpoints in a folder named “C:VMsVirtual Hard Disks” will not treat its checkpoints especially. This is because the switch only applies to virtual hard disk files.
  • Verbose: A built-in switch parameter. If specified, the script will use the Verbose output stream to show you the exact comparison that caused a virtual machine to be marked as inconsistent. Note: The script only traps the first item that causes a virtual machine to be inconsistent. That’s because the Consistent marker is a boolean; in boolean logic, it’s not possible to become more false. Therefore, I considered it to be wasteful to continue processing. However, all locations are stored in the Locations property of the report. You can use that for an accurate assessment of all the VMs’ component locations.

The Output Object

The following shows the output of the cmdlet run on my host with the -IgnoreVHDFolder and -Verbose switches set:

conscript_output

Output object structure:

  • Name: String that contains the virtual machine’s name
  • ComputerName: String that contains the name of the Hyper-V host that currently owns the virtual machine
  • VMId: String representation of the virtual machine’s GUID
  • Consistent: Boolean value that indicates whether or not the virtual machine’s storage is consistently placed
  • Location: An array of custom objects that contain information about the location of each component

Location object structure:

  • Component: A string that identifies the component. I made these strings up. Possible values:
    • Configuration: Location of the “Virtual Machines” folder that contains the VM’s definition files (.xml, .vmcx, .bin, etc.)
    • Checkpoints: Location of the “Snapshots” folder configured for this virtual machine. Note: That folder might not physically exist if the VM uses a non-default location and has never been checkpointed.
    • SecondLevelPaging: Location of the folder where the VM will place its .slp files if it ever uses second-level paging.
    • Virtual Hard Disk: Full path of the virtual hard disk.
    • CD/DVD Image: Full path of the attached ISO.

An example that puts the object to use:

Get-VM | .Get-VMStorageConsistency.ps1 -IgnoreVHDFolder | ? Consistent -ne $true | select Name

The above will output only the names of local virtual machines with inconsistent storage.

Script Source

The script is intended to be named “Get-VMStorageConsistency”. As shown, you would call its file (ex.: C:ScriptsGet-VMStorageConsistency). If you want to use it dot-sourced or in your profile, uncomment lines 55, 56, and 262 (subject to change from editing; look for the commented-out function { } delimiters).

<#
.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

 

Altaro Hyper-V Backup
Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

1 thoughts on "Free Hyper-V Script: Virtual Machine Storage Consistency Diagnosis"

Leave a comment or ask a question

Your email address will not be published. Required fields are marked *

Your email address will not be published. Required fields are marked *

Notify me of follow-up replies via email

Yes, I would like to receive new blog posts by email

What is the color of grass?

Please note: If you’re not already a member on the Dojo Forums you will create a new account and receive an activation email.