Save to My DOJO
Table of contents
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.
How the Script Works
I designed the script to be quick and simple to use and understand. Under the hood, it does these things:
- Gathers the location of the virtual machine’s configuration files. It considers that location to be the root.
- 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.
- 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.
- 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.
- 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.
- Emits a custom object that contains:
- The name of the virtual machine
- The name of the virtual machine’s Hyper-V host
- The virtual machine’s ID (a string-formatted GUID)
- A boolean ($true or $false) value that indicates if the virtual machine’s storage is consistent
- An array that contains a record of each location. Each entry in the array is itself a custom object with these two pieces:
- The component name(Configuration, Checkpoints, etc.)
- 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:
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
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"
will try this one for sure ! THX for sharing