InfiniTec - Henning Krauses Blog

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

Get the WebDAV url for an Exchange 2000/2003 mailbox

More Information

For Exchange 2000 and Exchange 2003 prior Service Pack 1, the building of an URL suitable for WebDAV requests against a users mailbox is a rather complicated thing. A solution for this is descrbied under the section Solution.

From Exchange 2003 SP 1 onwards, you can just use his SMTP address to get access to his mailbox. For example, to access the mailbox of John Doe (jdoe@contoso.com) you would use the url

http://<myserver>/exchange/jdoe@contoso.com

Solution

To build the webaddress, you must do the following:

  1. If you only have a domain\username, not the distinguished name of the user, you must first get the latter one. See this aricle how to retrieve the name.
  2. Get the directory entry with the distinguished name of the user
  3. From that object, retrieve the property homemdb. This property contains the distinguished name of the mailbox store.
  4. Retrieve the directory entry of the mailbox store.
  5. From this object, retrieve the property named msExchOwningServer. This is the distinguished name of the Exchange server that hosts the mailbox.
  6. Get the root of the http virtual server on the exchange server. The distinguished name is CN=http, CN=Protocols, <distinguished name of the exchange server>.
  7. Search for all http virtual server (LDAP filter: (objectClass=protocolCfgHttpServer), Scope: OneLevel, retrieve these properties: msExchServerBindings, msExchDefaultDomain).
  8. From this list, retrieve the default SMTP server. It's the one without the attribute msExchDefaultDomain.
  9. Get the default SMTP domain of the organization:
    1. Open the node CN=Default Policy, CN=Recipient Policies, CN=<Name of your organization>, CN=Microsoft Exchange, CN=Services,<Distinguished name of the configuration naming context>.
    2. Retrieve the property gatewayProxy. This is a multi-valued property that contains the default addresses of the organization.
    3. Check each of the entries in that list. If it starts with SMTP:, you have found it.
  10. Get the property proxyAddresses from the directory object of the user. This is a multi-valued properties that contains all addresses that are assigned to the user. Each entry has the following consists of a protocol identifier and an address entry. For example:
    SMTP:john.doe@example.com
    If the protocol identifier is all uppercase, it is the default entry for that protocol.
  11. Iterate through the list of assigned addresses and search for the SMTP address which domain part matches with the default SMTP retrieved above.
  12. Select the correct HTTP Server:
    • If you found an email address corresponding to the default SMTP policy, extract the alias from the found address. For the above example the alias would be john.doe.  The correct HTTP server to use is the default HTTP Server
    • If there is no smtp address matching the default policy, iterate through the list of proxy addresses again, and do the following, if it is an SMTP address:
      1. Extract the alias and the domain from the address.

      2. Iterate through the list of virtual HTTP server and check the property msExchDefaultDomain of each entry. If it matches the domain of the users address domain, you have found the correct http server.

  13. Get the property msExchServerBindings from the selected HTTP server node. This entry has the following structure:
    IPAddress:Port:Servername
    The first and the last part of the string can be empty, like in this example:
    :80:
  14. Depending on the content of this field, you can now build the desired url:
    • If the servername of the msExchServerBindings property is not empty, build the OWA url like this:
      http://<Servername>:<Port>/exchange/<Alias>
    • If the servername is empty, but an ipaddress is present, build the OWA url like this:
      http://<IPAddress>:<Port>/exchange/<Alias>
    • If both, the servername and the ipaddress fields are empty, get the ipaddress for the exchange server:
      1. From the node of the exchange server, retrieve the property networkaddress.
      2. Iterate through this property and retrieve the value which starts with ncacn_ip_tcp:.
      3. Now, build the url with the above scheme, using the just found ip address.

Tags: , , , , ,

Technorati: , , , , ,

Posted by Henning Krause on Friday, December 31, 2004 12:00 AM, last modified on Tuesday, July 26, 2011 9:57 PM
Permalink | Post RSSRSS comment feed

Manage distributionlists programatically

Description

When examined via WebDAV, distribution lists are stored as items of type urn :content -classes:group. The Outlook message class (http://schemas.microsoft.com/exchange/outlookmessageclass) is IPM.DistList.
The information about the members of the distribution list is stored in the field http://schemas.microsoft.com/mapi/dlmembers. This is a multivalued field of type mv.base64. Unfortunately, this field is not documented by Microsoft.
But, as pointed out in the summary, user can manage their distribution lists via Outlook Web Access. So, one way to programatically manage distribution lists is to simulate an Outlook Web Access client. This method is used in this article.

Solution

To get the contents of a distribution list, make a GET request to the URL of the distribution list with the query-string set to Cmd=viewmembers. The User-Agent header must be set to Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.1.4322) so that the Exchange ISAPI filter knows the client can handle an xml response.
The response will look like this:

    1 <xml id="memberlist">

    2   <cdldata>

    3     <href>

    4       <!--Uri to distribution list -->

    5     </href>

    6     <error></error>

    7     <membertext></membertext>

    8     <unresolvedcount>0</unresolvedcount>

    9     <member>

   10       <memberid>0</memberid>

   11       <icon>/exchweb/img/icon-1off.gif</icon>

   12       <dn>user@example.com</dn>

   13       <email>user@example.com</email>

   14     </member>

   15     <member>

   16       <memberid>1</memberid>

   17       <icon>/exchweb/img/icon-member.gif</icon>

   18       <dn>Administrator</dn>

   19       <email>Administrator@contoso.local</email>

   20     </member>

   21   </cdldata>

   22 </xml>

The member is is espacially important when removing members from a distribution list, because it must be passed along with the request.

To add a new member to a distritubion list, make a POST request to the URL of the list and specify this body within the request:

    1 Cmd=addmember

    2 msgclass=IPM.DistList

    3 member=user@example.com

Again, the User-Agent field must be set to the value mentioned above.

On success, you will receive a HTTP/1.1 302 Moved Temporarily message that point to the url of the list with the querystring set to Cmd = viewmembers.

To remove a member from the list, make a POST request with the following body within the request:

    1 Cmd=deletemember

    2 msgclass=IPM.DistList

    3 memberid=1

Be sure to set the member id to the id of the entry you want to remove. When successful, you will receive the same response as above.


Technorati:

Posted by Henning Krause on Friday, December 31, 2004 12:00 AM, last modified on Monday, December 26, 2005 12:00 PM
Permalink | Post RSSRSS comment feed

Retrieving a users availability (free/busy data)

This article was formerly published under the title "Get Free/Busy data for an Exchange 2000/2003 user via HTTP requests".

For each user, Exchange stores a free/busy data on the server. Exchange 2000 and 2003 store the free/busy information in a public folder. Exchange 2007 can also do this for compatibility, but it offers a far more convenient way to get the availability, namely a WebService.

With Exchange 2000/2003 we are left with two options to read the availability data: Use OWA and pretend to be an Internet Explorer 6 or later, or access the free/busy data directly. I will cover each option in this arcticle.

Accessing the availability data directly

The availability information is stored in the NON_IPM_SUBTREE folder hierarchy below the Public Folders hierarchy in a folder called EX:/o=<Name of your organization>. For each administrative group, there is a subfolder. Each one of those folders contains the availability data for the users associated with that administrative group.

The question is: How do I get the right item in those folder for a particular user?

The answer comes in the form of the legacyExchangeDistinguishedName property from the Active Directory object of the user.


ADSIEdit showing the legacyExchangeDistinguishedName of a user (click to enlarge)

Once you have the legacyExchangeDistinguishedName of a user, you'll have to split it up. From position 0 to the slash ("/") before cn=Recipients and from that index to the end of the string. To construct the item url, you can use this method:

    1 publicstaticstring GetFreeBusyItemUrl(string legacyExchangeDistinguishedName)

    2         {

    3             string url;

    4             string orgFolder;

    5             string ouFolder;

    6 

    7             url = "http://myserver/public/NON_IPM_SUBTREE/SCHEDULE%2B%20FREE%20BUSY/EX:";

    8 

    9             orgFolder = legacyExchangeDistinguishedName;

   10             orgFolder = orgFolder.Substring(0, orgFolder.IndexOf("/cn=Recipients"));

   11             ouFolder = legacyExchangeDistinguishedName.Substring(orgFolder.Length);

   12 

   13             url += HttpUtility.UrlPathEncode(orgFolder.Replace("/", "_xF8FF_")) + "/USER-" +

   14                    HttpUtility.UrlPathEncode(ouFolder.Replace("/", "_xF8FF_")) + ".eml";

   15 

   16             return url;

   17         }

Since the legacyExchangeDistinguishedName contains a slash in the item name, you must encode it with is unicode representation, _xF8FF_, otherwise Exchange would treat it as a folder delimiter.

Once you have the url to the free/busy item, you can issue a PROPFIND on the item and retrieve these properties:

  • x68531003 (Busy months)
  • x68541102 (Busy events)
  • x68511003 (Tentative months)
  • x68521102 (Tentative events)
  • x68551003 (Out of office months)
  • x68561102 (Out of office events)
  • x68470003 (Start of the published range)
  • x68480003 (End of the published range)

All these properties live in the http://schemas.microsoft.com/mapi/proptag/ namespace.

We'll start with the last two properties. These contain the start date and the end date of the published data. These values always fall on month boundaries and are encoded in systime, a MAPI datatype.

You can use this code to convert a systime to a DateTime:

    1 ///<summary>

    2 /// Parses a MAPI systime and converts it to a <see cref="DateTime"/> instance.

    3 ///</summary>

    4 ///<param name="value">The value.</param>

    5 ///<returns></returns>

    6 privatestaticDateTime ParseSysTime(int value)

    7 {

    8     DateTime result;

    9 

   10     result = newDateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromMinutes(value);

   11 

   12     return result.ToLocalTime();

   13 }

The other properties contain the actual free/busy data. For each free/busy status there are two properties: One describes the months which are covered, and one contains the availability data for those months. For example, the appointments marked as busy are recorded in the properties x68531003 and x68541102. The first property has a datatype of mv.int, the latter one is a mv.binary. Both arrays should have the same length, otherwise, the free/busy data is corrupted. To decode the values, you'll have to iterate through those arrays and decode the information. You can use this code snippet to do this:

    1 privatevoid ParseFreeBusyEntries(IList<int> monthValues, IList<byte[]> eventEntries, FreeBusyStatus freeBusyStatus)

    2 {

    3     BinaryReader reader;

    4     DateTime monthStart;

    5     DateTime start;

    6     DateTime end;

    7 

    8     for (int i = 0; i < Math.Min(monthValues.Count, eventEntries.Count); i++)

    9     {

   10         monthStart = newDateTime(monthValues[i] >> 4, monthValues[i] & 15, 1);

   11 

   12         reader = newBinaryReader(newMemoryStream(eventEntries[i]));

   13 

   14         while (reader.BaseStream.Length - reader.BaseStream.Position >= 4)

   15         {

   16             start = (monthStart + TimeSpan.FromMinutes(reader.ReadUInt16())).ToLocalTime();

   17             end = (monthStart + TimeSpan.FromMinutes(reader.ReadUInt16())).ToLocalTime();

   18 

   19             StoreFreeBusyEntry(start, end, freeBusyStatus);

   20         }

   21     }

   22 }

The method requires you to extract the arrays from the xml response stream and decode the base64 encoded data to a byte array. The FreeBusyStatus type in line one is an enumeration consiting of the various free/busy status. The method iterates through the two arrays. From each entry in the monthValues array, it extracts the start date for that range. It then constructs a BinaryReader to read through the corresponding entry in the eventEntries array and extracts the event information from the stream. For each entry, the StoreFreeBusyEntry() is called to do something with the extracted information. In my library, I put them into a global list of events.

You'll have to call this method for each of the three pairs of properties. Note that not all properties are always set. If no appointment with a free/busy status of busy occurs during the published period, the two corresponding properties would be null.

This method will work with Exchange 2000 and 2003. It will also work with Exchange 2007 if you have the public folder tree installed, which is not longer mandatory.

Getting the availability by using OWA

When you create a meeting with OWA, it will display the availability information of the attendees like Outlook does. To get the information, it uses a special OWA command which returns the free/busy information as an XML stream.

Just issue this request to the server and you'll get your data:

    1 GET /public/?Cmd=freebusy&start=<startDate>&end=<endDate>&interval=<interval>&u=SMTP:<userEmail>

    2 HTTP/1.1

    3 Translate: t

    4 User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.1.4322)

    5 Host: <Server>

The <startDate> and <endDate> must be replaced with the date range you are requesting, in the format yyyy-MM-ddTHH :mm:sszzz. An example would be 2004-01-01T14:00:00+02:00 (January first, 2004, two PM with a time-zone of two hours).
<interval> specifies the interval in minutes. Minimum is 5.
Finally, the <userEmail> must be replaced with the SMTP email address of the user for which the free/busy information is requested. If you want to request the free/busy data for multiple persons for the same date range, you can add more &u=SMTP:<Emailaddress>. However, the maximum length for the GET command is 1024 bytes.
The following example would request the free/busy data for John Doe (jdoe@contoso.com) for the 25 July 2004 from two PM to four PM CET (Central European Time, GMT+01:00) with an interval of 30 minutes:

    1 GET /public/?cmd=freebusy&start=2004-07-25T14:00:00+01:00&end=2004-07-25T16:00:00+01:00&interval=30&u=SMTP:alice@contoso.com

The response will look like this:

    1 <a:responsexmlns:a="WM">

    2   <a:recipients>

    3     <a:item>

    4       <a:displayname>Alice</a:displayname>

    5       <a:emailtype="SMTP">alice@contoso.com</a:email>

    6       <a:type>1</a:type>

    7       <a:fbdata>00120</a:fbdata>

    8     </a:item>

    9   </a:recipients>

   10 </a:response>

Now, the interesting information is in the <a:fbdata> node. This node contains the requested data. Each digit represents one interval. The meaning is as follows:

  • 0 equals CdoFree
  • 1 equals CdoTentative
  • 2 equals CdoBusy
  • 3 equals CdoOutOfOffice

It is important that you send that User-Agent string along. If you don't pretend to be an Internet Explorer 6 or later, you won't get an XML response because OWA thinks you are using a down-level browser which don't understands XML.

This method will also not work with Exchange 2007, as most of the OWA commands have been removed from Exchange.


Technorati:

Posted by Henning Krause on Friday, December 31, 2004 12:00 AM, last modified on Friday, November 26, 2010 8:57 AM
Permalink | Post RSSRSS comment feed

Retrieve all MAPI Properties of an Exchange Item

Solution

The Web Storage System of the Exchange system allows one to access the various properties of any item (e.g. a mail or appointment) via WebDAV.

To retrieve a list of all available properties, the WebDAV Propfind method is used, as displayed below:

    1 PROPFIND /public/container/ HTTP/1.1

    2 Host: www.contoso.com

    3 Depth: 0

    4 Content-type: text/xml;

    5 Content-Length: XXXXX

    6 

    7 <?xmlversion="1.0" ?>

    8 <D:propfindxmlns:D="DAV:">

    9   <D:allprop />

   10 </D:propfind>

However, this request will only return the named properties, such as the displayname, etc. The MAPI-Properties are not included in the list. Instead, the following request should be used, which uses the undocumented http://schemas.microsoft.com/exchange/allprop tag:

    1 PROPFIND /public/container/ HTTP/1.1

    2 Host: www.contoso.com

    3 Depth: 0

    4 Content-type: text/xml;

    5 Content-Length: XXXXX

    6 

    7 <?xmlversion="1.0" ?>

    8 <D:propfindxmlns:D="DAV:"xmlns:E="http://schemas.microsoft.com/exchange/">

    9   <D:allprop>

   10     <E:allprop />

   11   </D:allprop>

   12 </D:propfind>


Technorati:

Posted by Henning Krause on Friday, December 31, 2004 12:00 AM, last modified on Tuesday, April 11, 2006 12:00 PM
Permalink | Post RSSRSS comment feed

Access the Exchange store via WebDAV with Form-Based-Authentication turned on [Updated]

When a HTTP or WebDAV request is issued agains an Exchange 2003 or 2007 public folder or mailbox folder, the server returns a HTTP/1.1 440 Login Timeout error if Form Based authentication is enabled.

To work around this situation, you must do a logon to Outlook Web Access before performing any further requests. To do this, perform the following request against the exchange server:

    1 POST /exchweb/bin/auth/owaauth.dll HTTP/1.0

    2 Content-Type: application/x-www-form-urlencoded

    3 Connection: Keep-Alive

    4 User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.1.4322)

    5 Host: <Server>

    6 Content-Length: XXX

    7 

    8 destination=<destination-url>&username=<domain\\password>&password=<password>

Note: The url above is used with Exchange 2003. If want to authenticate against Exchange 2007 use this url:

    1 POST owa/auth/owaauth.dll

The Exchange 2003 url is still there but it won't work.

Replace the<destination-url> with the url of the uri where you want to do your request. The<domain\username> and the <password> fields must be replaced with the credential you want to use during your session.

When the login was successful, you will receive two cookies that you must pass along with all further requests. If no cookies are returned, the logon was not successful.

C# Example

Below is a sample code written in C#:

    1 privateHttpWebRequest GetRequestObject(string uri, string method)

    2 {

    3     HttpWebRequest webrequest;

    4     string[] credentials;

    5     CredentialCache cache;

    6     Uri _uri;

    7 

    8     // Initiate a new WebRequest to the given URI.

    9     _uri = newUri(uri);

   10     webrequest = (HttpWebRequest)System.Net.WebRequest.Create(_uri);

   11 

   12     webrequest.CookieContainer = newCookieContainer();

   13     webrequest.Method = method;

   14 

   15     return webrequest;

   16 }

   17 

   18 privateCookieCollection DoFormbasedAuthentication(string uri, NetworkCredential credential)

   19 {

   20     string server;

   21     HttpWebRequest request;

   22     HttpWebResponse response;

   23     byte[] body;

   24     Stream stream;

   25     string result;

   26 

   27     try

   28     {

   29         // Get the server portion of the requested uri and appen the authentication dll from Exchange

   30         server = uri.Substring(0, uri.IndexOf("/", 8)) + "/exchweb/bin/auth/owaauth.dll";

   31 

   32         request = GetRequestObject(server, "POST");

   33         request.CookieContainer = newCookieContainer();

   34         request.ContentType = "application/x-www-form-urlencoded";

   35 

   36         // Prepare the body of the request

   37         body = Encoding.UTF8.GetBytes(string.Format("destination={0}&username={1}\\{2}&password={3}",

   38             uri, credential.Domain, credential.UserName, credential.Password));

   39 

   40         request.ContentLength = body.Length;

   41 

   42         // Send the request to the server

   43         stream = request.GetRequestStream();

   44         stream.Write(body, 0, body.Length);

   45         stream.Close();

   46 

   47         // Get the response

   48         response = (HttpWebResponse)request.GetResponse();

   49 

   50         // Check if the login was successful

   51         if (response.Cookies.Count < 2) throw

   52             newAuthenticationException("Failed to login to OWA. Be sure your domain and password are correct.");

   53 

   54         return response.Cookies;

   55     }

   56     catch (AuthenticationException ex)

   57     {

   58         throw;

   59     }

   60     catch (Exception ex)

   61     {

   62         thrownewException("Failed to login to OWA. The following error occured: " + ex.Message, ex);

   63     }

   64 }

JavaScript Example

The same approach is possible with a small javascript.

Be sure to read this arcticle about the XmlHttp object: Permission Denied Error with XMLHTTP object

First, the main function for the example:

    1 var strCookies;

    2 var strRequest;

    3 var objXmlResult;

    4 

    5 try

    6 {

    7     strCookies = doFBALogin("https://exchangeserver/public", "exchangeserver", "domain\\username ", "password");

    8 

    9     strRequest = "<D:searchrequest xmlns:D = \"DAV:\" ><D:sql>SELECT \"DAV:displayname\" FROM \"https://exchangeserver/public\" WHERE \"DAV:ishidden\" = false</D:sql></D:searchrequest>";

   10 

   11     objXmlResult = Search("https://exchangeserver/public", strRequest, strCookies);

   12 

   13     // Do something with the XML object

   14 }

   15 catch (e)

   16 {

   17     // Handle the error!

   18 }

Now, do the actual work:

    1 function Search(strUrl, strQuery, strCookies)

    2 {

    3     // Send the request to the server

    4     var xmlHTTP = new ActiveXObject("Microsoft.xmlhttp");

    5     var i;

    6 

    7     xmlHTTP.open("SEARCH", strUrl, false);

    8     xmlHTTP.setRequestHeader("Content-type:", "text/xml");

    9 

   10     // Add the cookies to the request. According to Microsoft article Q234486, the cookie has to be set two times.

   11     xmlHTTP.setRequestHeader("Cookies", "Necessary according to Q234486");

   12     xmlHTTP.setRequestHeader("Cookies", strCookies);

   13     xmlHTTP.send(strQuery);

   14 

   15     return xmlHTTP.responseXML;

   16 }

   17 

   18 

   19 function doFBALogin(strDestination, strServer, strUsername, strPassword)

   20 {

   21     var xmlHTTP = new ActiveXObject("Microsoft.xmlhttp");

   22     var strUrl

   23         var strContent;

   24     var arrHeader;

   25     var strCookies;

   26     var intCookies;

   27     var i;

   28 

   29     // Prepare the URL for FBA login

   30     strUrl = "https://" + strServer + "/exchweb/bin/auth/owaauth.dll";

   31 

   32     xmlHTTP.open("POST", strUrl, false);

   33 

   34     xmlHTTP.setRequestHeader("Content-type:", "application/x-www-form-urlencoded");

   35 

   36     // Generate the body for FBA login

   37     strContent = "destination=" + strDestination + "&username=" + strUsername + "&password=" + strPassword;

   38     xmlHTTP.send(strContent);

   39 

   40     // Get all response headers

   41     arrHeader = xmlHTTP.getAllResponseHeaders().split("

   42         strCookies = "";

   43 

   44     intCookies = 0;

   45 

   46     // Iterate through the collection of returned headers

   47     for (i = 0; i<arrHeader.length; i++)

   48     {

   49         // If the entry is a cookies, extract the name/value

   50         if (arrHeader[i].indexOf("Set-Cookie") != -1)

   51         {

   52             strCookies += arrHeader[i].substr(12) + "; ";

   53             intCookies++;

   54         }

   55     }

   56 

   57     // Check if two cookies have been returned. Otherwise throw an exception

   58     if (intCookies < 2) throw"Could not log in to OWA!";

   59     return strCookies;

   60 }


Technorati:

Posted by Henning Krause on Friday, December 31, 2004 12:00 AM, last modified on Saturday, November 27, 2010 5:06 PM
Permalink | Post RSSRSS comment feed

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