Search code examples
powershellhyper-v

Obtain the DNS name of a VM from the Hyper-V host its running on using PowerShell


Using PowerShell, can I obtain the DNS hostname of a virtual machine if I only have access to the Hyper-V host running the VM?

I know I can get the IP of the VM and perform a reverse DNS lookup but I do not have access to the network/DNS servers that service this VM.

I also do not have credentials for the VMs

I feel like this information is accessible via integration services but have failed to find anything useful.


Solution

  • I found searching for this term leads to many, many "get hyperv host name from within VM" answers, and very few for what you're actually trying to do.

    You should be able to use the following code on the HyperV host to retrieve the VM dns hostname.

    $code = @'
    using System;
    using System.IO;
    using System.Xml.XPath;
    using System.Management;
    
    
    // exchangeDataItem xml document sample instance
    //
    //<INSTANCE CLASSNAME="Msvm_KvpExchangeDataItem">
    //      <PROPERTY NAME="Caption" PROPAGATED="true" TYPE="string"></PROPERTY>
    //      <PROPERTY NAME="Data" TYPE="string">
    //         <VALUE>AUTOBVT-4OVYXAB</VALUE>
    //      </PROPERTY>
    //      <PROPERTY NAME="Description" PROPAGATED="true" TYPE="string"></PROPERTY>
    //      <PROPERTY NAME="ElementName" PROPAGATED="true" TYPE="string"></PROPERTY>
    //      <PROPERTY NAME="Name" TYPE="string">
    //         <VALUE>FullyQualifiedDomainName</VALUE>
    //      </PROPERTY>
    //      <PROPERTY NAME="Source" TYPE="uint16">
    //          <VALUE>2</VALUE>
    //      </PROPERTY>
    //</INSTANCE>        
    
    
    namespace HyperV
    {
        public class VirtualMachineQuery
        {
            static bool VMRunning(ManagementObject vm)
            {
                const int Enabled = 2;
    
                bool running = false;
    
                foreach (UInt16 operationStatus in (UInt16[])vm["OperationalStatus"])
                {
                    if (operationStatus == Enabled)
                    {
                        running = true;
                        break;
                    }
                }
    
                return running;
            }
    
            public static string GetVirtualSystemDNS(string vmName, string ComputerName)
            {
                ManagementScope scope = new ManagementScope((ComputerName == null ? "" : "\\" + ComputerName) + @"root\virtualization\v2", null);
    
                string value = null;
    
                string query = String.Format("select * from Msvm_ComputerSystem where ElementName = '{0}'", vmName);
    
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query));
    
                ManagementObjectCollection vms = searcher.Get();
    
                foreach (ManagementObject vm in vms)
                {
    
                    if (VMRunning(vm))
                    {
    
                        ManagementObjectCollection kvpExchangeComponents = vm.GetRelated("Msvm_KvpExchangeComponent");
                        if (kvpExchangeComponents.Count != 1)
                        {
                            throw new Exception(String.Format("{0} instance of Msvm_KvpExchangeComponent was found", kvpExchangeComponents.Count));
                        }
    
                        foreach (ManagementObject kvpExchangeComponent in kvpExchangeComponents)
                        {
                            foreach (string exchangeDataItem in (string[])kvpExchangeComponent["GuestIntrinsicExchangeItems"])
                            {
                                XPathDocument xpathDoc = new XPathDocument(new StringReader(exchangeDataItem));
                                XPathNavigator navigator = xpathDoc.CreateNavigator();
                                navigator = navigator.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'FullyQualifiedDomainName']");
                                if (navigator != null)
                                {
                                    navigator = navigator.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()");
                                    value = navigator.Value;
                                    break;
                                }
                            }
                        }
                    }
                }
                return value;
            }
        }
    }
    '@
    
    $referencingassemblies = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.XML.dll", "C:\windows\Microsoft.NET\Framework\v4.0.30319\System.Management.dll"
    
    Add-Type -TypeDefinition $code -Language CSharp -ReferencedAssemblies $referencingassemblies
    

    You query with a single VM name like

    [HyperV.VirtualMachineQuery]::GetVirtualSystemDNS('VM-Name')
    

    NOTE: This requires elevation (run as administrator) and the VM to be on.

    EDIT If you want to use strictly powershell, I would recommend you use the CIM cmdlets instead of WMI. Here is a function you can use to pull the dns name that doesn't include fragile string parsing. You also weren't filtering by the VM in your answer, so that could be problematic as well.

    function Get-HypervGuestDnsHostname {
        [cmdletbinding()]
        Param(
            [parameter(Mandatory,Position=0,ValueFromPipeline)]
            $VMName,
            [parameter(Position=1)]
            $ComputerName
        )
    
        process {
            $params = @{
                Namespace = 'root\virtualization\v2'
                Class     = 'Msvm_ComputerSystem'
                Filter    = "ElementName = '$VMName'"
            }
    
            $instance = Get-CimInstance @params -ComputerName $ComputerName |
                Get-CimAssociatedInstance -ResultClassName Msvm_KvpExchangeComponent -ComputerName $ComputerName
    
            foreach($entry in $instance){
                foreach($kvp in $entry.GuestIntrinsicExchangeItems){
                    $node = ([xml]$kvp).SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Name']/VALUE[child::text() = 'FullyQualifiedDomainName']")
    
                    if($node){
                        $node.SelectSingleNode("/INSTANCE/PROPERTY[@NAME='Data']/VALUE/child::text()").value
                    }
                }
            }
        }
    }
    

    Naturally, you can call it like

    Get-HypervGuestDnsHostname -VMName VM-Name
    

    or

    'VM-Name' | Get-HypervGuestDnsHostname