Accessing the Hyper-V API: disks.

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

Technorati Tags: Microsoft,Windows Server 2008,Hyper-v,Virtulization,Powershell