Monitoring Changes to Active Directory Sites and Subnets with PowerShell

If you work with SCCM and you use AD Forest Discovery to automatically create boundaries from AD Sites or Subnets, you know how important it is for AD to stay up to date with the current information. And when something is changed in Sites or Subnets, you need to be made aware of it so you can reflect the change in your SCCM boundaries and boundary groups. Unfortunately, communication between IT teams is not always what it should be, so I wrote this script to run as a scheduled task and keep an eye on any changes made in AD Sites and IP subnets.

The script works by retrieving the current site and subnet information and exporting it to cache files. The next time the script runs, it will compare the current information with the information in the cached files, and if anything has changed, a report will be sent by email detailing the changes.

It’s one way of ensuring you’re keeping SCCM in sync with your AD!

######################################################################################
## ##
## This script compares the current list of AD sites and subnets with a cached list ##
## If anything has changed, the cached list will be updated and the changes emailed ##
## ##
######################################################################################
################
## PARAMETERS ##
################
# Location of cache files
$ADSitesFile = "G:\Scheduled Task Scripts\Cache Files\ADSites.csv"
$ADSubnetsFile = "G:\Scheduled Task Scripts\Cache Files\ADSubnets.csv"
# Email parameters
$EmailParams = @{
smtpserver = "contoso-com.mail.protection.outlook.com"
To = "SCCMAdmins@contoso.com"
From = "SCCMReports@contoso.com"
Subject = "Active Directory Site and Subnet Changes"
}
# Html CSS style
$Style = @"
<style>
table {
border-collapse: collapse;
}
td, th {
border: 1px solid #ddd;
padding: 8px;
}
th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4286f4;
color: white;
}
</style>
"@
#################
## MAIN SCRIPT ##
#################
# ArrayLists to hold the data
$ADSites = [System.Collections.ArrayList]::new()
$ADSubnets = [System.Collections.ArrayList]::new()
# Retrieve the list of AD sites for the current forest
$Sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
# Retrieve the AD subnets for each AD site and convert to a custom object
foreach ($ADSite in $Sites)
{
[void]$ADSites.Add(
[PSCustomObject]@{
'AD Site' = $ADSite.Name
}
)
Foreach ($Subnet in $ADSite.Subnets)
{
[void]$ADSubnets.Add(
[pscustomobject]@{
Name = $Subnet.Name
Site = $Subnet.Site
Location = $Subnet.Location
}
)
}
}
# Test whether the cached lists exist, if not create them assuming first run
If (!(Test-Path $ADSitesFile))
{
$ADSites | Sort 'AD Site' | Export-Csv -Path $ADSitesFile -NoTypeInformation -Force
}
If (!(Test-Path $ADSubnetsFile))
{
$ADSubnets | Sort Name | Export-Csv -Path $ADSubnetsFile -NoTypeInformation -Force
}
# Load in the cached lists
$ADSitesCached = Import-Csv -Path $ADSitesFile
$ADSubnetsCached = Import-Csv -Path $ADSubnetsFile
# More ArrayLists to hold the data
$ADSitesAdded = [System.Collections.ArrayList]::new()
$ADSitesRemoved = [System.Collections.ArrayList]::new()
$ADSubnetsAdded = [System.Collections.ArrayList]::new()
$ADSubnetsRemoved = [System.Collections.ArrayList]::new()
$ADSubnetsModified = [System.Collections.ArrayList]::new()
# New AD sites
Foreach ($Item in $ADSites)
{
If($Item.'AD Site' -notin $ADSitesCached.'AD Site')
{
[void]$ADSitesAdded.Add($Item)
}
}
# Removed AD Sites
Foreach ($Item in $ADSitesCached)
{
If($Item.'AD Site' -notin $ADSites.'AD Site')
{
[void]$ADSitesRemoved.Add($Item)
}
}
# IP subnet where AD Site has changed, or new IP subnet added
Foreach ($Item in $ADSubnets)
{
$Sub = $ADSubnetsCached.Where({$_.Name -eq $Item.Name})
If ($Sub)
{
If ($Sub.Site -ne $Item.Site)
{
[void]$ADSubnetsModified.Add(
[PSCustomObject]@{
Name = $Item.Name
OldSite = $Sub.Site
NewSite = $Item.Site
}
)
}
}
Else
{
[void]$ADSubnetsAdded.Add($Item)
}
}
# IP subnet removed
Foreach ($Item in $ADSubnetsCached)
{
$Sub = $ADSubnets.Where({$_.Name -eq $Item.Name})
If ($Sub){}
Else
{
[void]$ADSubnetsRemoved.Add($Item)
}
}
# Prepare the HTML
If ($ADSitesAdded.Count -ge 1)
{
$HTML1 = $ADSitesAdded |
ConvertTo-Html -Head $Style -Property 'AD Site' -Body "<h2>The following AD Sites have been ADDED in Active Directory</h2>" -CssUri "http://www.w3schools.com/lib/w3.css&quot; |
Out-String
}
If ($ADSitesRemoved.Count -ge 1)
{
$HTML2 = $ADSitesRemoved |
ConvertTo-Html -Head $Style -Property 'AD Site' -Body "<h2>The following AD Sites have been REMOVED from Active Directory</h2>" -CssUri "http://www.w3schools.com/lib/w3.css&quot; |
Out-String
}
If ($ADSubnetsAdded.Count -ge 1)
{
$HTML3 = $ADSubnetsAdded |
ConvertTo-Html -Head $Style -Property Name,Site,Location -Body "<h2>The following IP Subnets have been ADDED in Active Directory</h2>" -CssUri "http://www.w3schools.com/lib/w3.css&quot; |
Out-String
}
If ($ADSubnetsRemoved.Count -ge 1)
{
$HTML4 = $ADSubnetsRemoved |
ConvertTo-Html -Head $Style -Property Name,Site,Location -Body "<h2>The following IP Subnets have been REMOVED from Active Directory</h2>" -CssUri "http://www.w3schools.com/lib/w3.css&quot; |
Out-String
}
If ($ADSubnetsModified.Count -ge 1)
{
$HTML5 = $ADSubnetsModified |
ConvertTo-Html -Head $Style -Property Name,OldSite,NewSite -Body "<h2>The AD Site for the following IP Subnets has been MODIFIED in Active Directory</h2>" -CssUri "http://www.w3schools.com/lib/w3.css&quot; |
Out-String
}
$HTML = $HTML1 + $HTML2 + $HTML3 + $HTML4 + $HTML5
# Send the email report and update the cached lists if required
If ($HTML.Length -ge 1)
{
Try
{
Send-MailMessage -Body $HTML @EmailParams -BodyAsHtml -Priority High -ErrorAction Stop
}
Catch
{
$_
Break
}
$ADSites | Sort 'AD Site' | Export-Csv -Path $ADSitesFile -NoTypeInformation -Force
$ADSubnets | Sort Name | Export-Csv -Path $ADSubnetsFile -NoTypeInformation -Force
}

One thought on “Monitoring Changes to Active Directory Sites and Subnets with PowerShell

  1. Do you know of any automated methods (ideally using Powershell) to run a forest discovery sync immediately? It would seem using a temp schedule is not feasible because the minimum poll is 1 day. The closest I got was a suggestion to restart the SMS Components service. However this doesn’t appear to kick-start a forest discovery. Any ideas or suggestions welcomed. Thanks.

Leave a reply to beautyac Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.