PowerShell: Get All Hyper-V Servers in the Domain or Forest

This is a working version to correct my previous codes posted somewhere on this site – I know, I should use Tortoise or gitlab to keep track of these things. Anywho, the previous iteration has returned results faster at the cost of being inconclusive of all available nodes residing in the domain. Although I dislike the inefficiency of this current method, it seems to be the only feasible route to achieve accurate results.

function getHyperVHostsInForest{
    function includeRSAT{
        $ErrorActionPreference='stop'
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        #$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
        $rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
        $rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
        $rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
        $rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
        $rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
  
        # This command does not work on Windows 2012R2
        #$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
        #Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
        #NT\CurrentVersion.
        #At line:1 char:2
        #+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        #    + CategoryInfo          : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
        #    + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
  
        $releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
        $osVersion=[System.Environment]::OSVersion.Version
        [double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)"  
        $osName=(Get-WmiObject Win32_OperatingSystem).Name
        #$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
        #    1 {'client'}
        #    2 {'domaincontroller'}
        #    3 {'memberserver'}
        #    }
  
        $windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
  
        switch ($releaseId){
            1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
            1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
            1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
        }
      
        switch ($osVersionMajorMinor){
            {$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
            {$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
            {$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
            {$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
        }
 
        if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
            Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
            try{
                # If OS is Windows Server, then install RSAT using a different method
                if ($osName -match "^Microsoft Windows Server") {
                    # This sequence has confirmed to be valid on Windows Server 2008 R2 and above
                    Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
                    Import-Module ServerManager
                    Add-WindowsFeature RSAT-AD-PowerShell
                    }
                else{
                    Write-Verbose "This sequence targets Windows Client versions"
                    $destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
                    Write-Host "Downloading RSAT from $link..."
                    Start-BitsTransfer -Source $link -Destination $destinationFile
                    $fileCheck=Get-AuthenticodeSignature $destinationFile
                    if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
                    $wusaCommand = $destinationFile + " /quiet"
                    Write-host "Installing RSAT - please wait..."
                    Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
                    }
                return $true
                }
            catch{
                write-warning "$($error[0].Exception)"
                return $false
                }
        }else{
            Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
            return $true
            }
    }

    function listAllHyperVNodes($verbose=$true){
        try{
            $timer=[System.Diagnostics.Stopwatch]::StartNew()
            $domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}
            foreach ($domain in $domains){
                #[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
                write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
                $allClusters=(get-cluster -domain $domain).Name
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: cluster names collected"
                    }
                $nodes=$allClusters|%{try{Get-ClusterGroup -Cluster $_ -ea SilentlyContinue}catch{}}
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: Hyper V node names collected"
                }
                $hyperVs=($nodes|Group-Object -Property OwnerNode).Name
                if($verbose){
                    $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                    write-host "Minutes elapsed $elapsed`: Hyper V node names collected - Done!"
                    }
                }
            return $hyperVs
        }catch{
            Write-Error $_
            return $false
            }
    }
 
    function sortArrayStringAsNumbers([string[]]$names){
        $hashTable=@{}
        foreach ($name in $names){
            #[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
            #$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
            $x=.{[void]($name -match '(?:.(\d+)+)$');($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft(8,'0')}
            $hashTable.Add($name,$x)
            }
        $sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
        return $sorted
    }

    try{
        $null=includeRSAT;
        $hyperVHosts=listAllHyperVNodes
        $hyperVHostNames=sortArrayStringAsNumbers $hyperVHosts
        return $hyperVHostNames
    }catch{
        Write-Error $_
        return $false
        }
    }
# Short Version
function listAllHyperVNodes($verbose=$true){
    try{
        $timer=[System.Diagnostics.Stopwatch]::StartNew()
        $domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}
        foreach ($domain in $domains){
            #[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
            write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
            $allClusters=(get-cluster -domain $domain).Name
            if($verbose){
                $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                write-host "Minutes elapsed $elapsed`: cluster names collected"
                }
            $nodes=$allClusters|%{try{Get-ClusterGroup -Cluster $_ -ea SilentlyContinue}catch{}}
            if($verbose){
                $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                write-host "Minutes elapsed $elapsed`: Hyper V node names collected"
            }
            $hyperVs=($nodes|Group-Object -Property OwnerNode).Name
            if($verbose){
                $elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
                write-host "Minutes elapsed $elapsed`: Hyper V node names collected - Done!"
                }
            }
        return $hyperVs
    }catch{
        Write-Error $_
        return $false
        }
}

Leave a Reply

Your email address will not be published. Required fields are marked *