-- Ben Armstrong, Hyper-V Program Manager
Talking about Virtual PC and Hyper-V at Microsoft
A long time ago – I blogged about how to handle WMI job objects in Hyper-V. The short story here is that if you script or program against Hyper-V – chances are you will need to look at WMI jobs to find out whether requested operations have completed (operations like starting a virtual machine, creating a virtual hard disk or taking a snapshot all return their status through job objects).
When we first started implementing WMI job objects, we built the system to update the status of a WMI job and to delete the job when the operation was completed. There is an obvious flaw here: it was never possible to tell if an operation succeeded or failed, because the moment the operation completed (successfully or not) we would set the status on the WMI job and then immediately delete it. Whoops.
To deal with this we came up with a simple solution: once an operation is completed we maintain the WMI job in memory for 5 minutes before we delete it. This gives any programs enough time to check the status and act appropriately. However, this introduced a new problem. Each of those WMI job objects took up memory to maintain – and on a large, active system we could waste a lot of memory keeping them around. So a second change was made: if we find that we have over 4096 WMI job objects in memory – we start expiring the oldest jobs to reduce memory usage.
For the most part – this system works just fine. However – there are a couple of cases where I hear from people who accidentally hit WMI job expiration problems. Two specific examples that come to mind are:
Now, for the most part the Hyper-V UI attempts to handle WMI Job expiration has elegantly as possible – and will just report that we cannot find the job associated with the requested operation and direct you to look at the event log for details. But it is something that should be considered if you are writing your own code against our interfaces – jobs are not available eternally, and under load they can disappear quite quickly.
Cheers, Ben
I need to automate the process of exporting a virtual machine recently – and when I checked around I found that while there are a number of sample scripts out there that show you how to do this (some even on this blog) they are all written in VBScript. As I am now to the stage where PowerShell is my preferred scripting language – I sat down and wrote up this short script:
# Function for handling WMI jobs / return values
Function ProcessResult($result, $successString, $failureString)
{
#Return success if the return value is "0"
if ($result.ReturnValue -eq 0)
{write-host $successString}
#If the return value is not "0" or "4096" then the operation failed
ElseIf ($result.ReturnValue -ne 4096)
{write-host $failureString " Error value:" $result.ReturnValue}
Else
{#Get the job object
$job=[WMI]$result.job
#Provide updates if the jobstate is "3" (starting) or "4" (running)
while ($job.JobState -eq 3 -or $job.JobState -eq 4)
{write-host $job.PercentComplete "% complete"
start-sleep 1
#Refresh the job object
$job=[WMI]$result.job}
#A jobstate of "7" means success
if ($job.JobState -eq 7)
{write-host $successString
return $true}
{write-host $failureString
write-host "ErrorCode:" $job.ErrorCode
write-host "ErrorDescription" $job.ErrorDescription
return $false}
}
# Prompt for the Hyper-V Server to use
$HyperVServer = Read-Host "Specify the Hyper-V Server to use (enter '.' for the local computer)"
# Prompt for the virtual machine to use
$VMName = Read-Host "Specify the name of the virtual machine"
# Prompt for the path to export to
$ExportPath = Read-Host "Specify the path to place the exported virtual in"
# Get the management service
$VMMS = gwmi -namespace root\virtualization Msvm_VirtualSystemManagementService -computername $HyperVServer
# Get the virtual machine object
$VM = gwmi MSVM_ComputerSystem -filter "ElementName='$VMName'" -namespace "root\virtualization" -computername $HyperVServer
# Export the virtual machine
$result = $VMMS.ExportVirtualSystem($VM, $true, $ExportPath)
# Check to make sure we succeeded
$exportSucceeded = ProcessResult $result "Virtual machine exported." "Failed to export virtual machine."
Some quick notes to make about this script:
On Monday I posted a script for configuring virtual machine CPU scheduler settings. This script got me to thinking about another use for the virtual machine CPU reserve.
You see, it can also be used to ensure that you do not unintentionally start too many virtual machines at once.
If you were to set the CPU reserve on each virtual machine at 20% (or at 20,000 using the underlying API) then it is not possible to start extra virtual machines once you hit a ratio of 5 virtual processors for each physical processor. This is actually what System Center Virtual Machine Manager does to enforce limits on the system.
Here is a script that will do just that:
write-host "ErrorDescription" $job.ErrorDescription}
# Prompt for the new CPU reservation
$NewReservation = Read-Host "Specify the CPU reservation (from 0-100000)"
# Get all VSSDs for non-snapshots
$VSSDs = gwmi "MSVM_VirtualSystemSettingData" -namespace "root\virtualization" -computername $HyperVServer | ? {$_.SettingType -eq 3}
foreach ($VSSD in $VSSDs)
# Get the related VM
$VM = $VSSD.getRelated("MSVM_ComputerSystem") | select -first 1
# Get the processor setting data
$ProcSetting = $VSSD.getRelated("Msvm_ProcessorSettingData") | select -first 1
# Update ProcSetting with the new value
$ProcSetting.Reservation = $NewReservation
# Apply the changes to the processor setting data back to the virtual machine
$result = $VMMS.ModifyVirtualSystemResources($VM, $ProcSetting.GetText(1))
# Process the result
$successMessage = "Updated processor scheduling settings on '" + $VM.ElementName + "'"
$failureMessage = "Failed to update processor scheduling settings on " + $VM.ElementName + "'"
ProcessResult $result $successMessage $failureMessage
If you run this script and specify “20000” then you will be able to run at a ratio of 5 virtual processors for each physical processor. If you run this script and specify “25000” then you will be able to run at a ratio of 4 virtual processors for each physical processor.
Note that this will not apply to any virtual machine snapshots, or to any newly created virtual machines, so it is a little fragile as a solution.
A while ago I did a number of posts talking about the different controls that are available to you in regards to how Hyper-V schedules virtual machine processor resources (Hyper-V CPU Scheduling–Part 1, Hyper-V CPU Scheduling–Part 2, Hyper-V CPU Scheduling–Part 3 and Hyper-V CPU Scheduling–Part 4). Today I want to share a PowerShell script that shows you how to configure these settings programmatically:
# SettingType = 3 ensures that we do not get snapshots
$SystemSettingData = $VM.getRelated("Msvm_VirtualSystemSettingData") | where {$_.SettingType -eq 3}
$ProcSetting = $SystemSettingData.getRelated("Msvm_ProcessorSettingData") | select -first 1
# Get new values from the user
$NewReservation = Read-Host "Specify the CPU reserve (from 0-10000) [Currently:"$ProcSetting.Reservation"]"
$NewLimit = Read-Host "Specify the CPU limit (from 0-10000) [Currently:"$ProcSetting.Limit"]"
$NewWeight = Read-Host "Specify the CPU weight (from 0-10000) [Currently:"$ProcSetting.weight"]"
# Update ProcSetting with the new values
$ProcSetting.Limit = $NewLimit
$ProcSetting.weight = $NewWeight
ProcessResult $result "Updated processor scheduling settings." "Failed to update processor scheduling settings."
A recent forum poster asked:
How do I write a script that figures out how many virtual processors a virtual machine has, when it is offline (i.e. not running)?
His question showed that he had made a classic mistake when working with the Hyper-V WMI APIs. You see Hyper-V usually has two WMI objects for many parts of the virtual machine – one of them representing the settings and one of them representing the live object itself.
Case in hand: for a virtual processor you have “MSVM_Processor” which represents the live object, and you have “MSVM_ProcessorSettingData” which represents its settings.
Looking at the wrong object at the wrong time can cause problems. Live objects should be used for gathering usage information about running virtual machines – settings object should be used for getting and setting configuration details. 90% of the time – the settings object is what you want (and 90% of the time it is the object that ends with the string “SettingData”).
Here is a simple script that gets a virtual machines processor count using both objects:
# Set the stage
Write-host "There are two ways to get a virtual machines processor count, the wrong way and the right way..."
write-host
# Let's do the wrong way first
# Get the virtual processor objects
$vProcs = $vm.GetRelated("MSVM_Processor")
Write-host "Doing it the wrong way (using Msvm_Processor):"
write-host "The" $VMName "virtual machine is configured with" $vProcs.count "virtual processors."
# Now let's do it the right way
Write-host "Doing it the right way (using Msvm_ProcessorSettingData):"
write-host "The" $VMName "virtual machine is configured with" $ProcSetting.VirtualQuantity "virtual processors."
If I run this on a running virtual machine – both methods return the same thing:
But if the virtual machine is not running – only the MVSM_ProcessorSettingData actually tells me what I want to know:
Here is a dangerous script! Given a virtual machine name and a snapshot name, it will delete the specified snapshot *and* any snapshots underneath it in the snapshot tree that Hyper-V manager displays:
# Prompt for the name of the snapshot to delete the tree from
$SnapshotName = Read-Host "Specify the name of the snapshot to delete the tree from (warning - a lot of snapshots will be deleted)"
# Find the snapshot that we want to delete
$Snapshot = gwmi -Namespace root\virtualization -Query "Associators Of {$VM} Where AssocClass=Msvm_ElementSettingData ResultClass=Msvm_VirtualSystemSettingData" | where {$_.ElementName -eq $SnapshotName} | select -first 1
# Delete the snapshot
$result = $VMMS.RemoveVirtualSystemSnapshotTree($Snapshot)
$deleteSucceeded = ProcessResult $result "Deleted snapshot tree." "Failed to delete snapshot tree."
Note that this script actually uses a different WMI method to delete the snapshot sub-tree (when compared to deleting a single snapshot). There is no way to stop the deletion once it is started, and there is no confirmation prompt in this script – so be careful when you use it!
Here is a script that will allow you to delete a single snapshot from a Hyper-V virtual machine:
# Prompt for the name of the snapshot to delete
$SnapshotName = Read-Host "Specify the name of the snapshot to delete"
$result = $VMMS.RemoveVirtualSystemSnapshot($Snapshot)
$deleteSucceeded = ProcessResult $result "Deleted snapshot." "Failed to delete snapshot."
Now that we know how to take a snapshot, and how to list snapshots – let’s start applying snapshots:
# Prompt for the name of the snapshot to apply
$SnapshotName = Read-Host "Specify the name of the snapshot to apply"
# Find the snapshot that we want to apply
# Apply the snapshot
$result = $VMMS.ApplyVirtualSystemSnapshot($VM, $Snapshot)
$applySucceeded = ProcessResult $result "Snapshot applied." "Failed to apply snapshot."
This is a fairly standard Hyper-V WMI script. One thing to be aware of is that this operation will fail if the virtual machine is running when you try to apply the snapshot – the virtual machine needs to be turned off or put into a saved state first.
After the relatively trick “taking a snapshot” script – today’s snapshot script is quite easy. Listing the snapshots that a virtual machine has:
# Get snapshot objects associated with the virtual machine
$Snapshots = gwmi -Namespace root\virtualization -Query "Associators Of {$VM} Where AssocClass=Msvm_ElementSettingData ResultClass=Msvm_VirtualSystemSettingData"
write-host $VMName "has the following snapshots:"
# Display a formatted list of snapshots
$Snapshots | sort CreationTime | Format-Table -Property @{n='Snapshot Name';e={$_.ElementName}}
One thing to point out here is that I am using a WMI query that looks at the association class (MSVM_ElementSettingData) as well as the result class (MSVM_VirtualSystemSettingData). The reason why I do this is that if you grab all MSVM_VirtualSystemSettingData classes that are associated with a virtual machine – you will get two extra entries: one for the active virtual machine, and one for the latest snapshot (which results in one of your snapshots being duplicated). The method that I am using here ensures that you only get snapshots, and you do not get any duplicates.
This week I am going to be sharing a bunch of scripts that show you how to interact with virtual machine snapshots in Hyper-V. And I am going to start off with the hardest one – taking a snapshot:
# Prompt for the name of the new snapshot
$SnapshotName = Read-Host "Specify the name of the new snapshot"
# Take the snapshot
$result = $VMMS.CreateVirtualSystemSnapshot($VM)
# Process the results
$snapshotSucceeded = ProcessResult $result "The snapshot was taken." "Failed to take snapshot."
# If the snapshot is created - rename it
if ($snapshotSucceeded)
# Get the job object from the results
$job2=[WMI]$result.job
# Get the snapshot that is assocated with the completed job
$newSnapshot = $job2.GetRelated("Msvm_VirtualSystemSettingData") | select -first 1
# Change the "ElementName"
$newSnapshot.ElementName = $SnapshotName
# Apply the changes to the snapshot
$result2 = $VMMS.ModifyVirtualSystem($VM, $newSnapshot.GetText(1))
# Check to see if renaming the snapshot succeeds
$renameSucceeded = ProcessResult $result2 "The new snapshot was renamed." "Failed to rename the new snapshot."
The tricky part of this script is not actually taking the snapshot (which is done with CreateVirtualSystemSnapshot) but naming the snapshot after it is taken (Hyper-V always auto-generates a snapshot name for you – and you need to rename it if you want to have a custom snapshot name). Actually, that isn’t hard either – the hard bit is *finding* the snapshot after it is taken in order to name it.
The secret here is that the new snapshot object (in WMI) will actually be associated with the job object once the snapshot is complete. So this script waits for the snapshot job to complete, then it gets the related system setting data from the completed job, and uses that to rename the snapshot. Once you have figured this bit out – the rest is quite straight forward.