Link to home
Start Free TrialLog in
Avatar of tobinmarch
tobinmarch

asked on

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

Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image


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

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
ASKER CERTIFIED SOLUTION
Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of tobinmarch
tobinmarch

ASKER

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?

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
Thanks for that info. I do need the DN of the entry for further processing.

Appreciate all your help.

Glad I could help :)

Chris