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.
