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
Comments
- Anonymous
November 23, 2011
Is this similar to getting data into a vmware guest using OVF properties? Why doesn't Microsoft support the OVF standard and just do something similar to how this is done using an open standard? Simply map the OVF properties to the VM using a mounted iso or you could mount them to a registry hive. Seems you are accomplishing the same thing but in a proprietary way.