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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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