Hyper-V VHD Summary

When I made the move to Windows 8, one of my tasks was to migrate my test environment from VirtualBox to Hyper-V. Windows 8 includes a client Hyper-V feature that is easy to use and includes PowerShell support. Plus I needed to expand my Hyper-V skills anyway, especially from the console. After setting up a number of VMs, I realized I needed to get a handle on disks: what files were in use for which VM? The Get-VM cmdlet writes a VirtualMachine object to the pipeline. One of its properties is HardDrives.

PS S:\> $vm = get-vm "XP Lab"
PS S:\> $vm.GetType().Name
PS S:\> $vm | select *

VMName                      : XP Lab
VMId                        : da416538-7a72-4b57-b08d-7780d81745b6
Id                          : da416538-7a72-4b57-b08d-7780d81745b6
Name                        : XP Lab
State                       : Off
OperationalStatus           : {Ok}
PrimaryOperationalStatus    : Ok
SecondaryOperationalStatus  :
StatusDescriptions          : {Operating normally}
PrimaryStatusDescription    : Operating normally
SecondaryStatusDescription  :
Status                      : Operating normally
Heartbeat                   :
ReplicationState            : Disabled
ReplicationHealth           : NotApplicable
ReplicationMode             : None
CPUUsage                    : 0
MemoryAssigned              : 0
MemoryDemand                : 0
MemoryStatus                :
SmartPagingFileInUse        : False
Uptime                      : 00:00:00
IntegrationServicesVersion  :
ResourceMeteringEnabled     : False
ConfigurationLocation       : C:\ProgramData\Microsoft\Windows\Hyper-V
SnapshotFileLocation        : C:\ProgramData\Microsoft\Windows\Hyper-V
AutomaticStartAction        : StartIfRunning
AutomaticStopAction         : Save
AutomaticStartDelay         : 0
SmartPagingFilePath         : C:\ProgramData\Microsoft\Windows\Hyper-V
NumaAligned                 :
NumaNodesCount              : 1
NumaSocketCount             : 1
IsDeleted                   : False
ComputerName                : SERENITY
Notes                       :
Path                        : C:\ProgramData\Microsoft\Windows\Hyper-V
CreationTime                : 8/20/2012 5:43:38 PM
IsClustered                 : False
SizeOfSystemFiles           : 29366
ParentSnapshotId            :
ParentSnapshotName          :
MemoryStartup               : 536870912
DynamicMemoryEnabled        : False
MemoryMinimum               : 536870912
MemoryMaximum               : 1099511627776
ProcessorCount              : 1
RemoteFxAdapter             :
NetworkAdapters             : {Network Adapter}
FibreChannelHostBusAdapters : {}
ComPort1                    : Microsoft.HyperV.PowerShell.VMComPort
ComPort2                    : Microsoft.HyperV.PowerShell.VMComPort
FloppyDrive                 : Microsoft.HyperV.PowerShell.VMFloppyDiskDrive
DVDDrives                   : {DVD Drive on IDE controller number 1 at location 0}
HardDrives                  : {Hard Drive on IDE controller number 0 at location 0}
VMIntegrationService        : {Time Synchronization, Heartbeat, Key-Value Pair Exchange, Shutdown…}

The property is actually a nested object.

PS S:\> $vm.HardDrives

VMName ControllerType ControllerNumber ControllerLocation DiskNumber Path
—— ————– —————- —————— ———- —-
XP Lab IDE            0                0                             D:\VHD\XPLab.vhd

That tells me where the VHD is, but not about it. For that I can use Get-VHD.

PS S:\> $vm.HardDrives | Get-VHD

ComputerName            : SERENITY
Path                    : D:\VHD\XPLab.vhd
VhdFormat               : VHD
VhdType                 : Dynamic
FileSize                : 8709523456
Size                    : 10737418240
MinimumSize             : 10725765120
LogicalSectorSize       : 512
PhysicalSectorSize      : 512
BlockSize               : 2097152
ParentPath              :
FragmentationPercentage : 0
Alignment               : 0
Attached                : False
DiskNumber              :
IsDeleted               : False
Number                  :

There is some great information here like sizes, type and format. All I need now is a way to combine all of this information so that for every VM I can get a summary of pertinent VHD information. This is a great scenario for using Select-Object and creating a custom property.

$vm.HardDrives | Select VMName,Path,@{Name="Size";Expression={ (GetVHD $_.path).Size}}

VMName                                    Path                                                             Size
——                                    —-                                                             —-
XP Lab                                    D:\VHD\XPLab.vhd                                          10737418240

In the hash table, the Expression script block is running Get-VHD and returning the Size property. Eventually I want this for all VMs which means I’ll need to expand the HardDrives property.

$vm | select -expandproperty HardDrives | Select VMName,Path,@{Name="Size";Expression={ (GetVHD $_.path).Size}},
@{Name="FileSize";Expression={(GetVHD $_.path).FileSize}}

VMName                         Path                                                     Size                       FileSize
——                         —-                                                     —-                       ——–
XP Lab                         D:\VHD\XPLab.vhd                                  10737418240                     8709523456

That is very promising. In fact, let’s cut to the chase.

#requires -version 3.0

Function GetVHDSummary {

Get Hyper-V VHD Information
Get a summary report of all associated VHDs with their respective virtual
machines. This requires the Hyper-V PowerShell module.
PS C:\> Get-VHDSummary

VMName     : CHI-Client02
Path       : D:\VHD\Win7_C.vhd
Type       : Dynamic
Format     : VHD
SizeGB     : 25
FileSizeGB : 21

VMName     : CHI-Client02
Path       : C:\Users\Public\Documents\Hyper-V\Virtual Hard Disks\CHI-Client2-Swap.vhdx
Type       : Dynamic
Format     : VHDX
SizeGB     : 4
FileSizeGB : 1

VMName     : Win2012-01
Path       : D:\VHD\Win2012-01_340B1F8F-FFFB-4DCD-9B3A-7819D8BCE3C2.avhdx
Type       : Differencing
Format     : VHDX
SizeGB     : 40
FileSizeGB : 2

PS C:\> get-vhdsummary | sort Type | format-table -GroupBy Type -Property VMName,Path,*Size*
PS C:\> get-vhdsummary | Copy-item -destination Z:\VHDBackup
PS C:\> get-vhdsummary | measure-object -property FilesizeGB -sum


GetVM  | Select-Object -expandproperty harddrives |
Select-Object -property VMName,Path,
@{Name="Type";Expression={(GetVHD -Path $_.Path).VhdType}},
@{Name="Format";Expression={(GetVHD -Path $_.Path).VhdFormat}},
@{Name="SizeGB";Expression={((GetVHD -Path $_.Path).Size)/1GB -as [int]}},
@{Name="FileSizeGB";Expression={((GetVHD -Path $_.Path).FileSize)/1GB -as [int]}}

} #end function

This function writes a summary object for each VM which means I can further sort, filter or whatever I need to do. Because this can take a bit of time to run, I’ll save the results to a variable.

PS S:\> $summary = get-vhdsummary

One unexpected bonus was that it helped me identify a VM with a “bad” VHD reference.

PS S:\> $summary | where {-Not $_.type}

VMName     : Windows Server 2012 VHD Boot
Path       : C:\Win8BetaServer.vhd
Type       :
Format     :
SizeGB     : 0
FileSizeGB : 0

This was a file I had renamed, which “broke” the virtual machine. Or I can see how much space my VHD files are taking.

PS S:\> $summary  | measure FileSizeGB -sum

Count    : 18
Average  :
Sum      : 102
Maximum  :
Minimum  :
Property : FileSizeGB

Or perhaps I need to back them all up.

PS S:\> $summary | copy -dest g:\vhd-backup -whatif
What if: Performing operation "Copy File" on Target "Item: D:\VHD\Win7_C.vhd Destination: G:\vhd-backup\Win7_C.vhd".

Awesome. But…..my function is based on code I developed to run from the command line which works great as a one line command. I’m not likely to have a great number of virtual machines so performance isn’t that big a deal. Plus I could always run it as a job. If I’m going to turn this into a function, perhaps it makes sense to break this up.

If you look at my function, I’m running Get-VHD multiple times for the same file. It probably makes more sense to only get it once. Here’s a revised block of code.

Function GetVHDSummary2 {

#get all virtual machines

foreach ($vm in $vms) {
  Write-Host "Getting drive info from $($vm.name)" -foregroundcolor Cyan
  #get the hard drives foreach virtual machine
  $vm.HardDrives | foreach-object {
      #a VM might have multiple drives so for each one get the VHD
      $vhd=GetVHD -path $_.path

       $_ is the hard drive object so select a few properties and
       include properties from the VHD

      $_ | Select-Object -property VMName,Path,
        @{Name="SizeGB";Expression={($vhd.Size)/1GB -as [int]}},
        @{Name="FileSizeGB";Expression={($vhd.FileSize)/1GB -as [int]}}
 } #foreach
} #foreach vm

} #close function

I’ll get the same result, and actually faster. My first version took 7.8 seconds and this takes 2.3 seconds. Because this is a function, I only have to type it once so I can add comments and even a progress message with Write-Host. This isn’t too say I can’t do this all from the console; it is just a bit more tedious.

The purpose of my post is to not only demonstrate some of the Hyper-V cmdlets, but also that what you type at the console doesn’t always make the best PowerShell script. Sometimes you need to re-think things for performance and maintainability.

Download Get-VHDSummary.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati