The Hyper-V API - Disks

In an earlier post in this series (several posts ago now) I showed how the Msvm_virtualSystemManagementService WMI object can be used to configure resources in Hyper-V, and I started with the easy step of setting memory and CPUs which exist on a freshly created virtual machine. What about Network Cards, SCSI controllers, Disk drives and so on ? The 4 steps are the same as I outlined before i.e.

  1. Get a ResourceAllocationSettingData (RASD) object
  2. Modify one or more of its properties.
  3. Covert it to XML formatted Text,
  4. Pass the XMl as one of an array of arguments to one of the Methods of the Msvm_virtualSystemManagementService.

Where before I was using the ModifyVirtualSystemResources method, new resources need the AddVirtualSystemResources. Where memory and CPU had special ResourceAllocationSettingData (RASD) objects these ones are more generic and I have a "Get-vmRASD" function. This takes type and subtype arguments, here's a quick summary of them

Resource Type Resource Sub Type
6 Microsoft Synthetic SCSI Controller
10 Microsoft Emulated Ethernet Port
10 Microsoft Synthetic Ethernet Port
16 Microsoft Synthetic DVD Drive
21 Microsoft Virtual CD/DVD Disk
21 Microsoft Virtual Floppy Disk
21 Microsoft Virtual Hard Disk
22 Microsoft Synthetic Disk Drive

So lets see how  we use them in practice. As before I get an object for theVirtual System management service

     $VSMgtSvc=Get-WmiObject -NameSpace  "root\virtualization" -Class "MsVM_virtualSystemManagementService"

If I want to create a SCSI controller, the 4 steps above translate to:

     $SCSIRASD=Get-VMRASD -ResType 6 -ResSubType 'Microsoft Synthetic SCSI Controller' 
    $SCSIRASD.elementName="VMBus SCSI Controller" 
    $arguments = @($VM.__Path, @( $SCSIRASD.GetText([System.Management.TextFormat]::WmiDtd20) ), $null, $null ) 
    $VSMgtSvc.PSbase.InvokeMethod("AddVirtualSystemResources", $arguments)   

If I want to create a disk drive I change the type of Resource Allocation settings data, I either use

     $diskRASD=Get-VMRASD -ResType 22 -ResSubType 'Microsoft Synthetic Disk Drive'

if I want a hard drive or if I want a DVD drive it's:

     $diskRASD=Get-VMRASD 16 'Microsoft Synthetic DVD Drive'

The remaining 3 steps are the same, this time I have to set two properties: a parent - the disk controller, and the address on the controller (passed as a parameter named $LUN)

     $diskRASD.parent=(Get-VMDiskController -vm $vm -ControllerID $ControllerID -IDE).__Path 
    $diskRASD.address=$Lun 
    $arguments = @($VM.__Path, @( $diskRASD.GetText([System.Management.TextFormat]::WmiDtd20) ), $null, $null ) 
    $VSMgtSvc.PSbase.InvokeMethod("AddVirtualSystemResources", $arguments)

Then to mount a disk into the drive it's either

     $diskRASD=Get-VMRASD -resType 21 -resSubType 'Microsoft Virtual CD/DVD Disk' -server $vm.__Server 

if I want a Hard disk or if I want a DVD disk it's

    $diskRASD=Get-VMRASD -resType 21 -resSubType 'Microsoft Virtual Hard Disk'   -server $vm.__Server 

And then as before the disk needs a parent - the drive it is mounted in, which makes more sense for a DVD than a Hard disk; and it needs a connection to the Virtual hard disk, ISO or Physical disk on the host and then we continue as before

 $diskRASD.parent=(Get-VMDrive -controller (Get-VMDiskController -vm $vm -ControllerID $ControllerID -IDE)  -Lun $lun ).__Path } 
$diskRASD.Connection=$VHDPath 
$arguments = @($VM.__Path, @( $diskRASD.psbase.GetText([System.Management.TextFormat]::WmiDtd20) ), $null, $null ) 
$VSMgtSvc.psbase.invokeMethod("AddVirtualSystemResources", $arguments)

OK: you may have noticed that I'm calling functions Get-VMRASD , Get-VMDiskController and Get-VMDrive

Get-VMDiskController returns the RASD object(s) for either SCSI or IDE disk controller(s) or for both. The only thing which is a bit awkward here is that SCSI controllers don't have an obvious ID, we just get a single RASD object if there is one controller or an array if there is more than one, so I get the one I want with | Select first | select last. I showed before how I can set-up filters to take input from the pipe, and the real version of this code does exactly that ... why will become clear in just a moment

     Filter Get-VMDiskController 
    {Param ($VM , $ControllerID,  [Switch]$SCSI, [Switch]$IDE )
   if ($scsi) { $controllers=Get-WmiObject -Query "Select * From MsVM_ResourceAllocationSettingData 

                                             Where instanceId Like 'Microsoft:$($vm.name)%'
                                                     and resourceSubtype = 'Microsoft Synthetic SCSI Controller' " ` 

                                         -NameSpace "root\virtualization"  
          if ($controllerID -eq $null) {$controllers}
                     else  {$controllers | select -first ($controllerID + 1) | select -last 1} } 
      if ($IDE)  { Get-WmiObject -Query "Select * From MsVM_ResourceAllocationSettingData 
                                         Where instanceId Like 'Microsoft:$($vm.name)%\\$ControllerID%' 
                                         and resourceSubtype = 'Microsoft Emulated IDE Controller' " -NameSpace "root\virtualization"} 

}

Then I wrote Get-VM Drive which takes a controller as a parameter (and again I can pipe one or controller(s) in to that - though I've omitted  the code out for the sake of space. )

     Filter Get-VMDrive 
    {Param ($Controller, $LUN ) 
     $CtrlPath=$Controller.__Path.replace("\","\\")  
     Get-WmiObject -Query "Select * From MsVM_ResourceAllocationSettingData
                Where PARENT='$ctrlPath' and Address Like '$Lun%' " -NameSpace "root\virtualization" } 
    }

And finally I can pass the drive to Get-Disk, which looks like this

     Filter Get-VMDisk
    {Param ($Drive)
     $DrivePath=$Drive.__Path.replace("\","\\")
     Get-WmiObject -computerName $drive.__server -Query "Select * From MsVM_ResourceAllocationSettingData  
                                                          Where PARENT='$DrivePath' " -NameSpace "root\virtualization" 
    }

So I can find the VHDs mounted used by one or more VMs with

     Choose-VM -multiple  | GetVmDiskController -IDE -SCSI | GetVMDrive | get-VMDisk 

Or I can get more detailed information Like This

 Function Get-VMDiskList 
{Param ($vm) 
foreach ($v in $vm) { 
         foreach ($dc in (get-vmdiskcontroller -vm $v -ide -scsi)) { 
                 foreach ($drive in (get-vmdrive -controller $dc)) { 
                         get-vmdisk -drive $drive | select-object -property ` 
                                                       @{name="VMName"; expression={$v.elementName}}, 
                                                       @{name="VMGUID"; expression={$v.Name}}, 
                                                       @{name="ControllerName"; expression={$dc.elementName}}, 
                                                       @{name="ControllerInstanceID"; expression={$dc.InstanceId}}, 
                                                       @{name="ControllerID"; expression={$dc.instanceID.split("\")[-1]}}, 
                                                       @{name="DriveName"; expression={$drive.caption}} , 
                                                       @{name="DriveInstanceID"; expression={$drive.instanceID}}, 
                                                       @{name="DriveLUN"; expression={$drive.address}}, 
                                                       @{name="DiskPath"; expression={$_.Connection}}, 
                                                       @{name="DiskName"; expression={$_.ElementName}}, 
                                                       @{name="DiskInstanceID"; expression={$_.InstanceID}} }}} 
}

The Get-RASD code isn't nice. It's two WMI queries to get the name of the an object we need to create

     Function Get-VMRASD 
     {Param ($ResType, $ResSubType) 
      $allocCapsPath= ((Get-WmiObject -NameSpace "root\virtualization" -Query "Select * From MsVM_AllocationCapabilities
                                          Where ResourceType = $ResType AND ResourceSubType = '$ResSubType'").__Path).replace('\','\\') 
      New-Object System.Management.Managementobject((Get-WmiObject -ComputerName $server -NameSpace "root\virtualization" 
          -Query "Select * From MsVM_SettingsDefineCapabilities Where  valuerange=0 and Groupcomponent = '$AllocCapsPath'").partcomponent) 
     }

Whilst it may not be nice, knowing the Values to pass it from the table above, the properties to set, and how to pass it into AddVirtualSystemResources allows you to create what ever disk related bits you need. In a future post I'll move on to looking at how you similar things with NICs.  Then I'll have one more on creating VMs and setting the motherboard options and then I'll be posting the whole code for download - I've got some internal Microsoft people trying it out at the moment.

Technorati Tags: Microsoft,Windows Server 2008,Hyper-V,Virtualization,PowerShell,WMI

[Update the Original post had numerous proof reading errors - I hope there aren't any left]