Udostępnij za pośrednictwem


Talking about “Host-only KVPs”– or “How do you store random stuff in a VM configuration file” [Hyper-V Script]

Recently I have had a couple of different people approach me with this problem:

They are developing software to manage / work with virtual machines – and they need a way to associate custom data with a virtual machine.  The problem that they face is how to do this in a way that will survive if the virtual machine gets backed up, failed over, live migrated, snapshotted, etc…

Thankfully there is a simple answer.  In Windows Server 2008 R2 we extended the Key-Value Pair (KVP) functionality to include the concept of “host-only” KVPs.  Simply put – these are key value pairs that get stored in the virtual machine configuration file and can be written / read from the parent partition; but never get sent into the virtual machine.  The way this works is that you use AddKvpItems to add a Msvm_KvpExchangeDataItem where the source field has been set to “4” – which indicates that this is a host-only KVP.

Combining this with some of my existing code from my general DVD tool script and my parent KVP script – results in this hand “all purpose host-only KVP script”:

 # Function for handling WMI jobs / return values
 Function ProcessResult($result, $successString, $failureString)
 {
    #Return success if the return value is "0"
    if ($result.ReturnValue -eq 0)
       {write-host $successString} 
  
    #If the return value is not "0" or "4096" then the operation failed
    ElseIf ($result.ReturnValue -ne 4096)
       {write-host $failureString "  Error value:" $result.ReturnValue}
  
    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 "% complete"
           start-sleep 1
  
           #Refresh the job object
           $job=[WMI]$result.job}
  
        #A jobstate of "7" means success
        if ($job.JobState -eq 7)
           {write-host $successString}
        Else
           {write-host $failureString
           write-host "ErrorCode:" $job.ErrorCode
           write-host "ErrorDescription" $job.ErrorDescription}
        }
 }
  
 # Filter for parsing XML data
 filter Import-CimXml 
 { 
    # Create new XML object from input
    $CimXml = [Xml]$_ 
    $CimObj = New-Object -TypeName System.Object 
  
    # Iterate over the data and pull out just the value name and data for each entry
    foreach ($CimProperty in $CimXml.SelectNodes("/INSTANCE/PROPERTY[@NAME='Name']")) 
       { 
          $CimObj | Add-Member -MemberType NoteProperty -Name $CimProperty.NAME -Value $CimProperty.VALUE 
       } 
  
    foreach ($CimProperty in $CimXml.SelectNodes("/INSTANCE/PROPERTY[@NAME='Data']")) 
       { 
          $CimObj | Add-Member -MemberType NoteProperty -Name $CimProperty.NAME -Value $CimProperty.VALUE 
       } 
  
    # Display output
    $CimObj 
 } 
  
 # Prompt for the Hyper-V Server to use
 $HyperVServer = Read-Host "Specify the Hyper-V Server to use (enter '.' for the local computer)"
  
 # Prompt for the virtual machine to use
 $VMName = Read-Host "Specify the name of the virtual machine"
  
 # Get the management service
 $VMMS = gwmi Msvm_VirtualSystemManagementService -namespace root\virtualization -computername $HyperVServer
  
 # Get the virtual machine object
 $VM = gwmi MSVM_ComputerSystem -namespace root\virtualization -computername $HyperVServer | where {$_.ElementName -eq $VMName} 
  
 # Get the virtual machine setting data
 $VSSD = $VM.getRelated("Msvm_VirtualSystemSettingData") | where {$_.SettingType -eq 3}
  
 # Setup parameters for main menu prompt
 $message = "What do you want to do with host-only KVPs?"
 $list = New-Object System.Management.Automation.Host.ChoiceDescription "&List", "List the current host-only KVPs."
 $add = New-Object System.Management.Automation.Host.ChoiceDescription "&Add", "Add a new host-only KVP / update an existing one."
 $delete = New-Object System.Management.Automation.Host.ChoiceDescription "&Delete", "Delete a host-only KVP."
 $quit = New-Object System.Management.Automation.Host.ChoiceDescription "&Quit", "Exit the HostKVPTools script."
 $options = [System.Management.Automation.Host.ChoiceDescription[]]($list, $add, $delete, $quit)
  
 do 
    {
    
    # Setting up $KVPSettingData needs to be done inside the loop - as this value needs to be
    # refreshed after any add / modify / delete operation
    
    # Get KVP settings object
    $Query = "Associators of {$VSSD} Where ResultClass=Msvm_KvpExchangeComponentSettingData"
    $KvpSettingData = gwmi -Query $Query -namespace root\virtualization -computername $HyperVServer 
    
    # Ask the user what they want to do with the host-only KVPs
    write-host
    $promptResult = $host.ui.PromptForChoice("", $message, $options, 0)
    write-host 
  
    switch ($promptResult)
       {
          0  {# Display existing host-only KVPs
                 $KvpSettingData.HostOnlyItems | Import-CimXml 
              }
      
          1 {# Add a new host-only KVP / update an existing host-only KVP
          
                 # Prompt for the name for the new KVP
                 $NewKVPName = Read-Host "Specify the name of the KVP to add / update"
  
                 # Prompt for the KVP data
                 $NewKVPData = Read-Host "Specify the data for the KVP"
          
                 # Create new Msvm_KvpExchangeDataItem object
                 $wmiClassString = "\\" + $HyperVServer + "\root\virtualization:Msvm_KvpExchangeDataItem"
                 $newKvpExchangeDataItem = ([WMIClass]$wmiClassString).CreateInstance()
  
                 # Populate the KVP data item - a source of "4" indicates that it is "host-only"
                 $newKvpExchangeDataItem.Name = $NewKVPName
                 $newKvpExchangeDataItem.Data = $NewKVPData
                 $newKvpExchangeDataItem.Source = 4
  
                 # Check to see if we can find the key
                 $matchingString = '<PROPERTY NAME="Name" TYPE="string"><VALUE>' + $NewKVPName
                 $existingKVP = $KvpSettingData.HostOnlyItems | ? {$_ -match $matchingString}
                 
                 # If the key exists - modify it.  If not, create it.
                 if ($existingKVP) 
                     {$result = $VMMS.ModifyKvpItems($Vm, $newKvpExchangeDataItem.GetText(1))}
                 else {$result = $VMMS.AddKvpItems($Vm, $newKvpExchangeDataItem.GetText(1))}
  
                 # Handle the results                
                 ProcessResult $result "The host-only KVP has been added." "Failed to add host-only KVP."
             }
            
          2 {# Delete an existing host-only KVP
            
                 # Prompt for the name for the KVP to delete
                 $KVPName = Read-Host "Specify the name of the KVP to delete"
                 
                 # Check to see if we can find the key
                 $matchingString = '<PROPERTY NAME="Name" TYPE="string"><VALUE>' + $KVPName
                 $existingKVP = $KvpSettingData.HostOnlyItems | ? {$_ -match $matchingString}
                 
                 # If the key exists - remove it.
                 if ($existingKVP) 
                     {$result = $VMMS.RemoveKvpItems($Vm, $existingKVP)
                      ProcessResult $result "The host-only KVP has been deleted." "Failed to delete host-only KVP."}
                 else {write-host "No host-only KVP exists with that name"}
  
             }
            
  
        }
    }
    
 until ($promptResult -eq 3)

Cheers,

Ben

HostKVPTool.zip