At Silicon Valley Code Camp we got in a discussion about efficient LDAP queries.  I thought it would best to write a few blog posts about performance considerations of searching when using System.DirectoryServices (including LDAP queries).  I’m using the MSDN article Creating More Efficient Microsoft Active Directory-Enabled Applications as the basis for my posts, however personal experience and other sources are sprinkled in heavily.  I find the MSDN article talks about performance from a statistical stand point as opposed to a real world standpoint.

Today’s post is about configuring your DirectorySearcher object.  Future posts will cover:

  • LDAP Filters
  • Advanced Binding Options
  • Analyzing Performance of Searches
  • Creating New Indexes

    Some of this information is specific to Active Directory/ADAM.  However many if not most of the concepts are universal to all LDAP servers.

    Set Appropriate Search Root

    If you were searching your computer for a file you would have your searching tool start at the closest folder that you could to where the file could possibly be.  For example if you know the file is in c:\Windows you would start the search there instead of C:\.  The closer you get to the file, the faster the search will be.

    The same concept applies to LDAP queries.  If you know the object is under OU=Staff,DC=domain,dc=com then you would want to start searching there.

    In .Net this is easy to do.  On your DirectorySearcher object set the property SearchRoot to where you want the search to start.

    DirectorySearcher searcher = new DirectorySearcher();
    searcher.SearchRoot =
         new DirectoryEntry(LDAP://OU=Staff,DC=domain,DC=local);

    If you do not set the SearchRoot (or set it to null) the search will look at RootDSE of the “default” LDAP server (meaning this only works on an AD connected client) and start the search from the DefaultContext.  That’s a fancy way of saying that you will search your entire domain.

    Set Appropriate Search Scope

    There are three kinds of search scope:

        • Base – Only search the object specified as the SearchRoot
        • One-Level – Only search direct children of the SearchRoot
        • Subtree – Search all children of the SearchRoot, including sub-children.

    In .Net the base level search is often not necessary because if we know the path to the object we can simply create a DirectoryEntry object for it.  However there are circumstances where it becomes useful.

    The theory of setting search scope is similar to setting an appropriate search root.  If you know the object is in the SearchRoot container there is no need to iterate through sub-OU’s.  The fewer objects the directory has to search the faster your query will be.

    Setting your search scope in .Net is once again trivial.  Here we set it to One-Level.

    DirectorySearcher searcher = new DirectorySearcher();
    searcher.SearchScope = SearchScope.OneLevel;

    Set PropertiesToLoad Property

    You can define which LDAP attributes should be returned when searching for an object.  The more attributes returned, the more processing power to retrieve those attributes, the more data on the network, which means the longer it will take to retrieve the data you need.

    In .Net if you do not specify the PropertiesToLoad property it returns the default set of attributes.  When searching against my Windows 2003 Forest it retrieves 25 LDAP attributes.  I have no idea where it gets that list from, or if it is modifiable.

    To set your own list of PropertiesToLoad simply use the following code:

    DirectorySearcher searcher = newDirectorySearcher();

    searcher.PropertiesToLoad.Add(“sAMAccountName”);

    Use Paged Searches

    By default Active Directory will only return a maximum of 1000 results.  Only being able to search for up to 1000 objects would make the service useless as a directory server.  LDAP servers provide a method called paging to return larger result sets by allow clients to request the next page of objects.  This prevents excessive memory use by the server (and the client).  By paging the results it only has to store up to 1000 objects at a time in memory.

    To enable paging in .Net all we need to do is set the property PageSize to a value larger than 0.  Ideally you would set the PageSize to be the same as the servers MaxPageSize value to reduce the number of pages required to iterate through the entire set.  If you set the PageSize to a value larger than the MaxPageSize the server will still return the number of objects specified by MaxPageSize.

    DirectorySearcher searcher = newDirectorySearcher();

    searcher.PageSize = 1000;

    After PageSize is set paging happens seamlessly.  If you foreach through the SearchResultCollection it seamlessly goes from object 1000 to object 1001.

    SearchResultCollection results = searcher.FindAll();
    foreach (SearchResult result in results)
    {
       Console.WriteLine(result.Path);
    }

    If you step through your code you will see network activity and a visible delay when you try to read object 1001.

    Advanced DirectorySearcher Settings

    Here are some more advanced and less prominent settings you can change on your DirectorySearcher objects.

    The property PropertyNamesOnly causes the directory to only return the names of properties and does not retrieve their value.  This is useful if you only want to see if an object has a value for a certain attribute, or if you’re going to convert to a DirectoryEntry object anyways.  Converting to a DirectoryEntry object causes a new call to LDAP which will then create an object cache and populate the properties values anyways.

    The property ServerPageTimeLimit sets how long the server should search for objects before returning a page of results.  This is useful if you are doing a complicated query and want to retrieve results after so many seconds, even if a full page of results has not been retrieved yet.  With this setting enabled you will either receive a page of results when the PageSize limit is reached, or the specified amount of time as elapsed.

    LA Code Camp

    Thanks to everyone that made it to my talk at LA Code Camp.  We had a good group with lots of great questions.  Here’s the slide deck and code as promised.

    Slide Deck

    Code

    One of my customers has a file server with ~80,000 folder quota’s created by autoquota’s.  Pulling up the “File Server Resource Manager” GUI takes ~60 minutes to iterate through all of the quota’s before you can begin working with them.

    The solution is to use “dirquota.exe” from the command line.  It’s a simple command that has really good inline help.  Just type “dirquota.exe” and your presented with the options:

    U:\>dirquota
    The syntax of this command is:

    Dirquota {Quota | Autoquota | Template | Admin}

    Quota             List, add, modify, and delete quotas.
    Autoquota         List, add, modify, and delete auto apply quotas.
    Template          List, add, modify, and delete quota templates.
    Admin             Configure settings and perform administrative operations.

    The minimum sequence that uniquely identifies a switch can be used as an
    abbreviation. For example, “Dirquota q l /list-n” is equivalent to
    “Dirquota quota list /list-notifications”.

    As you select your options you drill into more and more detailed options.  For example:

    U:\>dirquota quota
    The syntax of this command is:

    Dirquota Quota {List | Add | Modify | Delete | Scan | Freespace}

    List              List currently configured quotas.
    Add               Add new quotas.
    Modify            Modify existing quotas.
    Delete            Delete existing quotas.
    Scan              Initiate scan to update quota usage.
    Freespace         Query free space.

    The minimum sequence that uniquely identifies a switch or a switch value can be
    used as an abbreviation. For example, “Dirquota q l /list-n” is equivalent to
    “Dirquota quota list /list-notifications”.

    To get details on a specific quota:

    U:\>dirquota quota list /path:I:\home\jdoe
    Quotas on machine FILE-HOME:

    Quota Path:             I:\home\jdoe
    Source Template:        None
    Quota Status:           Enabled
    Limit:                  1,000.00 MB (Hard)
    Used:                   344.64 MB (34%)
    Available:              655.36 MB
    Peak Usage:             344.67 MB (10/21/2008 10/21/2008)
    Thresholds:
    Warning ( 85%):      None
    Warning ( 95%):      None
    Limit (100%):        None

    All the same information and capablities of the GUI, but easier and more reliable access.

    SQL: Select Top X

    I do a lot of work with identity, and it’s very common to be connecting to an Oracle, Microsoft SQL, and sometimes even MySql databases . . . all in the same project.

    What I do with SQL in these instances is so basic it really doesn’t matter. Usually it’s just a glorified CSV file. However there is one SQL statement I use on a regular basis that isn’t standard between any of the three SQL engines. (thanks to Thea Burgers Blog for putting this information all in one place):

    SQL Server:

    SELECT TOP 10 * FROM table

    ORACLE:

    SELECT * FROM table WHERE ROWNUM <=10

    MYSQL:

    SELECT * FROM table LIMIT 10

    Time for a little standardization?

    I’m one of the few developers (one of the few windows users for that matter) that run without local admin rights. I do this for a few reasons:

    • I have the same experience others have in corporate environments
    • I don’t write code with LUA bugs
    • It’s safer; I comfortably don’t use anti-virus/anti-spyware software with limited concerns.
    • It lets me write this blog post, as well as an upcoming presentation on running without local admin rights

    Starting in Windows 2000 the runas command made it reasonable to run without local admin rights. However there’s no apparent way to access your local file system under alternate credentials. This comes in really handy when you need to modify the all users desktop or start menu, want to do some house cleaning of system files, etc, etc

    There is a cryptic command that will give you file system access under alternate credentials:

    Runas /user:administrator “explorer /separate”

     

    You’ll be prompted for your password and then you’ll get access to the file system under the account that you specified on the /user: flag.

    One small annoyance is that the screen doesn’t refresh when you modify files. So if you delete a file you need to hit F5 to actually see the file disappear. Same with creating files/folders, renames, etc.