Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

ADSI / LDAP query OU tree 1

Status
Not open for further replies.

djhawthorn

Technical User
Mar 4, 2002
641
AU
I have the following code which enumerates the list of OU's in the domain and outputs the list to a HTA (indented using UL and LI tags):

Code:
Function DoRecursiveLookup(strObjectDN)
  Set objConnection = GetObject("LDAP://" & strObjectDN)

  For each objChild In objConnection
    If (objChild.Class = "organizationalUnit") Then
      strOUName = Right(objChild.Name, Len(objChild.Name) - 3)
      strOUPath = objChild.DistinguishedName
      'Do some stuff to print the OU name to the HTA
  
      'Call the function again to enumerate sub-OU's
      DoRecursiveLookup("ou=" & strOUName & "," & strObjectDN)
    End If
  Next
End Function

Though as we have some 60,000 objects in the domain, it takes a while to build the list

Is there a faster way to enumerate (or filter on) only OU objects? I started playing around with ADO, however as soon as it loops it crashes, probably because I'm trying to instanciate two of the same object.

[ponder][laughtears] The dumber they think you are, the more surprised they'll be when you kill them! [machinegun][rofl2]
 
1) You should post this question in the VBScript forum.

2) Consider using ADO to get the list of OUs from the domain...

Code:
Option Explicit

Dim oDSE, oADOConnection, oADOCommand, sSearchRoot, oRecordSet, sDNC

' Bind to AD... Future 'gets' will not re-bind (efficient)
Set oDSE = GetObject("LDAP://rootDSE")

' Setup ADO Connection to Active Directory
Set oADOConnection = CreateObject("ADODB.Connection")
With oADOConnection
	.Provider = "ADsDSOObject"
	.Open "Active Directory Provider"
End With

' Setup ADO Command parameters
Set oADOCommand = CreateObject("ADODB.Command")
With oADOCommand
	.ActiveConnection = oADOConnection
	.Properties("Page Size") = 1000
	.Properties("Timeout") = 30
	.Properties("Cache Results") = False
End With

sDNC = oDSE.Get("defaultNamingContext")

sSearchRoot = "<LDAP://" & sDNC & ">"
Const sFilter = "(&(objectClass=organizationalUnit))"
Const sAttribs = "distinguishedName"
Const sScope = "subtree"

oADOCommand.CommandText = sSearchRoot & ";" & sFilter & ";" & sAttribs & ";" & sScope
Set oRecordSet = oADOCommand.Execute

Do Until oRecordSet.EOF
	wscript.Echo oRecordSet.Fields("distinguishedName").Value
	' or perform "stuff" to insert information into HTA

	' Move to the next record in the recordset.
	oRecordSet.MoveNext
Loop

' Close RecordSet
oRecordSet.Close

PSC

Governments and corporations need people like you and me. We are samurai. The keyboard cowboys. And all those other people out there who have no idea what's going on are the cattle. Mooo! --Mr. The Plague, from the movie "Hackers
 
That works; only that I need to set sScope to onelevel in order to show nested levels.

Eg.
Code:
<ul>
  <li>Level 1 - 1</li>
  <ul>
    <li>SubLevel 1 - 1</li>
    <li>SubLevel 1 - 2</li>
  </ul>
  <li>Level 1 - 2</li>
</ul>
As soon as I call the function to go into the sublevel, it will die as the oADOCommand object (etc) already exist and hold values for the initial level.

Unless I can use dynamic variable names or something to generate new variables for each level I dig down into?

[ponder][laughtears] The dumber they think you are, the more surprised they'll be when you kill them! [machinegun][rofl2]
 
The search I specified will return all OUs in the domain regardless of tree depth. What are you looking for?

PSC

Governments and corporations need people like you and me. We are samurai. The keyboard cowboys. And all those other people out there who have no idea what's going on are the cattle. Mooo! --Mr. The Plague, from the movie "Hackers
 
It will return all OU's, yes, but I need to build a tree view of those OU's, not have a flat listing of them.

So currently I have something like (somewhat psudeo code):

Code:
Function DoRecursiveLookup(strObjectDN)
  'Start of this level of OU
  echo "<ul>"
    
  'Retrieve a list of OU's for this level
  GetObject("LDAP://" & strObjectDN)
 
  'Go through all the OU's in this onelevel
  For Each OU in oneLevelOU
    'Print out the OU name
    echo "<li>" & OU.name & "</li>"

    'Now recursively run the functions for any sub-OU's
    '(which will print a new <ul> tag to indent to the next level, 
    'followed by all OU's on that level, before returning to complete 
    'the OU's on this level)
    DoRecursiveLookup("OU=" & OU & "," & strObjectDN)
  Next

  'End of this level
  echo "</ul>"
End Function

I end up with something like

Code:
Level1OU1
   Level2OU1
      Level3OU1
      Level3OU2
      Level3OU3
   Level2OU2
      Level3OU4
      Level3OU5
Level1OU2
   Level2OU3
Level1OU3

Thats the kind of output I'm after.

The only other way I can think of doing it is using the ADO object like you suggest, then building the list after the fact (rather than on the fly). It would just take a fair bit more code.

[ponder][laughtears] The dumber they think you are, the more surprised they'll be when you kill them! [machinegun][rofl2]
 
You can recurse this way... Every recursion will create a unique oRecordSet object...

Code:
Option Explicit

Dim oDSE, oADOConnection, oADOCommand, sSearchRoot, sDNC

' Bind to AD... Future 'gets' will not re-bind (efficient)
Set oDSE = GetObject("LDAP://rootDSE")

' Setup ADO Connection to Active Directory
Set oADOConnection = CreateObject("ADODB.Connection")
With oADOConnection
    .Provider = "ADsDSOObject"
    .Open "Active Directory Provider"
End With

' Setup ADO Command parameters
Set oADOCommand = CreateObject("ADODB.Command")
With oADOCommand
    .ActiveConnection = oADOConnection
    .Properties("Page Size") = 1000
    .Properties("Timeout") = 30
    .Properties("Cache Results") = False
End With

sDNC = oDSE.Get("defaultNamingContext")

sSearchRoot = "<LDAP://" & sDNC & ">"
Const sFilter = "(&(objectClass=organizationalUnit))"
Const sAttribs = "distinguishedName,name"
Const sScope = "onelevel" ' "subtree"

Call RecurseOUs(oADOConnection, oADOCommand, sSearchRoot, "")

Sub RecurseOUs(ByRef oADOConnection, ByRef oADOCommand, ByVal sSearchRoot, sTab)
	Dim oRecordSet

	oADOCommand.CommandText = sSearchRoot & ";" & sFilter & ";" & sAttribs & ";" & sScope
	Set oRecordSet = oADOCommand.Execute

	Do Until oRecordSet.EOF
	    wscript.Echo sTab & oRecordSet.Fields("name").Value
	    ' or perform "stuff" to insert information into HTA

		sSearchRoot = "<LDAP://" & oRecordSet.Fields("distinguishedName").Value & ">"

		Call RecurseOUs(oADOConnection, oADOCommand, sSearchRoot, sTab & vbTab)

	    ' Move to the next record in the recordset.
	    oRecordSet.MoveNext
	Loop

	' Close RecordSet
	oRecordSet.Close
End Sub

I tested this code and it took about 3 seconds to parse and display about 140 OUs. Most of that time is wasted setting up the ADO connection and performing the wscript.echo...


PSC

Governments and corporations need people like you and me. We are samurai. The keyboard cowboys. And all those other people out there who have no idea what's going on are the cattle. Mooo! --Mr. The Plague, from the movie "Hackers
 
Damn, it does work.

I missed a few things in my attempt at coding that obviously, as when I tried it cried around the time it (re)creates the command object. Thanks heaps for that - I really appreciate it.

I learnt a thing or two too.

Out of interest, why/what is the ByRef and ByVal do for the subroutine? I've never used them before and only seen them occasionally.

[ponder][laughtears] The dumber they think you are, the more surprised they'll be when you kill them! [machinegun][rofl2]
 
ByRef and ByVal are a little hard to explain. It has to do with how variables are passed. Essentially, when you pass a variable (including objects), you either copy (byval) the data (which may or may not be possible depending on the variable), or you pass a reference (byref) to the memory space of the original variable.

If you google it, you will find a reference back to a Microsoft blog on the subject and there are some good examples. See here...

In this case, the ByRef is my way of documenting the fact that I am referencing back to an object I declared somewhere else. The ByVal, lets me know that I am copying the variable, so that I don't modify it's predecessor.

In the end this is just semantics. They aren't technically required.


PSC

Governments and corporations need people like you and me. We are samurai. The keyboard cowboys. And all those other people out there who have no idea what's going on are the cattle. Mooo! --Mr. The Plague, from the movie "Hackers
 
Here's a link to a very brief reference of how variables are passed, by default, in VBScript. (I keep it on my wall as a reference, in case I need to restrict the modification of a passed variable)

[URL unfurl="true"]http://classicasp.aspfaq.com/general/how-do-i-specify-byref/byval-in-vbscript.html[/url]

PSC

Governments and corporations need people like you and me. We are samurai. The keyboard cowboys. And all those other people out there who have no idea what's going on are the cattle. Mooo! --Mr. The Plague, from the movie "Hackers
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top