InfiniTec - Henning Krauses Blog

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

DirectoryServices revisited

As promised in the first article (See here), here is another article on the classes in my DirectoryServices package. Here we go...

Lost in translation...

Ever wanted to translate an accountname like contoso\jdoe to the corresponding distinguished name? The Translator class comes to the rescue:

    1 Translator translator;

    2 TranslationResult result;

    3 

    4 using (Connection connection = newConnection("contoso.local", DirectoryIdentifierType.DnsDomain, false,

    5     newNetworkCredential("administrator", "password", "contoso"), AuthType.Basic))

    6 {

    7     translator = newTranslator(connection);

    8     translator.InputFormat = NameFormat.NT4AccountName;

    9     translator.OutputFormat = NameFormat.DistinguishedName;

   10     translator.Translate("contoso\\administrator");

   11 

   12     result = translator.Results[0];

   13     if (result.Status == TranslationStatus.Success)

   14     {

   15         Console.WriteLine("The " + translator.OutputFormat + " of " + result.InputName + " is " + result.TranslatedName);

   16     }

   17     else

   18     {

   19         Console.WriteLine("Could not translate the name. Error: " + result.Status);

   20     }

   21 }

This class wraps around the DsCrackNames function of the Win32 Directory Services API. Basically, you can translate between these name formats:

  • DistinguishedName
  • NT4AccountName
  • DisplayName
  • UniqueId
  • CanonicalName
  • UserPrincipalName
  • CanonicalNameEx
  • ServicePrincipalName
  • SidOrSidHistory
  • DnsDomainName
  • ListNamingContexts

 

Not every nameformat can be translated to every other. You will get a TranslationStatus.NoMapping error in this case.

The last entry, ListNamingContext, can be used to enumerate all naming contexts in the forest. To use this, set the InputFormat to ListNamingContext. Then, call the Translate method with at least one name (content is completely irrelevant):

    1 Translator translator;

    2 

    3 using (Connection connection = newConnection("contoso.local", DirectoryIdentifierType.Server, false,

    4     newNetworkCredential("administrator", "password", "sub"), AuthType.Basic))

    5 {

    6     translator = newTranslator(connection);

    7     translator.InputFormat = NameFormat.ListNamingContexts;

    8     translator.Translate("any_value");

    9 

   10     foreach (TranslationResult result in translator.Results)

   11     {

   12         Console.WriteLine(result.TranslatedName);

   13     }

   14 }

If the InputFormat is set to NameFormat.Unknown, the directory server tries to determine the format of the name(s) to translate. This causes some performance degration - If you know the format, you should supply it.

Unleashing the power of ambiguous name resolution...

Outlook has a handy feature called ambiguous name resolution: You can type only a part of a name, and Outlook resolves the given name to a complete name, if possible. Active Directory also implements this feature, and it's possible to use it with a special LDAP query: (anr=jo*) will find all items in the Active Directory with a special set of properties (per default, givenName, surname, displayName, legacyExchangeDN, msExchMailNickname, RDN, physicalDeliveryOfficeName, , proxyAddress, sAMAccountName) matches the specified filter.

The PrincipalResolver encapsulates this feature and extends it with an additional feature: If the name being searched for is exactly two characters long, the filter is set to (|(anr=value)(&(givenName=value[0]*)(sn=value[1]*))), which effectively resolves initials. Here an example:

    1 PrincipalResolver resolver;

    2 

    3 using (Connection connection = newConnection("contoso.local", DirectoryIdentifierType.Server, false, newNetworkCredential("administrator", "password", "sub"), AuthType.Basic))

    4 {

    5     resolver = newPrincipalResolver(connection);

    6     resolver.FindAll("al", ResolveTypes.User);

    7 

    8     foreach (ActiveDirectoryUser entry in resolver.SearchResult)

    9     {

   10         Console.WriteLine(entry.DisplayName);

   11     }

   12 }

If the domaincontroller, to which the connection object is bound is a global catalog, the entire forest will be searched. To search only a part of the forest, specify these settings:

    1 resolver.ResolveScope = ResolveScope.Domain;

    2 resolver.SearchRoot = Searcher.RootDomain;

The first line restricts the search to the specified domain, while the second line sets the domain for the search. Two default values are available: Searcher.RootDomain, which searches the root domain of the forest, and Searcher.DefaultDomain, which searches the default domain of the domain controller the current connection is bound to.

The PrincipalSearcher can search either for users, groups or both types. Note, that users do include contacts as well.

Speaking of users and groups

To simplify the handling of users and groups, there are two classes to handle these to types: The ActiveDirectoryUser and the ActiveDirectoryGroup:


(click to enlarge)

The base class for both classes is the ActiveDirectoryEntry, which contains some properties and methods for handling Active Directory entries. Based on the ActiveDirectoryEntry is the ActiveDirectoryPrincipal, which contains some properties regarding group membership and SIDs.

Both, the ActiveDirectoryUser and the ActiveDirectoryGroup inherit from this class: The ActiveDirectoryUser exposes mst the properties available on user objects. The same is true fro the ActiveDirectoryGrup.

For performance reasons, the group memberships are only exposed in SID form (the group memberships are stored in this way). The TranslateSids method can be used to translate those sids to a more readable form.

Thats it for now... I hope this library is of some use to anyone...

Technorati:

Posted by Henning Krause on Sunday, September 17, 2006 12:00 AM, last modified on Sunday, September 17, 2006 12:00 PM
Permalink | Post RSSRSS comment feed