Search code examples
active-directoryldapdirectoryservicesaccount-management

PrincipalContext: How do I get the full LDAP context path from only providing an OU?


I'd like to increase speed for finding users in a query similar to this:

using (PrincipalContext pc = PrincipalContext(ContextType.Domain))
using (UserPrincipal up = new UserPrincipal(pc) { SamAccountName = $"*{search}*" })
using (PrincipalSearcher ps = PrincipalSearcher(up))
{
  ...
}

Currently, the query searches the whole directory, taking a very long time.

I'd like to constrain the context to a specific container, but I only now a single portion of this container.

So, for instance, from a full LDAP path like this: OU=a,OU=b,OU=c,dc=d,dc=e,dc=loc I only have OU=b.

How can I retrieve the full LDAP path for using it in the context portion of the PrincipalContext constructor by using a class from System.DirectoryServices.AccountManagement having only a fraction of the path?


Solution

  • The root of your problem is this: SamAccountName = $"*{search}*"

    Specifically, the * at the beginning. The sAMAccountName attribute is indexed, so it is normally a very fast query. But since you have a wildcard at the beginning, it cannot use the index in this search. That means it has to look at every user in your domain to try and find a match.

    If you can change that to $"{search}*", your problems will go away.

    But to answer your actual question,

    I'd like to constrain the context to a specific container, but I only now a single portion of this container.

    If you really want to go through this, you will have to perform a search to find the OU and read the distinguishedName for it. You can't do it with PrincipalSearcher since the System.DirectoryServices.AccountManagement namespace just doesn't have any class for OUs. You will have to use DirectorySearcher directly (which is what PrincipalSearcher uses in the background anyway). It would look something like this:

    var ouName = "b";
    var search = new DirectorySearcher(new DirectoryEntry()) {
        Filter = $"(&(objectClass=organizationalUnit)(name={ouName}))"
    };
    search.PropertiesToLoad.Add("distinguishedName");
    var ou = search.FindOne();
    var ouDn = ou.Properties["distinguishedName"][0];
    

    Then you can use ouDn as the base path for your user search.