Script: Displaying the Structure of a Forest

Do you know the structure of your Active Directory forest? You could use a tool such as ADSI Edit and expand all of the OUs and containers in each domain, but if you have a lot of OUs, this would be very time consuming.

With a pretty simple script, you can enumerate all the domains, OUs, and containers in a forest. And you don’t need any type of privileged rights to do it. Here is the script:

' This code prints out the forest tree hierarchy
' BEGIN SECTION 1
 set objRootDSE = GetObject("LDAP://RootDSE")
 strBase    =  "
               objRootDSE.Get("ConfigurationNamingContext") & ">;"
 strFilter  = "(&(objectcategory=crossRef)(systemFlags=3));"
 strAttrs   = "name,trustParent,nCName,dnsRoot,distinguishedName;"
 strScope   = "onelevel"
 set objConn = CreateObject("ADODB.Connection")
 objConn.Provider = "ADsDSOObject"
 objConn.Open "Active Directory Provider"
 set objRS = objConn.Execute(strBase & strFilter & strAttrs & strScope)
 objRS.MoveFirst
 ' END SECTION 1
' BEGIN SECTION 2
 set dicSubDomainTrue = CreateObject("Scripting.Dictionary")
 set dicDomainHierarchy = CreateObject("Scripting.Dictionary")
 set dicDomainRoot = CreateObject("Scripting.Dictionary")
 ' END SECTION 2
' BEGIN SECTION 3
 while not objRS.EOF
    dicDomainRoot.Add objRS.Fields("name").Value, objRS.Fields("nCName").Value
    if objRS.Fields("trustParent").Value <> "" then
       dicSubDomainTrue.Add objRS.Fields("name").Value, 0
       set objDomainParent = GetObject("LDAP://" & _
                             objRS.Fields("trustParent").Value)
       dicDomainHierarchy.Add objRS.Fields("name").Value, _
                              objDomainParent.Get("name")
    else
       dicSubDomainTrue.Add objRS.Fields("name").Value, 1
    end if
    objRS.MoveNext
 wend
 ' END SECTION 3
' BEGIN SECTION 4
 for each strDomain in dicSubDomainTrue
   if dicSubDomainTrue(strDomain) = 1 then
       DisplayDomains strDomain, "", dicDomainHierarchy, dicDomainRoot
   end if
 next
Function DisplayDomains ( strDomain, strSpaces, dicDomainHierachy, dicDomainRoot)
    WScript.Echo strSpaces & strDomain
    DisplayObjects "LDAP://" & dicDomainRoot(strDomain), "  " & strSpaces
    for each strD in dicDomainHierarchy
       if dicDomainHierarchy(strD) = strDomain then
          DisplayDomains strD, "  " & strSpaces, dicDomainHierarchy, _
                         dicDomainRoot
       end if
    next
 End Function
' DisplayObjects takes the ADsPath of the object to display
 ' child objects for and the number of spaces (indention) to
 ' use when printing the first parameter
 Function DisplayObjects( strADsPath, strSpace)
    set objObject = GetObject(strADsPath)
    Wscript.Echo strSpace & objObject.Name
    objObject.Filter = Array("container","organizationalUnit")
    for each objChildObject in objObject
       DisplayObjects objChildObject.ADsPath, strSpace & " "
    next
 End Function
' END SECTION 4

Let’s walk through it.

In SECTION 1, I set up an ADO query to find all domains in a forest. This is a little trickier than you might imagine. Domains are represented in the Configuration naming context as crossRef objects; but since LDAP referrals can also be created as crossRef objects, I have to look for a specific type of crossRef object, ones that have a systemFlags attribute equal to 3. This signifies Active Directory domains.

In SECTION 2, I set up three dictionary objects that I’ll use throughout the script. Here is a brief overview of each dictionary object:

dicSubDomainTrue

This is used to identify whether domains contain subdomains. The keys will be domain names and values will be 0 if the domain has no subdomains or 1 if it does. This dictionary will be used later to enumerate the domain hierarchy.

dicDomainHierarchy

This is used to store the parent domain of a subdomain. The keys will be the domain names and the values will be each domain’s parent domain name.

dicDomainRoot

This is used to store the default naming context for each domain. The keys will be the domain names and the values will be the DN of each domain’s root.

In SECTION 3, I enumerate over each of the values returned by the ADO query started in SECTION 1. I first set the domain root in the dicDomainRoot dictionary. Next, I evaluate if the trustParent attribute contains a value. If it does, then I know the domain I’m currently on has a parent domain. If TRustParent does not contain a value, I know that it is the root domain of the forest or domain tree. If it does have a parent, I set an entry for the domain in dicSubDomainTrue to 0 to signify that I haven’t found a subdomain for this domain yet. I then set an entry in dicDomainHierarchy to contain the domain and parent domain as key value pairs.

At this point, I’ve set up all the data structures I need to start printing out the structure of a forest. In SECTION 4, I start iterating over each domain in the forest. I’ll enter only the domains that are roots of their forests. Then I call the DisplayDomains function. DisplayDomains prints the name of the current domain and calls DisplayObjects to print each container and organizationalUnit object in the domain. This effectively prints the structure of that domain. After it is done with that, it starts to loop over the keys in dicDomainHierarchy to find all child domains that have the current domain set as their parent. If it finds a subdomain of the current domain, it calls DisplayDomains (recursively) on that domain and the process repeats.