James O'Neill's blog

Windows Platform, Virtualization and PowerShell with a little Photography for good measure.

Accessing the Hyper-V API: disks.

Accessing the Hyper-V API: disks.

  • Comments 8
  • Likes

... In which we create compact, mount, unmount vhds

In my last post I said "There are two WMI objects which do most of the work", and mentioned the one named "Msvm_ImageManagementService". I spent last week with  poor Internet connectivity and so I had to discover some of the following by using WebmTest; it's not a widely used tool but it's a kind of swiss army knife for WMI. That's given me the subject matter for another post but, It's much easier to get the information from the MSDN page for Msvm_ImageManagementService.  This gives you a list of methods you can call via WMI. There are 13: CreateDynamicVirtualHardDisk , CreateFixedVirtualHardDisk , CreateDifferencingVirtualHardDisk , ReconnectParentVirtualHardDisk , CreateVirtualFloppyDisk , MergeVirtualHardDisk , CompactVirtualHardDisk , ExpandVirtualHardDisk , ConvertVirtualHardDisk , GetVirtualHardDiskInfo , Mount , Unmount , ValidateVirtualHardDisk

For now I'm only looking at 6 of them the 3 CreateXXXvirtualDisk functions, mount and unmount, and compact.

The script which contains my functions has a line which sets a variable to point to the Image management service WMI object. With that in place I created a New-VHD function. Initially  I created "new-DynamicVHD" and "new-FixedVHD" functions. I then thought I'd merge them and have a  -fixed switch, in addition to that I pass the function a path and a size (I love the fact that Powershell  understands what 20GB means here !)

function New-VHD
{param ([string]$vhdPath , [int64]$size , [Switch]$Fixed)
   if ($fixed) { $IMGMgtSvc.psbase.invokemethod("CreateFixedVirtualHardDisk",@($vhdPath,$Size,$null) ) }
   else  { $IMGMgtSvc.psbase.invokemethod("CreateDynamicVirtualHardDisk",$arguments ) }    }

[Update. A bit of PowerShell 2.0 crept into the above. In 1.0 you can't call the .InvokeMethod  method of a WMI object directly, you have to call it via .psbase]

I changed this later to have a Parent parameter. If this is present I Invoke the CreateDifferencingVirtualHardDisk  method of the WMI object: instead of passing it and array with path,size and a null, I pass it path , parent and a null. The null is for data being returned and points to a "job" - Hyper-V creates the hard disk in the background and we can check on progress by examining the WMI object representing the Job, and the final version of the function returns the job ID, to make that easier. The version above is easier to read, but I'll make the full version available with the rest of the functions at a later date.

You can see how the Job ID can be used in the next function. Mounting a disk via WMI is easy. Just for illustration I've used two different syntaxes $IMGMgtSvc.invokemethod("MethodName",arguments ) and $IMGMgtSvc.methodName(arguments)

All that's need to the mount the disk is to call the MOUNT method with the Path to the VHD. The functions return 4096 if they start a job, so I check for that and get the Storage Job, I could poll the job until it completes but I just wait 5 seconds instead. Buried in the storage job is the Disk  index. Because disks are mounted Offline I string together commands for mounting it and pipe them into DiskPart. If there are any partitions on the disk they'll have drive letters so after letting the mount process settle I check what they are. I make sure the drive letters are echoed to the screen, but I return the index of the disk as the result of the function.

function Mount-VHD
{param ([string]$vhdPath=$(throw("You must specify a Path for the VHD")) , [Switch]$Offline)
$result=$IMGMgtSvc.mount($vhdPath)
if   ($result.returnValue -eq 4096)
      {start-sleep 5
       $StorageJob=(Get-WmiObject -Namespace root\virtualization -QUERY "select * from msvm_storageJob
where instanceID=$($result.job.split("=")[1])")
       $diskIndex=(Get-WmiObject -query "Select * from win32_diskdrive
where Model='Msft Virtual Disk SCSI Disk Device'
and ScsiTargetID=$($storageJob.TargetId)
and ScsiLogicalUnit=$($StorageJob.Lun)
and scsiPort=$($storageJob.PortNumber)").index
       if ($diskIndex -eq $null) {"Mount failed"}
       elseif (-not $offline)  {@("select disk $diskIndex",
"online disk" ,
"attributes disk clear readonly",
"exit")  | diskpart | Out-Null
       start-sleep 5                  
     get-wmiobject -query "select * from Win32_logicaldisktoPartition
where __PATH like '%disk #$diskIndex%' " |
foreach-object {$_.dependent.split("=")[1].replace('"','') | out-host }
       $diskIndex
      }
else {"Mount Failed"}
}
 

Unmounting the disk is so simple

function UnMount-VHD
{param ([string]$vhdPath )

$IMGMgtSvc.Unmount($vhdPath) }

and compacting is hardly complicated, just be aware that it takes and ARRAY of paths not a single variable.

Function Compact-VHD
{param ([string]$vhdPath)
$IMGMgtSvc.invokemethod("CompactVirtualHardDisk",@($vhdpath)) }

Comments
  • Hi James,

    Most of these methods take the VHD path as the input. However if I want to perform on of these operations on the virtual disk of a particular VM, how do I get the path for that virtual disk.

    I know we can get it from the GUI and by looking at the xml file for this VM. But I am trying to get this programmatically by using the Hyper-V APIs.

    Thanks,

    Parag

  • Hi Parag, The answer to that is coming in the next installment

    However, Set $VM to the VM and $controller to 0 or 1 and this will get the path to the controller

    $ctrlPath=((Get-WmiObject -namespace "root\virtualization" -query "Select * from Msvm_ResourceAllocationSettingData where instanceId like '$((Get-VMSettingData $VM).instanceID)%' and resourceSubtype = 'Microsoft Emulated IDE Controller' and address='$Controller'").path.path).replace("\","\\")            }

    Then  set $lun to 0 or 1 and this will get the path to the disk

    $drivePath=(Get-WmiObject -namespace "root\virtualization" -query "SELECT * FROM Msvm_ResourceAllocationSettingData WHERE (PARENT='$ctrlPath') and (Address='$LUN')").path.path.replace("\","\\")                                                                                    Then you can get the resource allocation settings data with this

    $rasd=Get-WmiObject -namespace "root\virtualization" -query "Select * from Msvm_ResourceAllocationSettingData where parent = '$DrivePath' "                                                                              The "Connection" field holds the path. If you look at the GUIDs in the VM and the RASD object you can figure how to do the query more simply. But this code was easy to share from the computer I'm on :-)

  • Thanks a lot James! I was able to get the VHD file path.

    Thanks

    -Parag

  • In my last post I explained how snapshots work and gave a little bit of PowerShell for creating a one

  • Can I get C++ code sample for the same thing any where. I find a lot of powershell and vb scripts on net but rarely C++.

  • I don't know of anyone who is posting C++. I think the theory is this is *mostly* used in scripts, and if someone wants to build a more complex app they can see what is happening in the script and write their code based on that sample.

    Hopefully someone will correct me with a URL, - if comments have closed on this post please mail it to me.

  • In which we see how to set the number of CPUs I started with getting MSVM Computer System objects - which

  • There are 3 things I get asked regularly about Hyper-V. The first is "When can I get it ?". I've covered

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment