Consolidate All AD Empty Sites Into A Single SiteLink Using PowerShell

Do you have a large Active Directory site topology with many empty sites?  If so, this post is for you.

Empty sites are AD sites without a domain controller.  These sites are used to support AD site-aware applications like SCCM and DFS where clients get local site referrals.

Recently I encountered a customer with over 400 empty sites in Active Directory.  (This large customer environment also had a couple dozen regular sites populated with DCs, each in a single sitelink to the hub following best practices.)  Like most customers they started creating empty sites with a site link to the hub site.  Then another.  Then another.   Then another.  And finally it was too burdensome to create a dedicated sitelink from the hub to the empty site each time, so they started rolling new empty sites into one of two sitelinks.  By the time I arrived there were about 150 empty sites in SiteLinkA, 150 empty sites in SiteLinkB, and another 100 empty sites each with their own site link directly to the hub.

In short this was a mess.  Now everything worked just fine this way.  But it was disorganized, and they wanted to clean it up.  To manually consolidate all of these empty sites into a single link would take days.  (Queue the PowerShell action hero music.)

Side bar:  Since AD empty sites do not affect directory replication in any way it is perfectly fine to lump them all into a single sitelink.  There is no technical need for them to have individual site links to the hub site.  Therefore the least administrative burden is to wrangle them all into a single sitelink.  If they get populated with a DC in the future you can simply create a new sitelink and move the site out of the empty sites sitelink and into a new sitelink to the hub.  If you can think of a reason not to do this I'd love to hear it.

That brings us to this month's script.  I wrote some PowerShell code to do the following:

  1. Build a list of all AD sites that do not contain DCs (ie. "empty sites")
  2. Remove these sites from any other existing site links
  3. Delete site links with only two members including the empty site
  4. Create a new site link containing all empty sites
  5. Link the new "EmptySitesLink" to the hub site specified by the user

Since this is a quick fix type script to be run "hands-on" from the console I took the liberty of using Read-Host to get the hub site name from the user.  You can modify the script to specify it as a variable assignment if you like instead.  I suggest piping the output to a text file to create a log of changes made by the script.  (ex.  .\New-EmptySitesLink.ps1 > log.txt)

Note: In order to run this script you will need Enterprise Admin rights to modify sitelinks. You could run this from any domain in the forest, but the forest root is the preferred place to run it.

At the end of the day the customer was happy that they saved days of clicking in AD Sites and Services and their sitelinks were now as tidy as Bill Gates' sock drawer.


# Create AD Site Link With All Empty Sites            
#   (and remove empty sites from all other site links)            
# Ashley McGlone, Microsoft PFE            
# 1/26/2011            
# Empty sites are AD sites that do not contain domain controllers.            
# This script is handy for scenarios in large environments that use            
# empty sites for DFS, SCCM, etc.  Managing all these empty sites can be            
# an administrative burden when they are all in separate site links.            
# This script cleans up and consolidates all empty sites into a single            
# site link.            
# This script does the following:            
# -Builds a list of all AD sites that do not contain DCs (ie. "empty")            
# -Removes these sites from any other existing site links            
# -Deletes site links with only two members including the empty site            
# -Creates a new site link containing all empty sites            
# -Links the new "EmptySitesLink" to the hub site specified by the user            
Import-Module ActiveDirectory            
Function SiteIsEmpty($siteDN) {            
    # Empty sites will not have any nTDSDSA child objects under them            
    # representing DCs and replication connections.            
    $ChildDCs = Get-ADObject -Filter {ObjectClass -eq "nTDSDSA"} `
        -SearchBase $siteDN            
    If ($ChildDCs) {            
        Return $false            
    } Else {            
        Return $true            
$ConfigPath = (Get-ADRootDSE).configurationNamingContext            
$SiteLinkPath = "CN=IP,CN=Inter-Site Transports,CN=Sites," + $ConfigPath            
$EmptySiteArray = @()            
$Sites = Get-ADObject -Filter {ObjectClass -eq "site"} -SearchBase $ConfigPath            
$Sites | ForEach-Object {            
    If (SiteIsEmpty($_.DistinguishedName)) {            
        $EmptySiteArray += $_.DistinguishedName            
# For each empty site            
# Find each site link where it is a member            
# If there are only two sites in the link            
#  Delete the link            
# Else            
#  Delete the site from the link            
foreach ($EmptySite in @($EmptySiteArray)) {            
    # Although costly we have to re-query the site list for each iteration            
    # because we are modifying sitelinks as we go.  This keeps the list            
    # fresh for each iteration.            
    $SiteLinks = Get-ADObject -Filter 'ObjectClass -eq "siteLink"' `
        -SearchBase $SiteLinkPath -Properties siteList            
    $SiteLinks | ForEach-Object {             
        If ($_.siteList.Contains($EmptySite)) {            
            If (@($_.siteList).Length -le 2) {            
                Remove-ADObject $_.DistinguishedName -Confirm:$false            
                "Deleted sitelink $_.Name"            
            } Else {            
                Set-ADObject $_.DistinguishedName -Remove @{siteList=$EmptySite}            
                "Removed the empty site $EmptySite from the link $_.Name"            
# Create the new site link with all empty sites            
New-ADObject -name "EmptySitesLink" -type siteLink -path $SiteLinkPath `
    -OtherAttributes @{siteList=$EmptySiteArray;replInterval=15;cost=100;`
    description="Site link containing all empty sites and linked to the hub"}            
"Created EmptySitesLink containing:"            
# Now link the hub to this new site link            
$SiteHubName = Read-Host "What is the name of the hub site?"            
$SiteHub = Get-ADObject `
    -Filter {ObjectClass -eq "site" -and CN -eq $SiteHubName} `
    -SearchBase $ConfigPath            
$NewSiteLink = Get-ADObject `
    -Filter {ObjectClass -eq "siteLink" -and CN -eq "EmptySitesLink"} `
    -SearchBase $SiteLinkPath            
Set-ADObject `
    -Identity $NewSiteLink.DistinguishedName `
    -Add @{siteList=$SiteHub.DistinguishedName}            
"Added $SiteHubName to EmptySitesLink."            
#   ><>