-1

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.

FrankU32
  • 311
  • 1
  • 3
  • 18
  • You can use the IP instead of the Host Name using integration services. – jdweng Nov 30 '22 at 14:58
  • I'm sorry I don't understand what you are getting at? – FrankU32 Nov 30 '22 at 15:08
  • You do not need HostName, IP will work. So you do not need the reverse DNS. – jdweng Nov 30 '22 at 15:15
  • 1
    I don't think you have understood the question. I need the hostname. That is the information I am trying to acquire. – FrankU32 Nov 30 '22 at 15:16
  • I do understand the question. But do you really need the hostname if you can use the IP instead? – jdweng Nov 30 '22 at 15:52
  • 1
    Yes I specifically need the hostname. I am auditing hostnames of VM's. That's why I specifically asked for how to get the hostname. – FrankU32 Nov 30 '22 at 15:55
  • Can't you run a command on remote machine using IP : \\IP\c$\Windows to get the name? Here is somebody doing something similar : https://stackoverflow.com/questions/74628880/powershell-remote-invoke-command-start-process-app#comment131730928_74628880 – jdweng Nov 30 '22 at 15:59
  • As I said, I have no access to the network of the VM. All I have is access to the hypervisor. – FrankU32 Nov 30 '22 at 16:01
  • If you have access to the HyperV host, looks like you should check *Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters* Reference: https://techcommunity.microsoft.com/t5/itops-talk-blog/find-the-hostname-of-a-hyper-v-vm/ba-p/2074171 – Dallas Nov 30 '22 at 21:48
  • I have seen that article but I'm not convinced its complete. I have checked 10+ hosts in various environments, including a fresh build with a fresh VM and none have anything in that REG key. I think you probably need to install or activate something for it to work. I just cant work out what. – FrankU32 Dec 01 '22 at 09:10
  • 1
    You guys it seems like a very under-documented capability. In fact, searching for an answer leads to 98% “find the HyperV hostname from within the VM” – Doug Maurer Dec 01 '22 at 16:49

2 Answers2

2

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
Charlieface
  • 52,284
  • 6
  • 19
  • 43
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
1

The mechanism I needed to solve this was "Hyper-V Data Exchange Service (KVP)" which you can access via WMI.

I haven't used WMI much but was able t fumble my way through it. First I queried WMI to obtain a list of the VM using the namespace 'root\virtualization\v2 "

$WMIVM = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_KvpExchangeComponent 

The data in key 'GuestIntrinsicExchangeItems' contained the DNS hostname. Once I found this I was able to work out a way to parse this data. I'm sure someone could improve on how I have done this.

$seperator = '<PROPERTY NAME="Caption" TYPE="string"></PROPERTY><PROPERTY NAME="Data" TYPE="string"><VALUE>'
$VMDNSname = ($WMIVM[2].GuestIntrinsicExchangeItems -split $seperator)[3]

The result of this still isn't quite there and needed additional parsing (I am not good at parsing)

$VMDNSname = ($VMDNSname -split "<")[0]

The whole thing looks like this. $WMIVM is an array. I have parsed element 2 in the array. Hence it is referred to as '$WMIVM[2]' in this example

$WMIVM = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_KvpExchangeComponent
$seperator = '<PROPERTY NAME="Caption" TYPE="string"></PROPERTY><PROPERTY NAME="Data" TYPE="string"><VALUE>'
$VMDNSname = ($WMIVM[2].GuestIntrinsicExchangeItems -split $seperator)[3]
$VMDNSname = ($VMDNSname -split "<")[0]
FrankU32
  • 311
  • 1
  • 3
  • 18