Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Hyper-V VHD Summary

Posted on September 14, 2012August 9, 2013

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.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!
PS S:\> $vm = get-vm "XP Lab"
PS S:\> $vm.GetType().Name
VirtualMachine
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={ (Get-VHD $_.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={ (Get-VHD $_.path).Size}}, @{Name="FileSize";Expression={(Get-VHD $_.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 Get-VHDSummary {

<#
.Synopsis
Get Hyper-V VHD Information
.Description
Get a summary report of all associated VHDs with their respective virtual
machines. This requires the Hyper-V PowerShell module.
.Example
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

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

Param()

Get-VM  | Select-Object -expandproperty harddrives | 
Select-Object -property VMName,Path,
@{Name="Type";Expression={(Get-VHD -Path $_.Path).VhdType}},
@{Name="Format";Expression={(Get-VHD -Path $_.Path).VhdFormat}},
@{Name="SizeGB";Expression={((Get-VHD -Path $_.Path).Size)/1GB -as [int]}},
@{Name="FileSizeGB";Expression={((Get-VHD -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 Get-VHDSummary2 {
Param()

#get all virtual machines
$vms=Get-VM

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=Get-VHD -path $_.path

      <#
       $_ is the hard drive object so select a few properties and
       include properties from the VHD
      #>
      $_ | Select-Object -property VMName,Path,
        @{Name="Type";Expression={$vhd.VhdType}},
        @{Name="Format";Expression={$vhd.VhdFormat}},
        @{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.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d