How to Use PowerShell to Get the Hyper-V Host Name from a Guest VM

Understanding the infrastructure on which virtual machines (VMs) are running is crucial for IT administrators, especially those managing large-scale virtual environments. Whether you’re troubleshooting a virtual machine issue, conducting audits, or updating inventory systems, knowing which host a VM resides on can significantly streamline your workflows. This becomes particularly important in environments powered by Microsoft Hyper-V, where VMs are distributed across multiple hypervisors. Automating the process of identifying a VM’s hypervisor host using PowerShell not only saves time but also reduces the risk of manual error.

Background

Modern managed service providers (MSPs) and IT departments often oversee hundreds or thousands of virtual machines across many physical hosts. In these dynamic environments, tracking VM-to-host relationships manually is inefficient. This is where scripts like the one under review become invaluable. This PowerShell script is specifically designed to run within a guest VM and determine the name of the Hyper-V host that it’s running on. It optionally writes that information to a custom field in NinjaOne—an increasingly popular endpoint management platform among MSPs. The script is purpose-built for Windows environments and leverages native registry keys and NinjaOne’s CLI commands for data extraction and integration.

The Script

#Requires -Version 3

<#
.SYNOPSIS
    Reports on the hypervisor hostname of a guest VM. Must be ran on a Hyper-V guest VM.
.DESCRIPTION
    Reports on the hypervisor hostname of a guest VM. Must be ran on a Hyper-V guest VM.
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).

.PARAMETER -TextCustomFieldName
    Enter the text custom field name where the hypervisor hostname will be saved.

.EXAMPLE
    (No Parameters)
    
    [Info] WIN11-EDUCATION is hosted on: HYPERV-HOST-1

.EXAMPLE
    -TextCustomFieldName "text"
    
    [Info] Attempting to set Ninja custom field 'text'...
    [Info] Successfully set Ninja custom field 'text' to value 'HYPERV-HOST-1'.

    [Info] WIN11-EDUCATION is hosted on: HYPERV-HOST-1

.NOTES
    Minimum OS Architecture Supported: Windows 8, Windows Server 2012
    Release Notes: Initial Release
#>

[CmdletBinding()]
param (
    [Parameter()]
    [string]$TextCustomFieldName
)

begin {
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    function Test-IsVM {
        try {
            # first test via model. Hyper-V and VMWare sets these properties automatically and they are read-only
            if ($PSVersionTable.PSVersion.Major -lt 3) {
                $model = (Get-WmiObject -Class Win32_ComputerSystem -Property Model -ErrorAction Stop).Model
            }
            else {
                $model = (Get-CimInstance -ClassName Win32_ComputerSystem -Property Model -ErrorAction Stop).Model
            }

            # Hyper-V uses "Virtual Machine" VMWare uses "VM"
            if ($model -match "Virtual|VM"){
                return $true
            }
            else{
                # Proxmox can be identified via the manufacturer
                if ($PSVersionTable.PSVersion.Major -lt 3) {
                    $manufacturer = (Get-WmiObject -Class Win32_BIOS -Property Manufacturer -ErrorAction Stop).Manufacturer
                }
                else {
                    $manufacturer = (Get-CimInstance -Class Win32_BIOS -Property Manufacturer -ErrorAction Stop).Manufacturer
                }

                if ($manufacturer -match "Proxmox"){
                    return $true
                }
                else{
                    return $false
                }
            }
        }
        catch {
            Write-Host -Object "[Error] Unable to validate whether or not this device is a VM."
            Write-Host -Object "[Error] $($_.Exception.Message)"
            exit 1
        }
    }

    if (-not (Test-IsVM)){
        Write-Host "[Error] Host is not a virtual machine."
        exit 1
    }

    function Set-NinjaProperty {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $True)]
            [String]$Name,
            [Parameter()]
            [String]$Type,
            [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
            $Value,
            [Parameter()]
            [String]$DocumentName,
            [Parameter()]
            [Switch]$Piped
        )
        # Remove the non-breaking space character
        if ($Type -eq "WYSIWYG") {
            $Value = $Value -replace ' ', '&nbsp;'
        }
        
        # Measure the number of characters in the provided value
        $Characters = $Value | ConvertTo-Json | Measure-Object -Character | Select-Object -ExpandProperty Characters
    
        # Throw an error if the value exceeds the character limit of 200,000 characters
        if ($Piped -and $Characters -ge 200000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 200,000 characters.")
        }
    
        if (!$Piped -and $Characters -ge 45000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded: the value is greater than or equal to 45,000 characters.")
        }
        
        # Initialize a hashtable for additional documentation parameters
        $DocumentationParams = @{}
    
        # If a document name is provided, add it to the documentation parameters
        if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
        
        # Define a list of valid field types
        $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
    
        # Warn the user if the provided type is not valid
        if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type. Please check here for valid types: https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
        
        # Define types that require options to be retrieved
        $NeedsOptions = "Dropdown"
    
        # If the property is being set in a document or field and the type needs options, retrieve them
        if ($DocumentName) {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
            }
        }
        else {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
            }
        }
        
        # Throw an error if there was an issue retrieving the property options
        if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
            
        # Process the property value based on its type
        switch ($Type) {
            "Checkbox" {
                # Convert the value to a boolean for Checkbox type
                $NinjaValue = [System.Convert]::ToBoolean($Value)
            }
            "Date or Date Time" {
                # Convert the value to a Unix timestamp for Date or Date Time type
                $Date = (Get-Date $Value).ToUniversalTime()
                $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
                $NinjaValue = $TimeSpan.TotalSeconds
            }
            "Dropdown" {
                # Convert the dropdown value to its corresponding GUID
                $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
                $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
            
                # Throw an error if the value is not present in the dropdown options
                if (!($Selection)) {
                    throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown options.")
                }
            
                $NinjaValue = $Selection
            }
            default {
                # For other types, use the value as is
                $NinjaValue = $Value
            }
        }
            
        # Set the property value in the document if a document name is provided
        if ($DocumentName) {
            $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
        }
        else {
            try {
                # Otherwise, set the standard property value
                if ($Piped) {
                    $CustomField = $NinjaValue | Ninja-Property-Set-Piped -Name $Name 2>&1
                }
                else {
                    $CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1
                }
            }
            catch {
                Write-Host -Object "[Error] Failed to set custom field."
                throw $_.Exception.Message
            }
        }
            
        # Throw an error if setting the property failed
        if ($CustomField.Exception) {
            throw $CustomField
        }
    }

    if ($env:TextCustomFieldName -and $env:TextCustomFieldName -notlike ''){
        $TextCustomFieldName = $env:TextCustomFieldName
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    $ExitCode = 0

    $regPath = "HKLM:\Software\Microsoft\Virtual Machine\Guest\Parameters"

    Write-Host ""

    # if regPath is not present, error out
    if (-not (Test-Path $regPath)){
        Write-Host "[Error] Registry key cannot be found. This either means that $env:computername is not a Hyper-V guest, or the 'Data Exchange' integration is disabled in the VM settings."
        exit 1
    }

    # if registry key exists, get value of property
    $HyperVHost = (Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue).PhysicalHostName

    if ([string]::IsNullOrWhiteSpace($HyperVHost)){
        Write-Host "[Error] Registry key exists but the value is blank.`n"
        exit 1
    }
    else{
        Write-Host "[Info] $env:computername is hosted on: $HyperVHost"
    }

    # write to custom field if value is supplied
    if ($TextCustomFieldName){

        # attempt custom field write
        try {
            Write-Host "[Info] Attempting to set Ninja custom field '$TextCustomFieldName'..."
            Set-NinjaProperty -Name $TextCustomFieldName -Type "Text" -Value $HyperVHost -ErrorAction Stop
            Write-Host "[Info] Successfully set Ninja custom field '$TextCustomFieldName' to value '$HyperVHost'.`n"
        }
        catch {
            Write-Host "[Error] Error setting custom field '$TextCustomFieldName' to value '$HyperVHost'."
            Write-Host "$($_.Exception.Message)"
            Write-Host ""
            $ExitCode = 1
        }
    }

    exit $ExitCode
}
end {
    
    
    
}

 

Detailed Breakdown

At a high level, this script performs the following tasks:

  1. Validates the Environment
    • It confirms the script is running in a VM by checking system properties like model and BIOS manufacturer.
    • It verifies that the script has administrative privileges, a requirement to read system-level registry keys.
  2. Checks for Hyper-V Registry Key
    • The key HKLM:\Software\Microsoft\Virtual Machine\Guest\Parameters is read. This registry location is populated when the Hyper-V Data Exchange Integration Service is enabled.
  3. Retrieves Hostname of Hypervisor
    • The value of the PhysicalHostName property in the above registry path is the actual host the VM resides on.
  4. Writes to NinjaOne (Optional)
    • If a custom field name is provided via a parameter or environment variable, the script will attempt to write the discovered hostname to that field using NinjaOne’s property setting functions.
  5. Error Handling and Reporting
    • Comprehensive error messages help in diagnosing why a script might fail—be it due to lack of admin privileges, disabled integration services, or other reasons.

Potential Use Cases

Case Study:

An MSP managing 500+ VMs across 30 Hyper-V hosts needs to generate an up-to-date asset inventory report. With this script, the technician rolls it out through NinjaOne across all managed devices. The script logs the Hyper-V hostnames back into a designated custom field. Within minutes, the MSP has a live and accurate mapping of guest VMs to physical hosts, greatly simplifying license compliance checks and resource allocation reviews.

Comparisons

Compared to other methods, such as:

  • Manual Registry Browsing: Prone to errors and time-consuming.
  • Using Hyper-V Manager or Failover Cluster Manager: Requires GUI access and does not scale well.
  • Remote WMI Queries: Often blocked by firewalls or requires complex credential setups.

This script stands out due to its automation-readinessNinjaOne integration, and low system impact. It’s also portable, meaning it doesn’t depend on the broader network configuration or domain trust settings.

FAQs

Q: Does this script work on VMware or Proxmox?

A: It detects if it’s a VM on these platforms but only retrieves the host name if the guest is running under Hyper-V with Data Exchange enabled.

Q: What happens if the Data Exchange integration is disabled?

A: The script will exit and notify the user that the necessary registry key cannot be found.

Q: Can this script be modified to run without NinjaOne?

A: Yes. The NinjaOne portion is optional and only runs if a custom field name is provided.

Q: Is administrative privilege really necessary?

A: Yes. Reading from the registry path used requires elevated permissions.

Implications

Being able to programmatically determine the VM host can have wide-reaching implications. From a security standpoint, this visibility helps trace VM movements and ensure they reside on secure or compliant hosts. For disaster recovery, knowing where each VM lives helps accelerate failover and replication planning. Moreover, for license management, this data provides clarity on resource allocation across clusters.

Recommendations

  • Enable Hyper-V Data Exchange Services on all guest VMs to ensure this script functions correctly.
  • Run as Administrator to avoid permission errors.
  • Test on a Small Group before scaling out to production environments.
  • Utilize NinjaOne Tags or Notes alongside the custom field for more detailed inventory management.
  • Schedule Regular Runs to maintain updated host mappings in environments where VMs are migrated frequently.

Final Thoughts

This PowerShell script offers a robust, efficient method for extracting the host server name from a guest VM in a Hyper-V environment. With optional integration into NinjaOne’s platform, it becomes even more valuable for MSPs and enterprise IT teams looking to automate asset tracking and improve infrastructure visibility. As remote management and cloud-based operations continue to scale, tools like this script are no longer optional—they’re essential.

For IT professionals seeking to get host server name with a PowerShell script or wanting to automate how they get host server name from guest using PowerShell, this solution represents a best-in-class approach that balances simplicity with power.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service delivery tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our Terms of Use:

  • Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
  • Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
  • Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library belonging to or under the control of any other software provider.
  • Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
  • Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
  • Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
  • EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).