Rip off the bandaid with PowerShell (Using bitwise LDAP filters to search and destroy manual server connection objects)
AD Replication Bandaids
The worst part of a bandaid is ripping it off. The cure is worse than the problem. The only thing worse than a bandaid is surgical tape, especially when… never mind.
Do you have any bandaids in your AD replication? You know… those manual server connection objects. That's where admins try to outsmart the KCC and build their own connections instead. Sometimes these can serve a legitimate purpose like when bridge-all-site-links is disabled, but that is less common. The problem is that when DCs get refreshed we usually forget about these manual connections and they pile up like Beanie Babies on an episode of Hoarders. Today's post is a quick one-liner to rip off those bandaids… but in a painless PowerShell kind of way.
The Long Way
Follow me on a GUI journey first. Fire up AD Sites and Services. Drill down through a site name and through the servers under it until you get to NTDS Settings. Click on NTDS Settings. Now look in the right window. You'll see all of the server connection objects there, and they should all be named "<automatically generated>". If any other name is there, then someone besides the KCC must have put it there. (And I'm not pointing any fingers.) The only exception is for RODCs; then you'll see one labeled "RODC Connection (FRS)". We never want to delete the RODC connections. Now click through several other servers and connections to see if they all say "<automatically generated>".
The PowerShell Way
All of this clicking gets old quickly, and that is why we love PowerShell. Here is a one-liner to list all of the manually-created server connections:
Get-ADObject -LDAPFilter "(&(objectClass=nTDSConnection)(!options:1.2.840.1135184.108.40.2064:=1))" -Searchbase (Get-ADRootDSE).ConfigurationNamingContext -Property DistinguishedName, FromServer | Format-Table DistinguishedName, FromServer
Use DSQUERY in environments where there are no 2008 R2 DCs:
dsquery * "CN=Sites,CN=Configuration,DC=contoso,DC=com" -scope subtree -filter "(&(objectClass=nTDSConnection)(!options:1.2.840.1135220.127.116.114:=1))" -limit 0
Before we get to the fun deleting one-liner let's study this one to understand it better. Look at the ugly part I highlighted. What in the world is that? I'm glad you asked.
LDAP Bitwise Filters
That is a specially-formatted LDAP query to do a logical OR on the first bit of the options attribute. As with other options attributes on other AD object classes, this one is a series of bit switches. The first bit flags whether the connection object was automatically created. This is documented in the Active Directory Technical Specification.
IG (NTDSCONN_OPT_IS_GENERATED, 0x00000001): The nTDSConnection object was generated by the KCC.
<attribute name>:<matching rule OID>:=<value>
LDAP filters are common for many AD search tools: DSQUERY, LDIFDE, LDP, etc. It really pays to learn your way around with them. Let's break this down:
- ! – This is a NOT logical operator to reverse the boolean result. In this case we are looking for any connection objects where the bit is NOT flipped.
- options – This is the attribute name.
- 1.2.840.113518.104.22.1684 – This is the squirrelly part, and it represents a bitwise OR operation. A bitwise AND operation would end with "803" instead. This tells the filter to look for a bit match for the value following the ":=".
- := – Separates the rule from the value.
- 1 – This is the bit we are checking for a match.
The most common place you'll see bitwise operations is with the userAccountControl attribute on user and computer accounts (documented in KB305144 and MSDN). This single attribute is a long list of bit switches representing most of the checkboxes you find on a user account in AD Users and Computers. For example, the second bit tells you when an account is disabled.
Rip Off The Bandaids
Unless someone in the history of your Active Directory implementation put these bandaids in place with super glue when they disabled IP BASL (bridge all site links), then you should be clear to remove them in accordance with best practices.
Now that we have a list of all manually-created connection objects we simply pipe it into Remove-ADObject like this:
Get-ADObject -LDAPFilter "(&(objectClass=nTDSConnection)(!options:1.2.840.113522.214.171.1244:=1))" -Searchbase (Get-ADRootDSE).ConfigurationNamingContext | Remove-ADObject -Verbose
We add the -Verbose switch so that all of the deletions are echoed to the screen. If you don't want the confirmation prompts for all of the deletions you can add the -Confirm:$false switch like this:
Get-ADObject -LDAPFilter "(&(objectClass=nTDSConnection)(!options:1.2.840.1135126.96.36.1994:=1))" -Searchbase (Get-ADRootDSE).ConfigurationNamingContext | Remove-ADObject -Verbose -Confirm:$false
But wait! How will we replicate without these connection objects? Relax. The KCC will automatically build new ones where necessary in less than 15 minutes. Give replication a few cycles and everyone in the forest will be happily replicating on their shiny new connection objects.
Go ahead. Rip off the bandaid. With PowerShell it won't hurt a bit.
PostScript: LDAPFilter vs. Filter
After making this post I received some feedback regarding the equally valid use of the Filter parameter. You can do the exact same LDAPFilter with the PowerShell-style Filter parameter. Here is what it would look like:
Get-ADObject -Filter 'objectClass -eq "nTDSConnection" -and -not options -bor 1' -Searchbase (Get-ADRootDSE).ConfigurationNamingContext -Property DistinguishedName, FromServer | Format-Table DistinguishedName, FromServer
Either syntax will give you the same results, but this method avoids the ugly bitwise LDAP syntax. Once you have imported the ActiveDirectory module you can type Get-Help about_ActiveDirectory_filter to see many good comparisons and examples of the two filter types. Personally I prefer the LDAPFilter, because that standard syntax is used in so many other tools.