0

If i wanted to pull information about a user from Active Directory in .NET, i could use the DirectorySearcher class.

For example, to find the e-mail address of a user i would call:

public String GetUserEmailAddress(String accountName)
{
    DirectorySearcher searcher = new DirectorySearcher();
    searcher.Filter = String.Format("(&(objectCategory=user)(sAMAccountName={0}))", accountName);
    searcher.PropertiesToLoad.Add("mail");

    SearchResult searchResult = searcher.FindOne();

    return searchResult.Properties["mail"][0];
}

What is the native way to query the Active Directory?

Note:

  • no domain name is specified
  • no server name is specified

We can even extend our function to allow querying of any generic arbitrary information:

public Object GetUserAttribute(String accountName, String propertyName)
{
    DirectorySearcher searcher = new DirectorySearcher();
    searcher.Filter = String.Format("(&(objectCategory=user)(sAMAccountName={0}))", accountName);
    searcher.PropertiesToLoad.Add(propertyName);

    SearchResult searchResult = searcher.FindOne();

    return searchResult.Properties[propertyName][0];
}

AD has all kinds of information that you can pass as propertyName. For example:

  • displayName (Display-Name): The display name for an object. This is usually the combination of the users first name, middle initial, and last name. (e.g. Ian A. Boyd)
  • mail (E-mail-Addresses): The list of email addresses for a contact. (e.g. ianboyd@stackoverflow.com)
  • cn (Common-Name): The name that represents an object. Used to perform searches.
  • name (RDN): The Relative Distinguished Name of an object. (e.g. Ian Boyd)
  • sn (Surname): This attribute contains the family or last name for a user.
  • givenName (Given-Name): Contains the given name (first name) of the user.
  • sAMAccountName (SAM-Account-Name): The logon name used to support clients and servers running older versions of the operating system, such as Windows NT 4.0, Windows 95, Windows 98, and LAN Manager. This attribute must be less than 20 characters to support older clients.
  • objectGUID (Object-Guid): The unique identifier for an object. (e.g. {3BF66482-3561-49a8-84A6-771C70532F25})
  • employeeID (Employee-ID): The ID of an employee. /// "description" (Description): Contains the description to display for an object. This value is treated as single-valued by the system.
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 2
    There's the [COM `IADs` interface](http://msdn.microsoft.com/en-us/library/windows/desktop/aa705950%28v=vs.85%29.aspx) which you can call from pretty much any COM capable language..... – marc_s Dec 19 '11 at 16:57
  • Isn't Active Directory an LDAP implementation? If so, shouldn't any LDAP library that supports secure connections also work? (Not an answer because it's not *native*.) – Charles Dec 19 '11 at 17:39
  • @Charles i know i can use ADO to connect to an LDAP server using the LDAP provider (similar to how you can use ADO to connect to an SQL Server using the SQL Provider). But that LDAP OLEDB provider requires knowing the name of an LDAP server. i'm interesting in "querying Active Directory", rather than "querying an AD server" (since i don't know the name of my local AD server, and since there can be many). – Ian Boyd Dec 19 '11 at 17:41
  • @marc_s Figure out example syntax (i.e. ProgID/Clsids, methods) to query AD and you'll get an accepted answer. (As it stands right now even knowing about the interfaces i still have no idea how to query AD) – Ian Boyd Dec 19 '11 at 17:44
  • @IanBoyd: I can show you how it works in Delphi :-) – marc_s Dec 19 '11 at 17:57
  • @marc_s i'm *writing* it in Delphi! – Ian Boyd Dec 19 '11 at 18:57
  • Can you have a look [here](http://stackoverflow.com/a/6457140/608772) – JPBlanc Dec 19 '11 at 20:21
  • @marc_s That 12-part primer is quite short on information, and long on talking. i couldn't find any example of how to talk to active directory - but excruciatingly painfully unexplained examples of how to talk to an LDAP server. i don't *know* the name of any servers involved in Active Directory on the network. i downloaded your component, and the imported tlb. Useful enough, but i still don't know what to call of what to get what. The code can't be too long - the managed version to getting a user's e-mail address is 5 lines long. (In fairness, .NET's DirectorySearcher is also undocumented) – Ian Boyd Dec 20 '11 at 00:58

3 Answers3

1

A first step would be to check out the article series An ADSI primer on Windows IT Pro. It gives a fairly good overview of the basics of ADSI and the IADs interfaces and how to use them (from VBScript, I believe).

A second step in Delphi would be to import the Active_Ds type library - this should generate an ActiveDs_TLB.pas file that contains the basic types, interfaces, methods to deal with Active Directory from a native language, using ADSI.

To access the native functions, you need to use a so called function import for each of the functions you want - here the code for just one - ADsGetObject:

type
  TADsGetObject = function(aPathName: PWideChar; const aRIID: TGUID; out aObject): HResult; safecall;

var
  ADsGetObject : TADsGetObject = nil;

initialization
  hActiveDS := LoadLibrary(PChar('ActiveDS.dll')); // don't localize

  if (hActiveDS = 0) then
    raise Exception.Create(rc_CannotLoadActiveDS);

  LoadProcAddress(hActiveDS, 'ADsGetObject', @ADsGetObject);

Once you've created those functions from the external library, you can go about calling them - something like this:

var
  hr : HRESULT;
  oIADs : IADs;
  wsTemp : WideString;

begin
   wsTemp := 'LDAP://cn=IanBoyd,cn=Users,dc=YourCompany,dc=com';

   // try to bind to the ADSI object using the "sanitized" path
   hr := ADsGetObject(PWideChar(wsTemp), IID_IADs, oIADs);

   if Succeeded(hr) then begin
      // successful - now retrieve all properties into property cache
      try
        oIADs.GetInfo;
      except
         on EOleSysError do begin
           Exit;
         end;
      end;
 
      // get the object's GUID
      wsTemp := oIADs.GUID;

      // do more stuff here.....

Next, also see my ADSI Delphi Tips & Tricks page - so of the info is outdated, though (like the link to The Delphi Magazine's collection CD - that doesn't seem to be available anymore).

Searching ADSI with native code is quite involved - that would definitely go beyond the scope of such a posting. I did write a fairly extensive article on that - including sample code - which is available upon request from me (send me an e-mail to my address in my profile).

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • This answer looks promising, as a solution using only the ADS com objects. But what's the `path` syntax to find a user by account name? (e.g. i am trying `'LDAP://cn=ian,DC=avatopia,DC=com'` but *there is no such object on the server*. My account name (`ian`) can't be my Common Name (`Ian Boyd`) – Ian Boyd Dec 20 '11 at 18:31
  • @IanBoyd: get my [Delphi-based ADSI Browser](http://adsi.mvps.org/adsi/Delphi/adsibrowser.html)! And all your question will be answered :-) Most often, your user would be something like `LDAP://cn=ian,cn=Users,dc=avatopia,dc=com` - or if your users get created somewhere else other than the default `cn=Users` container - substitute that location instead – marc_s Dec 20 '11 at 18:35
  • i **did** get it! It looked like there was just a component that shows the basic `rootDSE` properties. Coming up with a way to search by `sAMAccountName` seems to be a secret. e.g. `LDAP://dc=avatopia,dc=com;(&(objectCategory=user)(sAMAccountName=iboyd))` doesn't work. Also, i don't *know* where users will be created; what's the syntax to search the entire `dc=avatopia,dc=com` subtree? All i will have is the user's username (aka `sAMAccountName`, login name, etc) – Ian Boyd Dec 20 '11 at 18:44
  • ...unless the ldap `path` can only be used to **bind**, and not **search**, in which case it's a lost cause. – Ian Boyd Dec 20 '11 at 18:49
  • @IanBoyd: if you want to search, you need two basic things: (1) an LDAP path as the **starting point** for your search (in terms of the LDAP tree), and (2) an **LDAP filter** which defines what you're searching for - but those are two distinct, separate items you need - you can't combine those into a single LDAP "command" or something... – marc_s Dec 20 '11 at 20:47
  • i posted the question of how to query active directory for a user, using the ActiveDs interfaces at: http://stackoverflow.com/questions/8591789/how-to-get-the-iads-interface-for-an-active-directory-user If i get an answer to that it can be back-copied here and used to complete your answer. – Ian Boyd Dec 21 '11 at 14:57
1

marc_s's deleted answer proved to be the most useful; but here's the answer to the question in pseudo-code:

public GetUserEmailAddress(String accountName): String;
{
   //Get the distinguished name of the current domain 
   String dn = GetDefaultDistinguishedName(); //e.g. "dc=stackoverflow,dc=com"

   //Construct the ldap table name  (e.g. "LDAP://dc=stackoverflow,dc=com")
   String ldapTableName := "LDAP://"+dc;

   //ADO connection string
   String connectionString := "Provider=ADsDSOObject;Mode=Read;Bind Flags=0;ADSI Flag=-2147483648";

   //The sql query to execute
   String sql := 
         "SELECT mail"+CRLF+
         "FROM "+QuotedStr(ldapTableName)+CRLF+
         "WHERE objectClass = "+QuotedStr("user")+CRLF+
         "AND sAMAccountName = "+QuotedStr(userName);

   ADOConnection conn := new ADOConnection(connectionString);
   try
      Recordset rs := conn.Execute(sql);
      try
         if (rs.Eof)
            return "";

         return rs["mail"].Value;
      finally
          rs.Free;
      end;
   finally 
      conn.Free;
   end;
}

The real secret is talking to "the domain", and not any particular server:

//get the distinguished name of the current domain
public GetDefaultDistinguishedName(): string;
{
   String path := "LDAP://rootDSE";

   IADs ads;
   ADsGetObject(PWideChar(path), IADs, out ads);

   //e.g. on the "stackoverflow.com" domain, returns "DC=stackoverflow,DC=com"
   return (String)ads.Get("defaultNamingContext"); 
}

Note: Any code is released into the public domain. No attribution required.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Strange, for you ADO ADSI is a 'native way' to access LDAP ? Using RootDSE means that somewhere a server is found, because RootDSE is nothing more than a LDAP search on an empty DN. – JPBlanc Dec 20 '11 at 15:29
  • @JPBlanc ADO is a a native API that can be accessed by assembly language developers, c developers, c++ developers, Delphi developers. You might be thinking of ADO.NET? i don't *know* that `RootDSE` is "*nothing more than an LDAP serch on an empty DN*". But if you can provide native code equivalent to my (managed) original question, you can get an accept. – Ian Boyd Dec 20 '11 at 16:04
  • Have a look to Lightweight Directory Access Protocol, you'll see that it's closer to assembly language than ADO stack. – JPBlanc Dec 20 '11 at 16:25
  • @JPBlanc i'm not after a purity test of "native-ness"; only after a way to query active directory (with the stipulation that it needs to be native code). i don't care *how* the API communicates with active directory, only that that it works. It would be useful, for completeness sake, if you can post your alternative code that uses LDAP. It can serve as an alternative to the ldap code i posted. – Ian Boyd Dec 20 '11 at 18:02
0

The native programming is LDAP you can use it in .NET with System.DirectoryServices.Protocols (S.DS.P).


Edited

If you are interested in how to interrogate active directory from native code, you may have a look to LDAP C-Binding API as discribed in RFC 1823 specifies, Microsoft support it, see MS Strategy for Lightweight Directory Access Protocol (LDAP). You'll find the using and reference manuals of the Microsoft API in Lightweight Directory Access Protocol.

You'll find here a way to find Active Directory on the network without providing any information but a correct DNS server. You can use domain DNS name in ldap_open function, doing this way you don't have to know a Domain server adress.

Community
  • 1
  • 1
JPBlanc
  • 70,406
  • 17
  • 130
  • 175
  • i think you misunderstood. i'm not interested in how to use LDAP from managed code, i'm interested in how to interrogate active directory from **native** code (which might involve ldap, or might involve some other technology) – Ian Boyd Dec 20 '11 at 00:46
  • Sorry, I misunderstood your question, I edit my answer to give you the native way to interrogate Active-Directory or another Directory from Microsoft box. – JPBlanc Dec 20 '11 at 04:54