Sending data from parent to virtual machine via KVP

Last week I showed you how to gather information about the guest operating system running in a virtual machine by using the key-value pair functionality of Hyper-V.  This week I would like to drill into how to send information from the parent to the virtual machine using the same component.

Using KVP you can send arbitrary string data into the virtual machine.  You specify one string as the unique "key" and a second string as the data that is to be sent in.

In the past I have discussed how to do this with Virtual Server - but things are significantly different with Hyper-V.

In Hyper-V what you manipulate is a collection of key-value pairs that are stored in the parent for each virtual machine.  This data is then pushed to the virtual machine whenever the KVP component is started inside the virtual machine (or when a new key is set).  This makes for a bit more complicated programming model - but it enables a couple of cool features.

The first one is that a virtual machine does not need to be running in order to send a KVP to the guest OS.  If the virtual machine is off when you set a KVP then that data will be automatically pushed into the virtual machine when the OS starts up next.

The second one is that even if the information is deleted from inside the guest OS - it will be repopulated the next time the OS starts.

So without further ado - here are the scripts to add a new KVP to send from the parent to the virtual machine:

VBScript:

 Option Explicit
  
 Dim HyperVServer
 Dim VMName
 Dim NewKVPName
 Dim NewKVPData
 Dim WMIService
 Dim VSManagementService
 Dim VM
 Dim KVP
 Dim NewKvpExchangeDataItem
 Dim NewKvpExchangeDataItemArray
 Dim Result
 Dim Job
 Dim InParam
 Dim OutParam
  
 'Prompt for the Hyper-V Server to use
 HyperVServer = InputBox("Specify the Hyper-V Server to be used:")
  
 'Prompt for the VM to use
 VMName = InputBox("Specify the name of the virtual machine to create the KVP on:")
  
 'Get name for new KVP
 NewKVPName = InputBox("Specify the name for the new KVP:")
  
 'Get data for new KVP
 NewKVPData = InputBox("Specify the value for the new KVP:")
  
 'Get an instance of the WMI Service in the virtualization namespace.
 Set WMIService = GetObject("winmgmts:\\" & HyperVServer & "\root\virtualization")
  
 'Get the VirtualSystemManagementService object
 Set VSManagementService = WMIService.ExecQuery("SELECT * FROM Msvm_VirtualSystemManagementService").ItemIndex(0)
  
 'Get the VM object that we want
 Set VM = (WMIService.ExecQuery("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" & VMName & "'")).ItemIndex(0)
  
 'Get the KVP Object for the virtual machine
 Set KVP = (VM.Associators_("Msvm_SystemDevice", "Msvm_KvpExchangeComponent")).ItemIndex(0) 
  
 ' Initialize a new Msvm_KvpExchangeDataItem object
 Set NewKvpExchangeDataItem = WMIService.Get("Msvm_KvpExchangeDataItem").SpawnInstance_()
  
 'Populate the KVP data item
 NewKvpExchangeDataItem.Name = NewKVPName
 NewKvpExchangeDataItem.Data = NewKVPData
 NewKvpExchangeDataItem.Source = 0
  
 'Put new KVP data item in a correctly formatted array
 NewKvpExchangeDataItemArray = Array(1)
 NewKvpExchangeDataItemArray(0) = NewKvpExchangeDataItem.GetText_(1)
  
 'Setup the input parameter list
 Set InParam = VSManagementService.Methods_("AddKvpItems").InParameters.SpawnInstance_()
 InParam.TargetSystem = VM.Path_.Path
 InParam.DataItems = NewKvpExchangeDataItemArray 
  
 'Execute the method and store the results in OutParam
 Set OutParam = VSManagementService.ExecMethod_("AddKvpItems", InParam) 
  
 'Check to see if the job completed synchronously
 if (OutParam.ReturnValue = 0) then
    Wscript.Echo "KVP data item created."
 elseif (OutParam.ReturnValue <> 4096) then
    Wscript.Echo "Failed to create KVP data item."
 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 "Failed to create KVP data item."
       Wscript.Echo "ErrorCode:" & Job.ErrorCode
       Wscript.Echo "ErrorDescription:" & Job.ErrorDescription
    else
       Wscript.Echo "KVP data item created."
    end If
 end if

PowerShell:

 # Prompt for the Hyper-V Server to use
 $HyperVServer = Read-Host "Specify the Hyper-V Server to be used"
  
 # Prompt for the VM to use
 $VMName = Read-Host "Specify the name of the virtual machine to create the KVP on"
  
 # Get name for new KVP
 $NewKVPName = Read-Host "Specify the name for the new KVP"
  
 # Get data for new KVP
 $NewKVPData = Read-Host "Specify the value for the new KVP"
  
 # Get the VirtualSystemManagementService object
 $VSManagementService = gwmi -class "Msvm_VirtualSystemManagementService" -namespace "root\virtualization" -computername $HyperVServer
  
 # Get the virtual machine object
 $query = "Select * From Msvm_ComputerSystem Where ElementName='" + $VMName + "'"
 $Vm = gwmi -namespace root\virtualization -query $query -computername $HyperVServer
  
 # Get the KVP Component Object
 $query = "Associators of {$Vm} Where AssocClass=Msvm_SystemDevice ResultClass=Msvm_KvpExchangeComponent"
 $Kvp = gwmi -namespace root\virtualization -query $query -computername $HyperVServer
  
 # Create new Msvm_KvpExchangeDataItem object
 $wmiClassString = "\\" + $HyperVServer + "\root\virtualization:Msvm_KvpExchangeDataItem"
 $newKvpExchangeDataItem = ([WMIClass]$wmiClassString).CreateInstance()
  
 # Populate the KVP data item
 $newKvpExchangeDataItem.Name = $NewKVPName
 $newKvpExchangeDataItem.Data = $NewKVPData
 $newKvpExchangeDataItem.Source = 0
  
 #This might throw an error - but it is necessary
 $temp = $newKvpExchangeDataItem.psbase.GetText(1)
  
 # Create the new KVP data item
 $result = $VSManagementService.AddKvpItems($Vm, $newKvpExchangeDataItem.psbase.GetText(1))
  
 #Return success if the return value is "0"
 if ($Result.ReturnValue -eq 0)
    {write-host "KVP data item created."} 
  
 #If the return value is not "0" or "4096" then the operation failed
 ElseIf ($Result.ReturnValue -ne 4096)
    {write-host "Failed to create KVP data item."}
  
   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 "KVP data item created."}
       Else
        {write-host "Failed to create KVP data item."
         write-host "ErrorCode:" $job.ErrorCode
         write-host "ErrorDescription" $job.ErrorDescription}
    }

Basically you need to create a new Msvm_KvpExchangeDataItem, populate it with the information you want, and then use AddKvpItems off of Msvm_VirtualSystemManagementService.  Once the data is sent it will appear inside the virtual machine in the registry at "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Virtual Machine\External" .  Note that this only works for Windows virtual machines with integration services installed.

Cheers,
Ben