Get all AD users (ADODB versus DirectorySearcher) performance issue.

I have 2 serprate functions, One uses an ADODB connection to Active directory and the other is using a DirectorySearcher object to get a "list" of all the user records in our domain.

The intent of this code it to get all our users telephone information from the Cisco Call Manager (LDAP) and import it into Active Directory (Win 2003).

The older method using ADODB is much quicker than the DirectorySearcher method, and the application rusn fine with it enabled. I would like to use the DirectorySearcher method as it doesnt require me to distribute any extra dll's with my application.

What is wrong with this and how could it be optimized to run more efficiently?

We have 2000+ users in AD. The ADODB method takes aprox. 10 sec to populate the array, while the DirectorySearcher method takes over 60sec.


Imports System
Imports System.IO
Imports System.DirectoryServices
Imports System.DirectoryServices.Protocols
Imports System.Net
Imports System.Text
Imports System.Runtime.InteropServices.COMException
Imports ADODB 'added via reference: C:\Program Files\Microsoft.NET\Primary Interop Assemblies\adodb.dll
 
Module modMain
 
    Sub Main()
        getADUsers_ADODB() '~10sec
        getADUsers_NEW() 'long time
    End Main
 
    Function getADUsers_ADODB()
        Dim arrADUsers As New ArrayList
 
        Try
            Dim oConn As New ADODB.Connection
            Dim oRS As New ADODB.Recordset
            Dim oComm As New ADODB.Command
            'Dim strComputerName As String = Nothing
            Dim strDomainName As String = "saws.org"
 
            ' set connection properties
            With oConn
                .Provider = "ADsDSOObject"
                .Open("Active Directory Provider")
            End With
 
            ' set command properties
            With oComm
                .ActiveConnection = oConn
                .CommandText = "<LDAP://DC=domain,DC=org>;(&(ObjectClass=user)(!ObjectClass=computer)(!description=Built-in*));sAMAccountName,employeeID,telephoneNumber,mail,distinguishedName"
            End With
 
            ' open recordset
            oRS = oComm.Execute
 
            If Not oRS.EOF Then
                oRS.MoveFirst()
                Do Until oRS.EOF
                    Dim arrADUser As New ArrayList
                    If Not oRS.Fields(0).Value.ToString = "" Then
 
                        arrADUser.Add(oRS.Fields(0).Value)
 
                        If Not oRS.Fields(1).Value.ToString = "" Then
                            arrADUser.Add(oRS.Fields(1).Value)
                        Else
                            arrADUser.Add("null")
                        End If
 
                        If Not oRS.Fields(2).Value.ToString = "" Then
                            arrADUser.Add(oRS.Fields(2).Value)
                        Else
                            arrADUser.Add("null")
                        End If
 
                        If Not oRS.Fields(3).Value.ToString = "" Then
                            arrADUser.Add(oRS.Fields(3).Value)
                        Else
                            arrADUser.Add("null")
                        End If
 
                        If Not oRS.Fields(4).Value.ToString = "" Then
                            arrADUser.Add(oRS.Fields(4).Value)
                        Else
                            arrADUser.Add("null")
                        End If
 
                    End If
                    arrADUsers.Add(arrADUser)
                    oRS.MoveNext()
                Loop
            End If
 
            ' clean up objects
            oRS.Close()
            oConn.Close()
            oRS = Nothing
            oComm = Nothing
            oConn = Nothing
 
 
        Catch ex As Exception
            Console.WriteLine()
            Console.WriteLine(ex.Message)
            Console.WriteLine()
            Console.WriteLine(ex.ToString)
        End Try
 
        Return arrADUsers
 
    End Function
 
 Function getADUsers_NEW()
        Dim arrADUsers As New ArrayList
 
        Try
            Dim de As DirectoryEntry = getDirectoryEntry()
            Dim ds As New DirectorySearcher(de)
 
            Dim filter As New StringBuilder
            filter.Append("(&(objectCategory=Person)(objectClass=user))")
 
            ds.Filter = filter.ToString
            ds.SearchScope = DirectoryServices.SearchScope.Subtree
            Dim results As SearchResultCollection = ds.FindAll
 
            Console.WriteLine("User COUNT: {0}", results.Count)
            Console.WriteLine()
 
            For Each result As SearchResult In results
                Dim dey As DirectoryEntry = getDirectoryEntry(result.Path)
                Console.WriteLine("User  : {0}", dey.Properties("sAMAccountName").Value)
                Console.WriteLine("Phone : {0}", dey.Properties("telephoneNumber").Value)
                Console.WriteLine()
                dey.Close()
            Next
 
            de.Close()
 
        Catch ex As Exception
            Console.WriteLine()
            Console.WriteLine(ex.Message)
            Console.WriteLine()
            Console.WriteLine(ex.ToString)
        End Try
 
        Return arrADUsers
 
    End Function
 
    Function getDirectoryEntry(Optional ByVal argPath As String = Nothing) As DirectoryEntry
        Dim de As New DirectoryEntry
        If argPath Is Nothing Then
            de.Path = "LDAP://DC=domain,DC=org"
        Else
            de.Path = argPath
        End If
        de.Username = "domain\username"
        de.Password = "password"
        Return de
    End Function
 
End Module

Open in new window

tobinmarchAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Chris DentPowerShell DeveloperCommented:

I suspect it's because of this:

                Dim dey As DirectoryEntry = getDirectoryEntry(result.Path)

I'd have to test it to be sure, but that looks to me exactly like you're connecting to the user account then retrieving values, whereas in the ADO query you're just accessing an existing record set.

Chris
0
Chris DentPowerShell DeveloperCommented:

Okay, I've checked and I am as sure as I can be.

I expect you'll find that this bit below is very fast:

            Dim results As SearchResultCollection = ds.FindAll

I know I did when I created a C# version.

However, in mine I'm using FindAll to find a small collection of results (5 to 10 at the most) so the overhead is much lower on retrieving the other attributes when I'm returning and handling the results.

Moving onto the next post while I play with the "make it faster" bit :)

Chris
0
Chris DentPowerShell DeveloperCommented:

Theoretically that gives us something like this. The syntax in C# is slightly different, so please check I have the conversion to Vb.NET right, programming isn't really my thing.

We avoid the User connection, which takes us back to match the ADO query.

Chris

Function getADUsers_NEW()
	Dim arrADUsers As New ArrayList
 
	Try
		Dim de As DirectoryEntry = getDirectoryEntry()
		Dim ds As New DirectorySearcher(de)
 
		Dim filter As New StringBuilder
		filter.Append("(&(objectCategory=Person)(objectClass=user))")
 
		ds.Filter = filter.ToString
		ds.SearchScope = DirectoryServices.SearchScope.Subtree
 
		ds.PropertiesToLoad.Add("sAMAccountName")
		ds.PropertiesToLoad.Add("telephoneNumber")
 
		Dim results As SearchResultCollection = ds.FindAll
 
		Console.WriteLine("User COUNT: {0}", results.Count)
		Console.WriteLine()
 
		For Each result As SearchResult In results
			Console.WriteLine("User  : {0}", SearchResult.Properties("sAMAccountName").Value)
			Console.WriteLine("Phone : {0}", SearchResult.Properties("telephoneNumber").Value)
			Console.WriteLine()
		Next
 
		de.Close()
 
		Catch ex As Exception
			Console.WriteLine()
			Console.WriteLine(ex.Message)
			Console.WriteLine()
			Console.WriteLine(ex.ToString)
	End Try
 
	Return arrADUsers
End Function

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Problems using Powershell and Active Directory?

Managing Active Directory does not always have to be complicated.  If you are spending more time trying instead of doing, then it's time to look at something else. For nearly 20 years, AD admins around the world have used one tool for day-to-day AD management: Hyena. Discover why

tobinmarchAuthor Commented:
I have tested the above response from Chris-Dent to my post and you are correct this is is much faster.

Before I accept your response answer me this:

By adding these lines;

ds.PropertiesToLoad.Add("sAMAccountName")
ds.PropertiesToLoad.Add("telephoneNumber")

are you specifying that these are the only LDAP properties that the SearchResultCollection should include?
0
Chris DentPowerShell DeveloperCommented:

The search results collection, as far as I know, doesn't include any explicit values at all by default.

With the PropertiesToLoad specified it will grab those values as well (or populate a null field), it works in much the same way as you must define the fields to retrieve using the ADO query.

You should still find that you can use .Path to access the distinguishedName of the object without further work (if you need to access the object as a DirectoryEntry).

Chris
0
tobinmarchAuthor Commented:
Thanks for that info. I do need the DN of the entry for further processing.

Appreciate all your help.
0
Chris DentPowerShell DeveloperCommented:

Glad I could help :)

Chris
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.