InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Find an user in a multi-domain Active Directory enironment programmatically

More Information

An often given solution to the problem outlined above is something like this (C#):

    1 public void FindUser(string name)

    2 {

    3     DirectoryEntry gc;

    4     DirectoryEntry searchRoot;

    5     DirectorySearcher searcher;

    6     SearchResultCollection result;

    7 

    8     // Get the directoryentry of the Global Catalog root

    9     gc = new DirectoryEntry("GC:", username, password, AuthenticationTypes.Secure);

   10 

   11     // This node has exactly one child, which can be used to search the entire forest

   12     foreach (DirectoryEntry child in gc.Children)

   13     {

   14         searchRoot = child;

   15     }

   16 

   17     // Search the forest for the user object

   18     searcher = new DirectorySearcher(searchRoot,

   19     string.Format("(&(objectCategory=person)​(objectClass=user)​(sAMAccountName={0}))", name),

   20     new string[] { "distinguishedName" }, SearchScope.Subtree);

   21 

   22     result = searcher.FindAll;

   23 }

Now, the result variable contains all user-accounts with the given account name. But in a multi-domain environment, several users may have the same account name.
So, one must iterate through the list to determine the domain of each object. Unfortunately, that information is not stored within the user object.
Another drawback is, that the above solution can only be used from a computer which is logged on to a domain within the forest. From outside the forest, this will not work.
The solution in the next paragraph shows how to work around these issues.

Solution

To find an user-account object based on the domain\username information, follow the following steps:

  1. Get the directory entry for RootDse (LDAP://RootDse or GC://RootDse).
  2. From that entry, retrieve distinguished name for the configuration naming context (Property configurationNamingContext).
  3. Get the directory entry CN=<domain name>, CN=Partitions, <distinguished name of configuration naming context>. Replace the <domain name> with the domain part of the username, and the <distinguished name of configuration naming context> with the distinguished name of the configuration naming context found above. If no such directory entry exists, the given domain was invalid.
  4. From that entry, get the naming context of the domain (Property nCName).
  5. Now that you have the distinguished name of the domain where the desired user account is located, you can do a search on that domain (depending on your environment and the required information from the user object, it may or may not be a good idea to use the global catalog).
    To find the user object in domain specified above, do a search with this filter: (&(objectCategory=person)(objectClass=user)(sAMAccountName=<username>)) (Replace the <username> with the name of the account you are searching for).
    This search will either return one found directory entry, or nothing if no user with the given account name exists.

C# Example

Below is a sample in C#:

Note: This code assumes you have a DirectoryEntry pointing to the Configuration-Naming Context

    1 /// <summary>

    2 /// Searches the forrest for the directory entry of the given user.

    3 /// </summary>

    4 /// <param name="principalname">Name of the user to find. Must be in the form domain</param>

    5 /// <param name="useGC">Specifies whether to use the Global Catalog to find the user. If false, a standard LDAP-Query is used.</param>

    6 /// <returns>The directoryentry of the user, if it is found. Null otherwise.</returns>

    7 public DirectoryEntry FindUser(string principalname, bool useGC)

    8 {

    9     DirectoryEntry searchRoot;

   10     DirectorySearcher searcher;

   11     string[] name;

   12     string ncName;

   13 

   14     name = principalname.Split('\\\\');

   15     if (name.Length != 2) throw new ArgumentException("principalname is not in the correct format", principalname);

   16 

   17     ncName = ResolveNetBiosNameToDN(name[0]);

   18 

   19     searchRoot = GetDirectoryEntry(ncName, useGC);

   20 

   21     searcher = new DirectorySearcher(searchRoot,

   22                                     string.Format("(&(objectCategory=person)​(objectClass=user)​(sAMAccountName={0}))", name[1]),

   23                                     new string[] {"distinguishedName"}, SearchScope.Subtree);

   24 

   25     try

   26     {

   27         return searcher.FindOne().GetDirectoryEntry();

   28     }

   29     catch (NullReferenceException ex)

   30     {

   31         throw new ArgumentException("The given username was not found", "principalname", ex);

   32     }

   33 }

   34 

   35 private string ResolveNetBiosNameToDN(string netbiosName)

   36 {

   37     try

   38     {

   39         return (string) GetDirectoryEntry(string.Format("CN={0}, CN=Partitions, {1}", netbiosName, (string) ConfigurationNamingContext.Properties["distinguishedName"].Value)).Properties["nCName"].Value;

   40     }

   41     catch (System.Runtime.InteropServices.COMException ex)

   42     {

   43         if ((uint) ex.ErrorCode == 0x80072030) throw new ArgumentException("The given netbios name was invalid", "netbiosName", ex);

   44         else throw;

   45     }

   46 }

   47 

   48 public DirectoryEntry GetDirectoryEntry(string distinguishedName)

   49 {

   50     return GetDirectoryEntry(distinguishedName, false);

   51 }

   52 

   53 public DirectoryEntry GetDirectoryEntry(string distinguishedName, bool useGC)

   54 {

   55     string path;

   56 

   57     path = (useGC) ? "GC://" : "LDAP://";

   58     if (_Server != null) path += _Server;

   59 

   60     if ((!path.EndsWith("/")) && (distinguishedName != "")) path += "/";

   61     path += distinguishedName;

   62 

   63     if (path.EndsWith("//")) path = path.Remove(path.Length-2, 2);

   64 

   65     return new DirectoryEntry(path, Username, Password, _AuthenticationType);

   66 }


Posted by Henning Krause on Friday, December 31, 2004 12:00 AM, last modified on Monday, November 29, 2010 9:25 PM
Permalink | Post RSSRSS comment feed