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