Example for Identifying Domain Controller Subnet Membership with PowerShell

First of all let me thank Rich Doyle (a fellow PFE) for allowing me to post this script.  Rich writes some pretty kick-butt stuff and I know I speak for many of us that appreciate his help and his scripts!

Now Rich claims he hacked this together and didn't get around to polishing it up with optimization and full error trapping and such.  That is okay as my posts are all meant to be just conceptual demos and are not meant at all to be production level scripts.  That said, this “unpolished” script is longer and way more thorough then what I usually write.  You go Rich!

 

Here is Rich’s write-up on this script:

 

The customer I am at this week wanted an easy way for admins to find DC subnet membership if the subnet of the DC did not match what was defined for the local AD Site in which the DC was a member.  The customer wanted finding the subnet in AD to be actionable by someone without extensive IP subnet knowledge - especially as there are a lot of defined subnets to review in AD.

Attached is a script that performs this search, and the results.

I am sending it out, as it provides some really useful functions for IPv4 Address, Network and Subnet calculations (IPv6 equivalent is coming soon).  These are ported and slightly modified from some great c# examples on MSDN Blogs, referenced within the script.  It also demonstrates some usage of the System.DirectoryService.ActiveDirectory.Forest class (and before anyone says anything, yes, I need to cache domains, domain controllers, sites and subnets data rather than re-search…but I haven’t gotten to optimization of the script yet J)

The script requires Powershell v1.0 (or 2.0, but not tested on the RTM release yet), and uses the following syntax:

.\Process-SubnetDefinitions.ps1 -serverlist:"server1","server2","server3"

The script checks the IP Address and subnet of the DC against the local site for an explicit network match, then all other sites for explicit network match.  If there is no explicit network match it proceeds to check all subnets as "catch-alls", starting with 32-bit/4th octect subnets and working up to 8-bit/1st octet subnets until a match (if there is one) is found.

It will provide the following output:

PS C:\Documents and Settings\xxxx\My Documents\Conflicting Subnet Definitions> .\Process-SubnetDefinitions.ps1 -serverlist:"xxxx","xxxx"

*****************************
Process-SubnetDefinitions.ps1
*****************************

Retrieving IP Address details and Subnet defintions for listed servers

Processing DC Local Sites First for explicit match....

Working on Server: xxxx

Working on Server: xxxx

Processing All Other Sites for explicit match....

Working on Server: xxxx
Checking Site: xxxx
Match Found

Working on Server: xxxx
Checking Site: xxxx
Match Found

DCName : xxxx
IPAddress : 10.100.139.74
SubnetMask : 255.255.255.0
Site : xxxx
SiteSubnets : {10.100.8.0/24, 172.28.22.0/24, 10.104.8.8/32, 10.100.139.33/32...}
SiteWithMatchingSubnet : xxxx
MatchingSiteSubnet : 10.100.139.0/24
MatchFound : True
CriticalError : False
ErrorDetails :

DCName : xxxx
IPAddress : 10.104.225.1
SubnetMask : 255.255.255.0
Site : xxxx
SiteSubnets : {10.104.225.36/32, 10.104.144.69/32, 10.179.2.20/32, 10.100.144.69/32...}
SiteWithMatchingSubnet : xxxx
MatchingSiteSubnet : 10.104.225.0/24
MatchFound : True
CriticalError : False
ErrorDetails :

 

Here is the script demo itself:

 param([array]$serverlist=$null)# This script solved a specific problem for me - Identification of a Domain Controller's subnet membership in Active Directory (even if not in the same site,# or a "catch-all" subnet - where the IPV4 Network cannot be used, and IP Address Ranges must be used to determine).

It checks the local AD Site for network match, then all other AD Sites for network match, then all AD Sites for "catch-all" match

After some research, I found helpful articles on MSDN, and MSDN Blogs

This script demonstrates IPv4 Address calculations, enumerating Domains, DCs and Sites within Active Directory (and still needs to be optimized to not# re-query AD Data as it runs).

References

MSDN - IPAddress Class - https://msdn.microsoft.com/en-us/library/system.net.ipaddress(VS.80).aspx# MSDN - Forest Class - https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory.forest(VS.80).aspx# MSDN Blogs - Knom's Developer Corner - https://blogs.msdn.com/knom/archive/2008/12/31/ip-address-calculations-with-c-subnetmasks-networks.aspx

This script is provided "AS IS" with no warranties, and confers no rights.# Use of any portion or all of this script are subject to the terms specified at# https://www.microsoft.com/info/cpyright.htm.

Write-Host "";Write-Host "";Write-Host "Process-SubnetDefinitions.ps1";Write-Host "";Write-Host "";If($serverlist -eq $null) { Write-Host "You must specify the target dc using the serverlist switch. e.g., .\Process-SubnetDefinitions.ps1 -serverlist:'servername'"; break; }Write-Host "Retrieving IP Address details and Subnet defintions for listed servers";$erroractionpreference = "SilentlyContinue";$error.Clear();# For use with [System.Net.IpAddress]::Parse which cannot read the native powershell byte array, so we Parse and create a string (returns a string, e.g. "255.255.255.0") function FormatBinaryMask{ param([byte[]]$binaryMask) $string = [string]::Empty; for([int]$i = 0; $i -lt 4; $i++) { if($i -eq 0) { $string = $string + $binaryMask[$i].ToString(); } else { $string = $string + "." + $binaryMask[$i].ToString(); }; }; return $string;}# Specify the number of bits for the Host to return the subnet mask (returns an IPAddress object) function SubnetMaskByHostBitLength{ param([int]$hostPartLength) $netPartLength = 32 - $hostPartLength; if ($netPartLength -lt 2) { Write-Error "Too many hosts for IPv4"; break; }; $binaryMask = New-Object byte[] 4; for([int]$i = 0; $i -lt 4; $i++) { if(($i * 8 + 8) -le $netPartLength) { $binaryMask[$i] = [byte]255; } elseif(($i * 8) -gt $netPartLength) { $binaryMask[$i] = [byte]0; } else { $oneLength = $netPartLength - $i * 8 $binaryDigit = [String]::Empty.PadLeft($oneLength, '1').PadRight(8, '0') $binaryMask[$i] = [System.Convert]::ToByte($binaryDigit, 2) }; }; return [System.Net.IPAddress]::Parse((FormatBinaryMask $binaryMask));}# Specify the number of bits for the Network to return the subnet mask (returns an IPAddress object) function SubnetMaskByNetBitLength{ param([int]$netPartLength) $hostPartLength = 32 - $netPartLength; return SubnetMaskByHostBitLength($hostPartLength);}# Performs a boolean AND between the IP Address and the subnet mask to return the Network Address, e.g. 192.168.1.0 (returns an IP Address object) function GetNetworkAddress{ param([System.Net.IPAddress]$address, [System.Net.IPAddress]$subnetMask) [byte[]]$ipAdressBytes = $address.GetAddressBytes(); [byte[]]$subnetMaskBytes = $subnetMask.GetAddressBytes(); if ($ipAdressBytes.Length -ne $subnetMaskBytes.Length) { Write-Error "Lengths of IP address and subnet mask do not match"; break; }; $broadcastAddress = New-Object byte[] $ipAdressBytes.Length; for ([int]$i = 0; $i -lt $broadcastAddress.Length; $i++) { $broadcastAddress[$i] = [byte]($ipAdressBytes[$i] -band ($subnetMaskBytes[$i])); }; return [System.Net.IPAddress]::Parse((FormatBinaryMask $broadcastAddress));}# Performs a boolean AND between the the subnet mask and 255.255.255.255 to return a filter for determining the number of usable addresses and those # limits within a network, e.g. would return 0.0.0.255 for the subnet mask 255.255.255.0 (returns an IP Address object) function GetNetworkUsableAddressRangeFilter{ param([System.Net.IPAddress]$subnetMask) $topend = [System.Net.IPAddress]::Parse("255.255.255.255"); [byte[]]$subnetMaskBytes = $subnetMask.GetAddressBytes(); [byte[]]$topendBytes = $topend.GetAddressBytes(); $broadcastAddress = New-Object byte[] $subnetMaskBytes.Length; for ([int]$i = 0; $i -lt $broadcastAddress.Length; $i++) { $broadcastAddress[$i] = [byte]($topendBytes[$i] -bxor ($subnetMaskBytes[$i])); }; return [System.Net.IPAddress]::Parse((FormatBinaryMask $broadcastAddress));}# Compares the networks of 2 sets of IP Address and subnet masks to determine if they are on the same network function { param([string]$address1, [string]$subnetMask1, [string]$address2, [string]$subnetMask2) $network1 = GetNetworkAddress ([System.Net.IPAddress]::Parse($address1)) ([System.Net.IPAddress]::Parse($subnetMask1)); $network2 = GetNetworkAddress ([System.Net.IPAddress]::Parse($address2)) ([System.Net.IPAddress]::Parse($subnetMask2)); return $network1.Equals($network2);}$results = @();$forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest();foreach($server in $serverlist){ $error.Clear(); $result = New-Object PSObject | Select-Object "DCName","IPAddress","SubnetMask","Site","SiteSubnets", "SiteWithMatchingSubnet","MatchingSiteSubnet","MatchFound","CriticalError","ErrorDetails"; $result.DCName = $server; $wmi = Get-WmiObject -computer $server Win32_NetworkAdapterConfiguration -filter "IPEnabled='true'"; If ($error) { $result.CriticalError = $true; $result.ErrorDetails = $error[0]; $error.Clear(); } else { $result.CriticalError = $false; $result.ErrorDetails = $null; $result.IPAddress = $wmi.IPAddress[0]; $result.SubnetMask = $wmi.IPSubnet[0]; }; foreach($domain in $forest.Domains) { foreach($dc in $domain.DomainControllers) { if($dc.Name.ToLower().Contains($server.ToLower())) { $result.Site = $dc.SiteName; }; }; }; foreach($site in $forest.Sites) { if($site.Name -eq $result.Site) { $result.SiteSubnets = $site.Subnets; }; }; $result.SiteWithMatchingSubnet = $null; $result.MatchingSiteSubnet = $null; $result.MatchFound = $false; $results += $result;};# Search Local Site Subnets for explicit match

Write-Host "";Write-Host "Processing DC Local Sites First for explicit match....";foreach($result in $results){ If($result.CriticalError -eq $false) { Write-Host ""; Write-Host (" Working on Server: {0}" -f $result.DCName); foreach($subnet in $result.SiteSubnets) { if($result.MatchFound -eq $false) { $res = $false; $netaddress = $subnet.Name.Split("/"); $res = IsInSameSubnet $result.IPAddress $result.SubnetMask $netaddress[0] (SubnetMaskByNetBitLength $netaddress[1]); If($res) { Write-Host " Match Found"; $result.SiteWithMatchingSubnet = $result.Site; $result.MatchingSiteSubnet = $subnet.Name; $result.MatchFound = $true; }; }; }; };}$continue = $false;foreach($result in $results){ if($result.MatchFound -eq $false) { $continue = $true; };}if($continue -eq $false) { $results; break; };# Search All other Sites for explicit match

Write-Host "";Write-Host "Processing All Other Sites for explicit match....";foreach($result in $results){ if($result.CriticalError -eq $false) { Write-Host ""; Write-Host (" Working on Server: {0}" -f $result.DCName); foreach($site in $forest.Sites) { $found = $false; if ($site.Name -ne $result.Site) { Write-Host (" Checking Site: {0}" -f $site.Name); foreach($subnet in $site.Subnets) { $res = $false; $netaddress = $subnet.Name.Split("/"); $res = IsInSameSubnet $result.IPAddress $result.SubnetMask $netaddress[0] (SubnetMaskByNetBitLength $netaddress[1]); If($res) { Write-Host " Match Found"; $result.SiteWithMatchingSubnet = $site.Name; $result.MatchingSiteSubnet = $subnet.Name; $result.MatchFound = $true; }; }; }; if($result.MatchFound) { break; }; }; };};$continue = $false;foreach($result in $results){ if($result.MatchFound -eq $false) { $continue = $true; };}if($continue -eq $false) { $results; break; };# Search all subnets for "catch-all" match

Write-Host "";Write-Host "Processing All Other Sites for Catch-All subnet match....if required....";foreach($server in $results){ If($server.CriticalError -eq $false) { Write-Host ""; write-host (" Working on Server: {0}" -f $server.DCName); while($server.MatchFound -eq $false) { foreach($site in $forest.Sites) { if($server.MatchFound -eq $false) { Write-Host (" Checking Site: {0}" -f $site.Name); foreach($subnet in $site.Subnets) { if($server.MatchFound -eq $false) { $lastaddress = new-object byte[] 4; $netaddress = $subnet.Name.Split("/"); If($netaddress[0] -le 8) { $netmask = SubnetMaskByNetBitLength $netaddress[1]; $filter = GetNetworkUsableAddressRangeFilter $netmask; $i = 0; foreach($byte in $filter.GetAddressBytes()) { if ($byte -eq 0) { $lastaddress[$i] = [System.Net.IPAddress]::Parse($netaddress[0]).GetAddressBytes()[$i]; } else { $lastaddress[$i] = ([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[$i] + $byte; }; $i++; }; $serverip = ([System.Net.IPAddress]::Parse($server.IPAddress)).GetAddressBytes(); If(($serverip[0] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[0]) -and ($serverip[0] -le $lastaddress[0])) { If(($serverip[1] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[1]) -and ($serverip[1] -le $lastaddress[1])) { If(($serverip[2] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[2]) -and ($serverip[2] -le $lastaddress[2])) { If(($serverip[3] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[3]) -and ($serverip[3] -le $lastaddress[3])) { Write-Host " Match Found"; $server.MatchingSiteSubnet = ("{0}/{1}" -f ($netaddress[0], $netaddress[1])); $server.SiteWithMatchingSubnet = $subnet.Site; $server.MatchFound = $true; }; }; }; }; }; }; }; }; }; }; };};$results;$erroractionpreference = "Continue";

 

Again Thanks a ton Rich!!!!!  You really rock man!

 

-Gary

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at https://www.microsoft.com/info/cpyright.htm.