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?
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.
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.
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.
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.
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.