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