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.





















