Retrieving Linux Hyper-V KVPs in PowerShell

This is a small PowerShell script I wrote to retrieve the key value pairs that Hyper-V exposes to Linux VMs. To view the most up to date version of this script, go to this GitHub link, otherwise look at the initial public code at the bottom of this blog post.

What is Hyper-V Key-Value Pair Data Exchange?

Simply put, Hyper-V Key-Value Pair Data Exchange is simply a way of transferring data between Hyper-V and a VM, both to and from. In Windows, this is exposed as registry keys in HKLM:\Software\Microsoft\Virtual Machine.

I am definitely not the most informed on this, if you would like to learn more, please see the following links:

How my script works

This script doesn't really do anything fancy. In linux, these values are exposed via a file. My script primarily targets the content in /var/lib/hyperv/.kvp_pool_3. In fact, if you wanted to access them without a script, you could just by using cat in bash or Get-Content in PowerShell.

$ cat /var/lib/hyperv/.kvp_pool_3
HostNameHyperV.ad.example.comHostingSystemEditionId8HostingSystemNestedLevel0HostingSystemOsMajor10HostingSystemOsMinor0HostingSystemProcessorArchitecture9HostingSystemProcessorIdleStateMax0HostingSystemProcessorThrottleMax100HostingSystemProcessorThrottleMin5HostingSystemSpMajor0HostingSystemSpMinor0PhysicalHostNameHYPERVPhysicalHostNameFullyQualifiedHyperV.ad.example.comVirtualMachineDynamicMemoryBalancingEnabled0VirtualMachineId5A535AFF-1708-4F6B-9B23-E506DF8CC5C5VirtualMachineNamekvp-testingtu18.04

But, that is literally just a long string with no delimiter. Or is it?

Each KVP in the file is stored with a 512 byte key and 2048 byte value. Everything after the actual content is null content basically. So, we can use that to parse the data.

Essentially, my script reads the file as a byte stream, and parses out 512 bytes for the key, 2048 for the value, and then starts on the next one. I store this in a hashtable, and then return it as PSObject (because objects are awesome).

Here is a sample output:

PS /home/serveradmin> Get-LinuxHypervKvpValues

HostingSystemOsMinor                        : 0
HostingSystemProcessorThrottleMax           : 100
HostingSystemProcessorIdleStateMax          : 0
PhysicalHostName                            : HYPERV
HostingSystemEditionId                      : 8
HostingSystemOsMajor                        : 10
HostingSystemNestedLevel                    : 0
HostingSystemSpMajor                        : 0
HostName                                    : HyperV.ad.example.com
VirtualMachineDynamicMemoryBalancingEnabled : 0
HostingSystemSpMinor                        : 0
VirtualMachineName                          : kvp-testingtu18.04
HostingSystemProcessorThrottleMin           : 5
HostingSystemProcessorArchitecture          : 9
PhysicalHostNameFullyQualified              : HyperV.ad.example.com
VirtualMachineId                            : 5A535AFF-1708-4F6B-9B23-E506DF8CC5C5

In addition, I added the ability to send a PSSession to the script, so I could get info about remote VMs without making sure to copy this function over.

The Script

Function Get-LinuxHypervKvpValues {
    <#
    .SYNOPSIS
    Retrive Hyper-V KVP Data Exchange values from a Linux based Hyper-V virtual machine.

    .DESCRIPTION
    Hyper-V provides key value pairs to VMs to send VM/Host info in a safe way. This function retrieves those key value pairs and returns them as a PowerShell object.

    .PARAMETER Path
    Location of the kvp_pool you would like to access. They are usually named .kvp_pool_x, where x is an integer between 0 and 4.

    .PARAMETER Session
    Optional parameter for a PSSession to remotely retrieve the KVP values.

    .EXAMPLE
    Get-LinuxHypervKvpValues -Session (New-PSSession -Hostname 192.168.1.1 -Username serveradmin)

    .NOTES
    Cody Ernesti
    github.com/soarinferret

    .LINK
    https://github.com/Soarinferret/PowerShell
    https://blog.kanto.cloud/retrieving-linux-hyper-v-kvps-in-powershell

    #>

    Param(
        [ValidateScript({Test-Path $_ -PathType 'Leaf'})] 
        [String]$Path = "/var/lib/hyperv/.kvp_pool_3",

        [Parameter(Mandatory=$false)]
        [ValidateScript({$_.State -eq "Opened"})] 
        [PsSession]$Session
    )
    function get-kvp ($KvpPath){
        $KEY_LENGTH = 512
        $VALUE_LENGTH = 2048

        $KVP_POOL = Get-Content $KvpPath -AsByteStream 

        $properties = @{}
        for($y = 0; $y -lt $KVP_POOL.Length; $y = $y + $KEY_LENGTH + $VALUE_LENGTH){

            $properties.add(
                # Key
                $([System.Text.Encoding]::UTF8.GetString($KVP_POOL[$y..$($y+$KEY_LENGTH -1)]) -replace "`0", ""),
                
                # Value
                $([System.Text.Encoding]::UTF8.GetString($KVP_POOL[$($y+$KEY_LENGTH)..$($y+$VALUE_LENGTH -1)]) -replace "`0", "")
            )
        }
        return New-Object PSObject -Property $properties
    }

    if($Session -and $Session.State -eq "Opened"){
        Invoke-Command -Session $Session -ScriptBlock ${function:get-kvp} -ArgumentList $Path
    }else{
        get-kvp $Path
    }
}
Show Comments