Update 2009.04.16: At the request of a commenter, I added a couple lines to the script that will dump the output to a text file in the root of the C: drive. I also corrected a couple errors in the script.

I was tasked to get a dump of all the users in our Schema Admins, Enterprise Admins and Domain Admins for our Forest. I started thinking about it and realized a couple things. Two of the three groups reside at the forest root while the Domain Admins group exists for every domain in the forest. This meant I would need to enumerate every domain and depending on the domain, enumerate either all three groups or just one.

My thinking was overly complex and I realized this halfway through writing a new script. Using the power of LDAP, I can use a logical “or” (|) statement. When run against a domain, it would always return “Domain Admins” since it will always exist in an AD domain. When it is run against the forest root domain, it would also return the “Enterprise Admins” group and “Schema Admins” group. Here is the LDAP query:

(&(objectCategory=group)(|((name=Enterprise Admins*)(name=Domain Admins*)(name=Schema Admins*))))

At this point, all I need to do is this:

  1. Enumerate all domains in the forest
  2. Loop through each domain
  3. Execute LDAP query against each domain
  4. Loop through LDAP query results
  5. Dump membership of each group

The script below does just that. I hope some find it useful. There is no configuration necessary. You should be able to just run it from your environment as no domain references (or really anything) is hard coded. The only thing you may want to add to or remove from is the LDAP filter. Cheers!

'==========================================================================
' VBScript Source File
' NAME: Active Directory Admin Audit
' AUTHOR: Andrew J Healey
' DATE  : 2009.04.16
' COMMENT: This script will check all the domains within a forest
'		and report all the members of the following groups: Schema
'		Admins, Enterprise Admins and Domain Admins. See notes to
'		expand on the groups.
'==========================================================================

'Define Constants
Const adUseClient = 3
Const ForWriting = 2
 
'Set the path and filename for the dump of sensitive users
'  Folder must exist!
fileTemp = "C:\AD Admin Audit.txt"
 
'Create tmp file and report file
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTempFile = objFSO.OpenTextFile(fileTemp, ForWriting, True)
 
'Query RootDSE and return array with all AD domains in forest
Set adoComm = CreateObject("ADODB.Command")
Set adoConn = CreateObject("ADODB.Connection")
adoConn.Provider = "ADsDSOObject"
adoConn.cursorLocation = adUseClient
adoConn.Open "Active Directory Provider"
adoComm.ActiveConnection = adoConn
 
Set objRootDSE = GetObject("LDAP://RootDSE")
strBase   =  "<GC://" & objRootDSE.Get("rootDomainNamingContext") & ">;"
strFilter = "(objectcategory=domainDNS);"
strAttrs  = "distinguishedName;"
strScope  = "subtree"
 
strQuery = strBase &amp; strFilter &amp; strAttrs &amp; strScope
adoComm.CommandText = strQuery
adoComm.Properties("Page Size") = 50
adoComm.Properties("Timeout") = 30
adoComm.Properties("Cache Results") = False
 
Set adoRS = adoComm.Execute
 
'Start Loop
Do Until adoRS.EOF
	'Parse ad search results to create well formed DNS domain
	strDomain = Replace(adoRS.Fields(0).Value,"DC=","")
	strDomain = Replace(strDomain,",",".")
	Call GrpAll(strDomain)
	adoRS.MoveNext
Loop
adoRS.Close
adoConn.Close
wscript.quit
 
Function GrpAll(x)
	'To search for more groups, edit the "strFilter" line. It uses a simple
	' LDAP or (|) so multiple groups can be added. It uses ADO record sets
	' to loop so it doesn't have to find all of them, just one. Every domain
	' will contain at least the Domain Admins group.
	Set adoCommand = CreateObject("ADODB.Command")
	Set adoConnection = CreateObject("ADODB.Connection")
	adoConnection.Provider = "ADsDSOObject"
	adoConnection.cursorLocation = adUseClient
	adoConnection.Open "Active Directory Provider"
	adoCommand.ActiveConnection = adoConnection
 
	strBase   = ";"
	strFilter = "(&amp;(objectCategory=group)(|((name=Enterprise Admins*)" &amp; _
				"(name=Domain Admins*)(name=Schema Admins*))));"
	strAttrs  = "name,member;"
	strScope  = "subtree"
 
	strQuery = strBase &amp; strFilter &amp; strAttrs &amp; strScope
	adoCommand.CommandText = strQuery
	adoCommand.Properties("Page Size") = 5000
	adoCommand.Properties("Timeout") = 30
	adoCommand.Properties("Cache Results") = False
 
	Set adoRecordset = adoCommand.Execute
 
	objTempFile.WriteLine "Group report for domain: " &amp; x
 
	adoRecordset.MoveFirst
 
	Do Until adoRecordset.EOF
	    objTempFile.WriteLine vbTab &amp; adoRecordset.Fields(0).Value
		For Each strMember in adoRecordset.Fields(1).Value
			objTempFile.WriteLine vbTab &amp; vbTab &amp; strMember
		Next
	    adoRecordset.MoveNext
	Loop
 
	adoRecordset.Close
	adoConnection.Close
End Function