PowerShell

How to find different server types in Active Directory with PowerShell

Working as a freelancer is a great thing if you can handle it. Each day, each week something new happens, and a new problem shows up on my doorstep. It also means it's rarely dull at your job and you get to play with new stuff. But there's one drawback to this. You're often thrown at the problem, told to fix it, but usually, that's about as much information as you get. It wasn't very different today. I was told to switch Office 365 from ADFS to Password Synchronization. The critical question here is what ADConnect server is responsible for this configuration?

How to find ADConnect

While today it was just AD Connect that I had to find, yesterday it was Microsoft Exchange server. Tomorrow they will most likely ask me to fix SQL so before I tell them I quit I decided that I need some handy PowerShell command that will do my work for me. Of course, some companies name their servers after the role they have so it's quite easy to find them (like my home lab), but sometimes it's not that obvious. So what's the best way to find roles of servers without having a clue about them? Ask your Active Directory! Starting with AD Connect, I've created small function. It's not foolproof, but it works. If you have a habit of touching your AD, changing descriptions, modifying every bit of what AD Connect does when it's set up this will most likely not work.

function Find-ADConnectServer {
    [alias('Find-ADSyncServer')]
    param(

    )
    $Description = Get-ADUser -Filter { Name -like "MSOL*" } -Properties Description | Select-Object Description -ExpandProperty Description

    foreach ($Desc in $Description) {
        $PatternType = "(?<=(Account created by ))(.*)(?=(with installation identifier))"
        $PatternServerName = "(?<=(on computer ))(.*)(?=(configured))"
        $PatternTenantName = "(?<=(to tenant ))(.*)(?=(. This))"
        $PatternInstallationID = "(?<=(installation identifier ))(.*)(?=( running on ))"
        if ($Desc -match $PatternServerName) {
            $ServerName = ($Matches[0]).Replace("'", '').Replace(' ', '')

            if ($Desc -match $PatternTenantName) {
                $TenantName = ($Matches[0]).Replace("'", '').Replace(' ', '')
            } else {
                $TenantName = ''
            }
            if ($Desc -match $PatternInstallationID) {
                $InstallationID = ($Matches[0]).Replace("'", '').Replace(' ', '')
            } else {
                $InstallationID = ''
            }
            if ($Desc -match $PatternType) {
                $Type = ($Matches[0]).Replace("'", '').Replace('by ', '').Replace('the ', '')
            } else {
                $Type = ''
            }


            $Data = Get-ADComputer -Identity $ServerName
            [PSCustomObject] @{
                Name              = $Data.Name
                FQDN              = $Data.DNSHostName
                DistinguishedName = $Data.DistinguishedName
                Type              = $Type
                TenantName        = $TenantName
                InstallatioNID    = $InstallationID 
            }
        }
    }
}
Find-ADConnectServer | Format-Table -AutoSize *

When you execute it Find-ADConnectServer | Format-Table -Autosize * you get a nicely formatted output.

It also supports AD Sync if you've some old stuff in your AD, and even if you've multiple servers, accounts it should output all of them. If it doesn't work for you, please let me know, and we'll figure it out.

How to find Exchange Servers

There's a one really obvious way to find all Microsoft Exchange servers in Active Directory. It's based on verifying group membership of Exchange Servers group.

function Find-ExchangeServer {
    <#
    .SYNOPSIS
    Find Exchange Servers in Active Directory
    
    .DESCRIPTION
    Find Exchange Servers in Active Directory
    
    .EXAMPLE
    Find-ExchangeServer

    .NOTES
    General notes
    #>
    [CmdletBinding()]
    param(

    )
    $ExchangeServers = Get-ADGroup -Identity "Exchange Servers" | Get-ADGroupMember | Where-Object { $_.objectClass -eq 'computer' }
    foreach ($Server in $ExchangeServers) {
        $Data = Get-ADComputer -Identity $Server.SamAccountName -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName
        [PSCustomObject] @{
            Name              = $Data.Name
            FQDN              = $Data.DNSHostName
            OperatingSystem   = $Data.OperatingSystem
            DistinguishedName = $Data.DistinguishedName
            Enabled           = $Data.Enabled
        }
    }
}
Find-ExchangeServer | Format-Table -Autosize *

Simple, but effective.

How to find Hyper-V Servers

This little function helps to find Hyper-V servers in your Active Directory domain. It doesn't detect Windows 10 Hyper-V (bummer I know) but it seems to work fine on server environments.

function Find-HyperVServer {            
    [cmdletbinding()]            
    param()            
    try {            
        $ADObjects = Get-ADObject -Filter 'ObjectClass -eq "serviceConnectionPoint" -and Name -eq "Microsoft Hyper-V"' -ErrorAction Stop            
    } catch {            
        Write-Error "Error: $_"            
    }            
    foreach ($Server in $ADObjects) {            
        $Temporary = $Server.DistinguishedName.split(",")            
        $DistinguishedName = $Temporary[1..$Temporary.Count] -join ","        
        $Data = Get-ADComputer -Identity $DistinguishedName -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName       
        [PSCustomObject] @{
            Name              = $Data.Name
            FQDN              = $Data.DNSHostName
            OperatingSystem   = $Data.OperatingSystem
            DistinguishedName = $Data.DistinguishedName
            Enabled           = $Data.Enabled
        }
    }      
}
Find-HyperVServer | Format-Table -Autosize *

It output similar data as Microsoft Exchange command.

How to find all types of servers - Monster Edition

While working on next set I've thought that I will most likely forget all those command names soon enough and I need to have a reliable way to get my server types from Active Directory. That's why I've decided to prepare this little monster. While it may not be the most optimized code in the world it does, it's the job and returns data pretty quickly giving your full overview of servers.

function Find-ADConnectServer {
    [alias('Find-ADSyncServer')]
    param(

    )
    $Description = Get-ADUser -Filter { Name -like "MSOL*" } -Properties Description | Select-Object Description -ExpandProperty Description

    foreach ($Desc in $Description) {
        $PatternType = "(?<=(Account created by ))(.*)(?=(with installation identifier))"
        $PatternServerName = "(?<=(on computer ))(.*)(?=(configured))"
        $PatternTenantName = "(?<=(to tenant ))(.*)(?=(. This))"
        $PatternInstallationID = "(?<=(installation identifier ))(.*)(?=( running on ))"
        if ($Desc -match $PatternServerName) {
            $ServerName = ($Matches[0]).Replace("'", '').Replace(' ', '')

            if ($Desc -match $PatternTenantName) {
                $TenantName = ($Matches[0]).Replace("'", '').Replace(' ', '')
            } else {
                $TenantName = ''
            }
            if ($Desc -match $PatternInstallationID) {
                $InstallationID = ($Matches[0]).Replace("'", '').Replace(' ', '')
            } else {
                $InstallationID = ''
            }
            if ($Desc -match $PatternType) {
                $Type = ($Matches[0]).Replace("'", '').Replace('by ', '').Replace('the ', '')
            } else {
                $Type = ''
            }


            $Data = Get-ADComputer -Identity $ServerName
            [PSCustomObject] @{
                Name              = $Data.Name
                FQDN              = $Data.DNSHostName
                DistinguishedName = $Data.DistinguishedName
                Type              = $Type
                TenantName        = $TenantName
                InstallatioNID    = $InstallationID 
            }
        }
    }
}

function Find-ServerTypes {
    [cmdletbinding()]
    param(
        [string[]][ValidateSet('All', 'ADConnect', 'DomainController', 'Exchange', 'Hyper-V', 'RDSLicense', 'SQL', 'VirtualMachine')] $Type = 'All'
    )
    $Forest = Get-ADForest

    foreach ($Domain in $Forest.Domains) {
        try {
            $DomainInformation = Get-ADDomain -Server $Domain -ErrorAction Stop
        } catch {
            Write-Warning "Find-ServerTypes - Domain $Domain couldn't be reached. Skipping"
            continue
        }

        try {
            $ServiceConnectionPoint = Get-ADObject -Filter 'ObjectClass -eq "serviceConnectionPoint"' -ErrorAction Stop -Server $Domain
            foreach ($Point in $ServiceConnectionPoint) {  
                $Temporary = $Point.DistinguishedName.split(",")            
                $DistinguishedName = $Temporary[1..$Temporary.Count] -join ","    
                $Point | Add-Member -MemberType 'NoteProperty' -Name 'DN' -Value $DistinguishedName -Force
            }
        } catch {
            Write-Error "Find-ServerTypes - Get-ADObject command failed. Terminating. Error $_"
            return
        }

        $ADConnect = Find-ADConnectServer
        $Computers = Get-ADComputer -Filter * -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName -Server $Domain
        $Servers = foreach ($Computer in $Computers) {
            $Services = foreach ($Service in $Computer.servicePrincipalName) {
                ($Service -split '/')[0]
            }
            [PSCustomObject] @{
                Name                   = $Computer.Name
                FQDN                   = $Computer.DNSHostName
                OperatingSystem        = $Computer.OperatingSystem
                DistinguishedName      = $Computer.DistinguishedName
                Enabled                = $Computer.Enabled
                IsExchange             = if ($Services -like '*ExchangeMDB*' -or $Services -like '*ExchangeRFR*') { $true } else { $false }
                IsSql                  = if ($Services -like '*MSSql*') { $true } else { $false }
                IsVM                   = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'Windows Virtual Machine') { $true } else { $false } 
                IsHyperV               = if ($Services -like '*Hyper-V Replica*') { $true } else { $false }
                IsSPHyperV             = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'Microsoft Hyper-V') { $true } else { $false } 
                IsRDSLicense           = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'TermServLicensing') { $true } else { $false } 
                #IsDC                   = if ($Services -like '*ldap*' -and $Services -like '*DNS*') { $true } else { $false }
                IsDC                   = if ($DomainInformation.ReplicaDirectoryServers -contains $Computer.DNSHostName) { $true } else { $false }  
                IsADConnect            = if ($ADConnect.FQDN -eq $Computer.DNSHostName) { $true } else { $false }
                Forest                 = $Forest.Name
                Domain                 = $Domain
                ServicePrincipalName   = ($Services | Sort-Object -Unique) -Join ','
                ServiceConnectionPoint = ($ServiceConnectionPoint | Where-Object { $_.DN -eq $Computer.DistinguishedName }).Name -join ','
            }
        }
        if ($Type -eq 'All') {
            $Servers
        } else {
            if ($Type -contains 'SQL') {
                $Servers | Where-Object { $_.IsSql -eq $true }
            }
            if ($Type -contains 'Exchange' ) {
                $Servers | Where-Object { $_.IsExchange -eq $true } 
            }
            if ($Type -contains 'Hyper-V') {
                $Servers | Where-Object { $_.IsHyperV -eq $true -or $_.IsSPHyperV -eq $true } 
            }
            if ($Type -contains 'VirtualMachine') {
                $Servers | Where-Object { $_.IsVM -eq $true } 
            }
            if ($Type -contains 'RDSLicense') {
                $Servers | Where-Object { $_.IsRDSLicense -eq $true } 
            }
            if ($Type -contains 'DomainController') {
                $Servers | Where-Object { $_.IsDC -eq $true } 
            }
            if ($Type -contains 'DomainController') {
                $Servers | Where-Object { $_.IsDC -eq $true } 
            }
            if ($Type -contains 'ADConnect') {
                $Servers | Where-Object { $_.IsADConnect -eq $true } 
            }
        }
    }
}

This little function can deliver multiple pieces of information. It requires an earlier function to work, so I'm showing it above for easy copy-paste scenario. As always those functions are part of PSSharedGoods module that I publish and update quite frequently.  You can review its sources on GitHub. If you have that already Update-Module PSSharedGoods and you're good to go.

Find-ServerTypes | Format-Table *

Find-ServerTypes -Type ADConnect, Exchange, SQL | Format-Table *

As you can see above, there are two ways to use it. Just ask for everything, and it will scan your whole forest and every domain it has, returning lots of information. Or you can specifically ask it only about Exchange, ADConnect or SQL. Actually you can ask for any of following types: ‘All', ‘ADConnect', ‘DomainController', ‘Exchange', ‘Hyper-V', ‘RDSLicense', ‘SQL', ‘VirtualMachine'. It works in OR scenario. That means if you ask for ADConnect and DomainController it will get you both types and not one type that matches both. You may have noticed that output has IsHyperV and IsSPHyperV based on Service Principal Name and Service Connection Point. I've added both because in my testing I've found out one reporting less than the other. However, the one that reported more had most of those additional machines unreachable.

BONUS - Get all domain controllers in AD Forest

As a bonus addition, I've decided to add my function that helps me find out information about all Domain Controllers in a forest. In a small environment it's quite easy to get that information but in larger ones where there are multiple domains within a forest you have to execute various commands to get those. I've simplified it for myself, and I hope it can be useful to you. It's not finished because I wanted it to verify, output more information but it's shareable at its current state.

function Get-WinADForestControllers {
    [alias('Get-WinADDomainControllers')]
    <#
    .SYNOPSIS


    .DESCRIPTION
    Long description

    .PARAMETER TestAvailability
    Parameter description

    .EXAMPLE
    Get-WinADForestControllers -TestAvailability | Format-Table

    .EXAMPLE
    Get-WinADDomainControllers

    .EXAMPLE
    Get-WinADDomainControllers | Format-Table *

    Output:

    Domain        HostName          Forest        IPV4Address     IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment
    ------        --------          ------        -----------     --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- -------
    ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189            True      False         True                     True        True      True                 True
    ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192            True      False        False                    False       False     False                False
    ad.evotec.pl                    ad.evotec.xyz                                                   False                    False       False     False                False Unable to contact the server. This may be becau...

    .NOTES
    General notes
    #>
    [CmdletBinding()]
    param(
        [switch] $TestAvailability,
        [switch] $SkipEmpty
    )
    $Forest = Get-AdForest
    $Servers = foreach ($D in $Forest.Domains) {
        try {
            $DC = Get-ADDomainController -Server $D -Filter *
            foreach ($S in $DC) {
                [PSCustomObject]@{
                    Domain                   = $D
                    HostName                 = $S.HostName
                    Forest                   = $Forest.RootDomain
                    IPV4Address              = $S.IPV4Address
                    IsGlobalCatalog          = $S.IsGlobalCatalog
                    IsReadOnly               = $S.IsReadOnly
                    SchemaMaster             = ($S.OperationMasterRoles -contains 'SchemaMaster')
                    DomainNamingMasterMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                    PDCEmulator              = ($S.OperationMasterRoles -contains 'PDCEmulator')
                    RIDMaster                = ($S.OperationMasterRoles -contains 'RIDMaster')
                    InfrastructureMaster     = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                    Comment                  = ''
                }
            }
        } catch {
            [PSCustomObject]@{
                Domain                   = $D
                HostName                 = ''
                Forest                   = $Forest.RootDomain
                IPV4Address              = ''
                IsGlobalCatalog          = ''
                IsReadOnly               = ''
                SchemaMaster             = $false
                DomainNamingMasterMaster = $false
                PDCEmulator              = $false
                RIDMaster                = $false
                InfrastructureMaster     = $false
                Comment                  = $_.Exception.Message
            }
        }
    }
    if ($TestAvailability) {
        foreach ($Server in $Servers) {
            if ($Server.IPV4Address -ne '') {
                $Output = Test-Connection -Count 1 -Server $Server.IPV4Address -Quiet -ErrorAction SilentlyContinue
                Add-Member -InputObject $Server -MemberType NoteProperty -Name 'Pingable' -Value $Output
            } else {
                Add-Member -InputObject $Server -MemberType NoteProperty -Name 'Pingable' -Value $false
            }
        }
    }
    if ($SkipEmpty) {
        return $Servers | Where-Object { $_.HostName -ne '' }
    }
    return $Servers
}
Get-WinADForestControllers | Format-Table -Autosize *
# Alternatively 
Get-WinADDomainControllers

I've also added an option to verify connectivity to DC's quickly. It's a simple ping but sometimes helpful.

Get-WinADForestControllers -TestAvailability | Format-Table -AutoSize *

In my final words, I wanted to add that functions I've created are based on Active Directory and require RSAT to work. They are asking different parts of AD (Service Connection Point, Service Principal Name and so on) to guess server type. It's possible some of that information is out of date, or that guess is incorrect. If you find this useful, please let me know and spread this knowledge. If I made a mistake also let me know so I can fix it and everyone can benefit.

This post was last modified on %s = human-readable time difference 19:26

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

1 miesiąc ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

2 miesiące ago

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

7 miesięcy ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

11 miesięcy ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

1 rok ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

1 rok ago