Ian Boyd
Ian Boyd

Reputation: 256581

How to query ActiveDirectory from native Win32 (i.e. not .NET) code

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:


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:

Upvotes: 0

Views: 6032

Answers (3)

marc_s
marc_s

Reputation: 754220

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).

Upvotes: 1

Ian Boyd
Ian Boyd

Reputation: 256581

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.

Upvotes: 1

JPBlanc
JPBlanc

Reputation: 72612

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.

Upvotes: 0

Related Questions