04.28
Domain Controllers replicate Active Directory data with each other. They do so through Connections that are partly generated by the KCC (Knowledge Consistency Checker), partly configured by you: the Sysadmin . Each connection is one-way. If you open Active Directory Sites and Services, expand a Site and then a Server node, you’ll notice that Connections listed under NTDS Settings are labeled “From Server” and “From Site”. In the image below (stolen from here), the DC named HEIDITEST will replicate AD changes by sending them to MHILLMAN2. The Connection Object is thus defined from HEIDITEST, to MHILLMAN2. You can expect a specular Connection to exist, defined under the NTDS Settings node of HEIDITEST.
See Active Directory Replication for a more in-depth explanation.
Besides Connection objects automatically created by the KCC, which does its best to build a proper replication topology, you sometimes add your own for fault/link tolerance or other reasons. If the domain is sufficiently big, things may become messy. Instead of fumbling my way through Active Directory Sites and Services I wanted to automatically generate a visual representation of such topology, with DCs and Connections: time to write yet another script.
This time I chose VBS over Perl, hoping that this post would be more “instructional”. Perl on Windows is not so common, while VBScript is the standard way to automate stuff on that O.S. (despite the language being incredibly clumsy and annoying1).
As for the graph format, I chose to output Graphviz DOT format/language.
The script works this way:
- Find the current domain.
- Find all the Domain Controllers (AD objects of class nTDSDSA, see this) and the Site they’re in.
- For each DC/Site, select nTDSConnection objects in NTDS Settings. Of course this is done by means of LDAP queries over ADO, but the view we get is equivalent to what we’re seeing in Active Directory Sites and Services.
- Print the DOT graph on standard output: DCs, connections and sites. DCs in the same site will be clustered together.
To use it, first generate the graph’s definition:
Then use Graphviz’s tools to lay out the graph and turn it into an actual image. For optimal results, I suggest something like:
Here’s what showed up, in my test case:
And here’s the same Domain, after some treatment:
Such graphs may be useful from a Sysadmin point of view, but they’re quite ugly, honestly. I originally thought to use Graphviz to output “some” format, read it in Dia or similar diagram drawing software, and then fix the aesthetics. But Dia support (if it ever worked) has been dropped from Grapviz (December 10, 2009). Dia’s 0.97.1 tarball bears a “dot2dia.py” plugin, but I haven’t hacked it into working. Any other editable format known to Graphviz (e.g.: SVG) doesn’t support “connector” primitives meaning that arrows won’t stick to objects while you drag them around… I’ll follow up if I make some progress.
' in the current Domain.
' ----------------------------
' Giuliano - http://www.108.bz
Set objRootDSE = GetObject("LDAP://RootDSE")
strConfigurationNC = objRootDSE.Get("configurationNamingContext")
Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Open "Active Directory Provider"
adoCommand.ActiveConnection = adoConnection
strBase = "<LDAP://" & strConfigurationNC & ">"
strFilter = "(objectClass=nTDSDSA)"
strAttributes = "AdsPath"
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 60
adoCommand.Properties("Cache Results") = False
Set adoRecordset = adoCommand.Execute
Dim dictDCtoSite
Set dictDCtoSite = CreateObject("Scripting.Dictionary")
Dim dictSites
Set dictSites = CreateObject("Scripting.Dictionary")
Dim arrLink()
Function pp(s)
pp = Replace(right(s,len(s)-3), "-", "_") ' trash the leading "CN="
End Function
Do Until adoRecordset.EOF
Set objDC = _
GetObject(GetObject(adoRecordset.Fields("AdsPath").Value).Parent)
Set objSite = _
GetObject(GetObject(objDC.Parent).Parent)
dictDCtoSite.Add objDC.name, objSite.name
if not dictSites.Exists(objSite.name) Then
dictSites.Add objSite.name, 1
End If
adoRecordset.MoveNext
Loop
adoRecordset.Close
For Each strDcRDN in dictDCtoSite.Keys
strSiteRDN = dictDCtoSite.Item(strDcRDN)
strNtdsSettingsPath = "LDAP://cn=NTDS Settings," & strDcRDN & _
",cn=Servers," & strSiteRDN & ",cn=Sites," & strConfigurationNC
Set objNtdsSettings = GetObject(strNtdsSettingsPath)
objNtdsSettings.Filter = Array("nTDSConnection")
For Each objConnection In objNtdsSettings
'WScript.Echo strSiteRDN & " : " & Split(objConnection.fromServer, ",")(1) & " -> " & strDcRDN
ReDim Preserve arrLink(2,k)
arrLink(0,k) = strSiteRDN
arrLink(1,k) = Split(objConnection.fromServer, ",")(1)
arrLink(2,k) = strDcRDN
k = k + 1
Next
Set strNtdsSettingsPath = Nothing
Next
Dim arrSubgraphs()
Redim arrSubgraphs(dictSites.Count-1)
WScript.Echo "Digraph AD {"
WScript.Echo " fontname=helvetica;"
WScript.Echo " node [fontname=helvetica];"
' Same site links
For Each strSiteRDN in dictSites
nosamesitelinks = True
headerwritten = False
For k = 0 To Ubound(arrLink, 2)
If strSiteRDN = arrLink(0,k) Then
if dictDCtoSite.Item(arrLink(1,k)) = dictDCtoSite.Item(arrLink(2,k)) Then
if nosamesitelinks Then
nosamesitelinks = False
WScript.Echo " subgraph cluster_" & pp(strSiteRDN) & " {"
headerwritten = True
End If
WScript.Echo " " & pp(arrLink(1,k)) & " -> " & pp(arrLink(2,k)) & ";"
End If
End If
Next
If headerwritten Then
WScript.Echo " label= """ & pp(strSiteRDN) & """"
WScript.Echo " }"
End If
Next
Wscript.Echo
' Inter-site links
For k = 0 To Ubound(arrLink, 2)
if dictDCtoSite.Item(arrLink(1,k)) <> dictDCtoSite.Item(arrLink(2,k)) Then
WScript.Echo " " & pp(arrLink(1,k)) & " -> " & pp(arrLink(2,k)) & ";"
End If
Next
WScript.Echo "}"
- No powerful and convenient data types, no free and ready to use debugger, no public CPAN-like module repository, unnecessarily verbose syntax; I may go on for an hour… ↩