Handling Job Objects with Hyper-V WMI scripting
Many of the methods in the Hyper-V WMI have an odd way of returning status. They can either immediately return a return value - or instead return a job object - and it can be hard to predict which one you are going to get. The reason for this is that some methods are always synchronous (and return a return value), some methods are always asynchronous (and return a job object) while other methods can be executed synchronously or asynchronously on the back end by WMI, and could return either.
The job object returned by asynchronous methods allows you to track the progress of the operation, and get detailed error information in the case of a failure.
Given this you have two options when working with our WMI interfaces:
Ignore method results.
This is simple and for many scripts is acceptable, as you can figure out whether the method succeeded or not through observing the results.
Do the work to handle the results.
Thankfully, Hyper-V does help a little here. All methods return two objects: a return value and a job path. If the return value is anything but "4096" then you know the method executed synchronously - and a return value of "1" means success. If the return value is "4096" then you need to go and look at the job object.
Methods that always complete asynchronously will always return "4096" in the return value.
Digging into the second option - and starting with this script for mounting a virtual hard disk - let's look at how to add proper support for handling the method results.
This is relatively simple with PowerShell where:
#Specify the VHD to be mounted
$VHDName = "F:\Windows.vhd"
#Get the MSVM_ImageManagementService
$VHDService = get-wmiobject -class "Msvm_ImageManagementService" -namespace "root\virtualization" -computername "."
#Mount the VHD
$Result = $VHDService.Mount($VHDName)
Becomes:
#Specify the VHD to be mounted
$VHDName = "F:\Windows.vhd"
#Get the MSVM_ImageManagementService
$VHDService = get-wmiobject -class "Msvm_ImageManagementService" -namespace "root\virtualization" -computername "."
#Mount the VHD
$Result = $VHDService.Mount($VHDName)
#Return success if the return value is "0"
if ($Result.ReturnValue -eq 0)
{write-host "Operation completed sucessfully"}
#If the return value is not "0" or "4096" then the operation failed
ElseIf ($Result.ReturnValue -ne 4096)
{write-host "Operation failed"}
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
start-sleep 1
#Refresh the job object
$job=[WMI]$Result.job}
#A jobstate of "7" means success
if ($job.JobState -eq 7)
{write-host "Operation completed sucessfully"}
Else
{write-host "Operation failed"
write-host "ErrorCode:" $job.ErrorCode
write-host "ErrorDescription" $job.ErrorDescription}
}
As you can see, the only addition is code to inspect and respond to the return value and job object.
In VBScript life is harder:
Option Explicit
Dim WMIService
Dim VHDService
Dim VHD
'Specify the VHD to be mounted
VHD = "F:\Windows.vhd"
'Get instance of 'virtualization' WMI service on the local computer
Set WMIService = GetObject("winmgmts:\\.\root\virtualization")
'Get the MSVM_ImageManagementService
Set VHDService = WMIService.ExecQuery("SELECT * FROM Msvm_ImageManagementService").ItemIndex(0)
'Mount the VHD
VHDService.Mount(VHD)
Becomes:
Option Explicit
Dim WMIService
Dim VHDService
Dim VHD
Dim Result
Dim Job
Dim InParam
Dim OutParam
'Specify the VHD to be mounted
VHD = "C:\Hyper-V Virtual Machines\Virtual Hard Disks\SCVMM - Domain Controller.vhd"
'Get instance of 'virtualization' WMI service on the local computer
Set WMIService = GetObject("winmgmts:\\.\root\virtualization")
'Get the MSVM_ImageManagementServiceSet
Set VHDService = WMIService.ExecQuery("SELECT * FROM Msvm_ImageManagementService").ItemIndex(0)
'Setup the input parameter list
Set InParam = VHDService.Methods_("mount").InParameters.SpawnInstance_()
InParam.Path = VHD
'Execute the method and store the results in OutParam
Set OutParam = VHDService.ExecMethod_("mount", InParam)
'Check to see if the jub completed synchronously
if (OutParam.ReturnValue = 0) then
Wscript.Echo "Operation succeeded"
elseif (OutParam.ReturnValue <> 4096) then
Wscript.Echo "Operation failed"
else
'Get the job object
set Job = WMIService.Get(OutParam.Job)
'Wait for the job to complete (3 == starting, 4 == running)
while (Job.JobState = 3) or (Job.JobState = 4)
Wscript.Echo Job.PercentComplete
WScript.Sleep(1000)
'Refresh the job object
set Job = WMIService.Get(OutParam.Job)
Wend
'Provide details if the job fails (7 == complete)
if (Job.JobState <> 7) then
Wscript.Echo "Operation failed"
Wscript.Echo "ErrorCode:" & Job.ErrorCode
Wscript.Echo "ErrorDescription:" & Job.ErrorDescription
else
Wscript.Echo "Operation succeeded"
end If
end if
Yikes! The issue here is that when you call a method under VBScript directly you do not get both the return value and job path in the output. You just get the return value. In order to get both output parameters you need to use "ExecMethod_", which means you need to make an instance of the input parameters, populate it and pass it to ExecMethod - along with the name of the method you want to run.
Cheers,
Ben
Comments
Anonymous
May 19, 2008
Sorry about the off-topic/side-topic... how do you do the layout of the code? I don't think you write all those span tags by hand (unless you know how to dilate time). Thanks in advance.Anonymous
May 22, 2008
I get an error trying to to mount a disk: Operation failed ErrorCode: 32769 ErrorDescription The system failed to mount 'C:Hyper-Vbase-xp-pro-sp3-eng-x86.vhd' with error 'General access denied error.' (0x80070005) I'm not sure why this is happening.Anonymous
June 05, 2008
oreidomar - I use Live Writer and the "insert code snippet" plugin Joe - Try running your script from an elevated command prompt Cheers, Ben