Active Directory Graphs

Be the first to like.

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:

cscript /nologo ntdsconnections_graph.vbs > AD-pre.dot

Then use Graphviz’s tools to lay out the graph and turn it into an actual image. For optimal results, I suggest something like:

ccomps -x AD-pre.dot | dot | gvpack -u | neato -Tpng -n2 > AD-pre.png

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.

' A/D replication topology graph (Graphviz .DOT format)
' 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 = _
    Set objSite = _
    dictDCtoSite.Add objDC.name, objSite.name
    if not dictSites.Exists(objSite.name) Then
        dictSites.Add objSite.name, 1
    End If

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

    Set strNtdsSettingsPath = Nothing

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
    If headerwritten Then
        WScript.Echo "        label= """ & pp(strSiteRDN) & """"
        WScript.Echo "    }"
    End If
' 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
WScript.Echo "}"
  1. 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…