Directory Service Interfaces
Author: Deepak Shenoy
ADSI in Delphi
(Download the code for this article at http://www.agnisoft.com/downloads
Microsoft has released Active Directory Service Interfaces
(ADSI) for Windows 9x, NT and Windows 2000, and now it's inbuilt
in Windows 2000. The Windows 2000 application specification
states that you must use Active Directory when you can, so
now you're thinking: How are YOUR applications going to adapt?
What changes do you need to make? How are you going to use
Delphi effectively to make these changes? This is what I intend
to address in this presentation.
Let me first introduce a “directory service”.
A directory service is like a telephone directory: if you
have a person’s name, you can find his/her phone number.
A directory service keeps track of “resources”,
which could be anything – a file system is a directory
that keeps track of files and folders, an email server is
a directory service that indexes users, user groups etc. There
are many directory services already in place: we already have
file systems and email servers. What’s so different?
Traditionally, you’d have to use different API’s
(Application Programming Interfaces) to access different directory
services – which :
- Limit you to the vendor’s directory service. For
example, if you wrote an application that extracted email
information from Microsoft Mail and used the Mail Application
Programmers Interface (MAPI), you would find it difficult
to move to a different vendor.
- Increase development time for your applications because
you need to learn more API’s to get your application
There is a need to have a common model that every directory
service would support – similar to the ODBC programming
model that all (or most) database vendors now support. A model
that will support a hierarchy of resources, like folders and
files, and be simple to use. Active Directory provides this
model. To access an Active Directory, you will use Active
Directory Service Interfaces(ADSI).
Where is Active Directory used?
"Active Directory" encapsulates all directory services
- a list of printers on a network, a set of services on an
NT server etc. Directory services are very useful in an enterprise
where one might know what he wants, may not know what that
resource is named. (like, "give me a list of printers
in the 2nd floor").
Any directory service can choose to publish itself as an Active
Directory, so that a common querying mechanism can be used.
At this point the following products support Active Directory:
- Microsoft Exchange Server: Allows queries for email ids,
names and a number of other attributes of people or groups
across an enterprise.
- Microsoft Site Server: Stores the list of users as an
- Microsoft Windows NT: This gives you uniform access to
users (earlier through User Manager for Domains), services
(earlier through service manager) and network resources
(computers, printers etc.)
Active Directory also provides bridges to access other similar
directory services, like LDAP, NDS (Novell Directory Services)
Active Directory is also an integral part of Windows 2000.
In a large organization, Windows NT server has been used as
a Primary or Backup Domain Controller, but it was difficult
to integrate many such controllers to be able to provide security
and privileges across the organization. Active Directory makes
it simpler by allowing you to structure your organization
into units which seamlessly (or so they say) integrate with
each other. Which also means lesser problems when you expand,
add more servers, more users etc.
To know more about how you can use Active Directory effectively
in your organization, please visit: http://www.microsoft.com/windows2000/library/technologies/activedirectory/default.asp
Here's a list of places ADSI would be best used in:
- User/Groups:Adding/Deleting users , Finding out if a user
is a member of a group.
- Changing a user's password
- Starting and stopping services.
- Getting printer information (jobs in print queue etc.)
- Adding/Deleting web or FTP sites from Microsoft Internet
- Getting user attributes from Microsoft Exchange Server,
like email ids, Addresses, web directories etc.
These are just a few applications, and I'm sure there will
be more as time goes on.
Prerequisites – What do you need to know?
This presentation assumes readers know COM (Component Object
Model) and a working knowledge of using COM in Delphi.
What is ADSI?
Directory Services and Namespaces
There could be many objects within a namespace (users in an
email server, files in a file system) which need to be uniquely
identifiable by name. But you might have a user in the email
server with the same name as a file in the file system:and
they have no idea about each others existance. To maintain
uniqueness, object names are prefixed with the name of the
Directory Service they belong to. This name identifies a "namespace",
with a namespace identifier like "WinNT:", "LDAP:"
etc. (The ":" means that it's a namespace, and therefore,
a directory service)
Object names are prefixed by the namespace identifier, and
Note:This has analogies in the Internet, where you'd have
Web pages identified by "http://..." and files on
FTP servers by "ftp://..." etc.
ADSI in a nutshell
As Microsoft puts it, “ADSI is a set of COM programming
interfaces that will make it easy for customers and Independent
Software Vendors (ISVs) to build applications that register
with, access, and manage multiple directory services with
a single set of well-defined interfaces” (Ref.1) Active
Directory Service Interfaces abstract the capabilities of
individual directory services: which means you, as a developer,
could access a file system the same way you access an email
server or any other service that supports ADSI.
ADSI objects are Component Object Model (COM) Objects.
You needn’t learn a new API for ADSI programming. You
can develop for ADSI using simple Automation concepts. All
ADSI objects support (and must support) IDispatch, so you
can choose to use Late Binding or Early Binding. (Delphi supports
both, quite magnificently) Here’s a small example of
how to create a user on Windows NT 4.0 using Early Binding.
Container : IADsContainer;
NewObject : IADs;
User : IADsUser;
hr : HREsult;
// COM must be initialized
// Bind to the container.
hr := ADsGetObject('WinNT://YOURDOMAIN',IADsContainer,Container);
if Failed(hr) then exit;
// Create the new Active Directory Service Interfaces
NewObject := Container.Create('User','ActiveDirectoryUser')
// Get the IADsUser interface from the user object.
// Set the password.
// Complete the operation to create the object.
Note: The ADsGetObject function is declared in AdsHlp.pas
that is included with this article. The interface definitions
are imported from ActiveDS.tlb in the WinNT/System32 folder.
You will need to install ADSI 2.5 from http://www.microsoft.com/ntserver/nts/downloads/other/ADSI25/default.asp.
I will explain the architecture in more detail in the Architecture
section. Active Directory Service Interfaces can be used easily
in Delphi, though all the examples and support in the http://www.microsoft.com/adsi
are in Visual Basic or C++ code. With this paper, you will
find all the translations of the header files, some samples
and some new Delphi translations.
A Directory Service Provider is a module
that gives a user access to a certain directory service. For
instance, we were able to add a user to the Windows NT user
manager because Microsoft has written an ADSI provider for
the user manager.
This doesn't sound very great, you might say. What's the
difference between doing this and using native Windows NT
calls to add users? First, this is COM based
so if Microsoft decides to change the entire implementation
of the user architecture, your applications are safe, because
there will still be an ADSI provider supporting the same interfaces.
(Note: this kind of thing might not be about to happen.)
Second, consider your gains in extensibility.
You can now add a user on a Netware Server using very similar
code, except you'd have to use the ADSI provider for Netware.
As we will see later, the code will follow a similar pattern
for doing different activities: creating a web site, adding
an email user etc.
Take a look at the next section for more benefits.
Why should you use ADSI?
||Any directory provider can implement an Active Directory
Service Interfaces provider; users can easily move to
a different provider of the same service with a minimum
||ADSI supports both Authentication and Authorization
programming model - You can give even role based security
for your applications.
|Simple Programming Model
||As you will see, the COM model is very easy to understand.
The model remains standard for all providers: there's
no need to understand vendor specific APIs.
||Any Automation Controller (for example, Delphi, Visual
Basic, C/C++ and others) can be used to develop directory
service applications. Administrators and developers can
use the tools they already know.
||ISVs and sophisticated end users can develop serious
applications using the same Active Directory Service Interfaces
models that are used for simple scripted administrative
||Directory providers, ISVs, and end users can extend
Active Directory Service Interfaces with new objects and
functions to add value or meet unique needs.
The Architecture of ADSI
This section deals with the core concepts of ADSI. There
- a description of the COM object model and
- an introduction to the various interfaces
Most directory services are hierarchical in nature and thus
lend themselves to a hierarchical object model. ADSI abstracts
this concept by defining Container Interfaces
and leaf interfaces. A container object (that
implements a Container Interface) will contain zero or more
ADSI objects - which could be other containers or leaf objects.
As I've said before, access to directory services is through
ADSI providers - so each provider is identified by a unique
namespace identifier. A provider implements
a Namespace object which is a COM Object
that is a one-stop-shop: you can access any object in the
namespace through this object.
These namespace objects are stored in an Active
Directory Namespace Container object which is identified
by the name "ADS:". (See Figure).
Each Namespace object is itself a container - it contains
the root nodes of the directory service objects. Every container
object and leaf object support a common set of methods - so
that users can interact with it uniformly. These common methods
are part of the IADs interface for all nodes, and IADsContainer
interface for container nodes.
These common methods do not expose all the functionalities
of a provider - simply because the domain of a "directory
service provider" is too large to be able to abstract
everything. To allow providers to extend functionality, ADSI
supplies a schema model that I will describe later.
COM Object Model
There are many ADSI Interfaces that have been defined for
specific purposes. Here is a list:
Fundamental interface required on all ADSI objects. You
use this interface to get and set properties.
||Object Lifetime Management and
Fundamental interface to be supported on all ADSI container
objects. Manages object creation, deletion, copying and
moving, binding, and
||Object Property Management
Manages an objects properties in the property cache. You
can use this to get and set properties. (Alternatively
||Direct Object Access
Low-level object access for clients that do not want to
use Automation (Early binding) . Really useful when you
want to get/set many properties in one call, instead of
using multiple IADs.Get or IADs.Set calls..
||COM Object Management
Required on all COM objects.
||Type Library Information and Method
Required on all Automation objects. ADSI Objects must
support this interface.
As an analogy, consider any Delphi application. There's a
global object called Screen that contains information about
all the forms created(Screen.Forms). Each form has a set of
standard properties (Name, Tag for example).
Each form could contain many components in it, each of which
has the standard properties (Name, Tag) at least. So a Form
is analogous to a "container" in ADSI. In congruence,
Screen is also a container.
Taking this further, a "Form" is a namespace -
Any object in this namespace is derived from TForm and thus
supports everything that TForm does. One more thing: ADSI
requires that any object has a unique name: if we assume that
all forms had unique names (which they usually will), then
I could identify any object (Form ) by using its name. If
I had to register this globally - I would prefix the name
with "Form:". (Form:MainForm for instance). So "Form:"
is handled by the Screen object which figures out where "Mainform"
is. (You could have other namespaces handled by other containers)
To translate this to ADSI, we would have to have TForm support
- IADs (All ADSI objects)
- IADsContainer (All ADSI Container objects)
All components (hosted on TForms) should support
TScreen should support
- IADs (All ADSI objects)
- IADsContainer (All ADSI Container objects)
- IADsOpenDSObject (All ADSI Namespace container objects)
(This is only an example to demonstrate the ADSI object model.
It might not be the best way to have your application support
If this was implemented, you can create any form by calling
ADSI, there are libraries present so that you can open an
object directly, given its path. You don't need to create
the namespace container in order to create an object. A few
functions here are:
- ADsGetObject(Path, Interfacename, Object)
- returns an instance of the object who's name is Path.
To avoid round trips with QueryInterface, (See Effective
COM by Don Box et. al) the second parameter is an Interface
ID that determines which interface will be returned.
- ADsOpenObject - Similar to ADsGetObject,
except you can log on using a different identity to get
the parameters. The security model in ADSI works with NT
User security, so you can have role based security work
- ADsBuildEnumerator, ADsEnumerateNext, ADsFreeEnumerator
- Helper functions for the IEnumVARIANT
interface, which allows Visual Basic developers to use the
for each syntax.
Using ADSI in Delphi
In this section I will talk about how you can use ADSI in
Delphi. I've used a number of samples from the Windows Platform
SDK as a reference. If you take a look at this, most examples
you will find will use late binding : Visual Basic code or
VBScript/ASP code. I'll use a combination of Late and Early
Binding - Delphi can use both - to demonstrate various features.
Binding to an ADSI object and enumeration
A directory may contain a large number of items. Each item
must be uniquely identifiable. ADsPath is a property available
on any ADSI object which uniquely identifies it, both on a
particular provider and across providers. Each provider corresponds
to a namespace: Consider a few ADSI providers that ship with
- WinNT: - The Windows NT provider for Windows NT 4.0 (and
Windows 2000) domain controllers.
- LDAP: - For communication with LDAP servers like Exchange
- NDS: - Provider for Netware Directory Services
(The initial elements of the ADsPath string
are the namespace identifier (progID) of the ADSI provider,
followed by "//", followed by whatever syntax is
dictated by the provider namespace)
With this information at hand, lets consider a few ADsPaths
that could identify objects.
- WinNT://MyDomain/Adminstrator - identifies the Administrator
user on MyDomain, a Windows NT domain.
- LDAP://EXCHSVR/CN=info,DC=AGNISOFT,DC=COM - the firstname.lastname@example.org
account on EXCHSVR
To find all the providers installed on your machine, you
can enumerate the namespaces in "ADs:". Here is
some Visual Basic code that does it:
For Each provider
Since we do not have the "For Each" syntax in Delphi,
I will use the helper functions provided by ADSI to enumerate
e : IEnumVariant;
hr, i : integer;
varArr : OleVariant;
lNumElements : ULONG;
item : IADs;
hr := ADsGetObject( 'ADs:', IID_IADsContainer,
x); // bind to the object
hr := ADsBuildEnumerator(x,e); // start enumerating
while SUCCEEDED(hr) do
// get the next contained object
hr := ADsEnumerateNext(e,1,varArr,lNumElements);
then // are we done?
//varArr contains an IDispatch pointer to
the contained object.
if e<>nil then
hr := ADsFreeEnumerator(e);
The binding code is in ADsGetObject( 'ADs:', IID_IADsContainer,
This code is a bit complex and troublesome to do everytime.
I've added a funtion that allows easy enumeration.
ADsEnumerateObjects(Container : IADsContainer;
Func : TADsEnumCallback);
e : IEnumVARIANT;
varArr : OleVariant;
lNumElements : ULong;
obj : IADs;
hr : integer;
hr := ADsBuildEnumerator(Container,e);
hr := ADsEnumerateNext(e,1,
if (lNumElements=0) then
varArr := NULL;
// do not call ADsFreeEnumerator(e); since e will
be released by Delphi
You can use this in a Delphi form like so:
procedure TForm1.Callback(Obj: IADs);
s : string;
You may also use ADsOpenObject for binding,
the only difference being that you can choose to bind as a
different user instead.
hr := ADsOpenObject('IIS://localhost',
ADS_SECURE_AUTHENTICATION , IADs, obj );
ADSI caches connections to servers - on all objects not yet
destroyed. So, if you will bind to many objects, you can create
a dummy object that binds to some object on the server, perform
your set of operations using a different set of objects and
finally, released the dummy object.
Once Binding is established, you will want to get or set
properties of the object. The steps involved are:
a) Bind to the object
b) Set properties
c) Call SetInfo
d) Release the object
(b) and (c) are required because ADSI caches the properties
on the client side and updates the server only when you call
SetInfo. (Saves a lot of network traffic this way).
Here's an example.
// Late Binding
var obj : Variant;
obj := ADsHlp.GetObject('WinNT://AGNISOFT/Deepak');
// bind to the object
obj.Put( 'FullName', 'Deepak'); // set properties
obj := NULL; // release the object
Note: In 'WinNT://AGNISOFT/Deepak', AGNISOFT
is the Active Directory Domain and Deepak
is the user name. FullName is a property
of every user.(earlier accessible using User Manager for Domains)
Properties could also have multiple values,
like additional phone numbers. You would use the PutEx
and GetEx functions to set or access these
// assume there was '111-1111' and '222-2222'
obj.SetInfo; //now there will be '111-1111',
//'222-2222' and '333-3333'
obj.SetInfo; //now there will be only '333-3333'
obj.SetInfo; //now there will be '888-8888'
obj.SetInfo;//now there will be nothing
Searching Active Directory
1. Using ADO
Active Data Objects is Microsoft's latest
Database Access solution. It works with OLE DB providers,
and ADSI comes with a provider named "ADsDSOObject".
In Delphi, all you need to do is to drop a TADOConnection
and set its provider to "ADsDSOObject".
Then, drop a TADOQuery, connect it to the
TADOConnection and query ADSI - this is a method for simple
Here's how I've got all the users, their user names and their
last names from my machine's Active Directory.
The SQL syntax is :
SELECT [ALL] select-list FROM 'ADsPath' [WHERE search-condition]
[ORDER BY sort-list]
The Query I have used in the form is
SELECT AdsPath, CN, SN
FROM 'LDAP://DC=AGNISOFT,DC=COM' WHERE objectClass='user'
ORDER BY sn
The ADSI provider is read-only at this time. Microsoft plans
to ship a read-write provider in future. To modify data, you
1. Get the ADsPath from the Query
2. Use ADsGetObject to bind to the ADsPath
3. Get/Set properties
At this point, only the LDAP and the NDS providers support
searching using ADO. You cannot use ADO to search for user
in a Windows NT (Or 2000) domain.
2. Using COM Interfaces - IDirectorySearch
If you don't want to use ADO, you can use the IDirectorySearch
Interface. The steps involved are:
1. Bind to the object
2. Call QueryInterface on the object for
3. Call IDirectorySearch.ExecuteSearch, passing
the search query and get a search handle
4. Call IDirectorySearch.GetNextRow and for
each row, call IDirectorySearch.GetColumn(columnName)
to get the columns.
// bind to the object
AdsGetObject(edtObjectPath.Text, IDirectorySearch, search);
// set parameters
opt.dwSearchPref := ADS_SEARCHPREF_SEARCH_SCOPE;
opt.vValue.dwType := ADSTYPE_INTEGER;
opt.vValue.Integer := ADS_SCOPE_SUBTREE;
p := StringToOleStr('Name');
// get records
hr := search.GetNextRow(ptrResult);
while (hr <> S_ADS_NOMORE_ROWS)
hr := search.GetColumn(ptrResult, p,col);
if Succeeded(hr) then
Hr := search.GetNextRow(ptrResult);
finally // free memory
ADSI supports Authentication using a login name and a password.
If you use ADsGetObject then the user currently logged on
is used to authenticate the login. You can specify a user
name by using ADsOpenObject.
hr := ADsOpenObject('IIS://localhost', 'testuser','pwd',
ADS_SECURE_AUTHENTICATION , IADs, obj );
ADS_SECURE_AUTHENTICATION specifies that Kerberos or NTLM
is used to authenticate the password. You can even specify
password encryption (if your server supports it).
Role based security to properties - You
might need to secure ADSI itself - control who can read/write
certain properties etc. The properties of ADSI are controlled
by Access Control Entries (ACEs) in Windows
2000. You can create an ACE and add it to the Discretionary
Access Control List (DACL) of the SecurityDescriptor
of an object. ( Use obj.Get('ntSecurityDescriptor')
to get the security descriptor) The ACE can allow or
deny access to one or all properties of an object. The security
is inherited - so if you change the access control list of
a container, all its descendants will inherit it.
In the Windows 2000 application specification, it is recommended
that you use ADSI in your applications in specific instances.
1. If your application will use a known directory service,
you must use ADSI to get or set properties in the Active Directory
service. For instance, if you need to add or modify a user,
you must use ADSI to do so.
2. In a client-server or multi-tier application, you are suggested
to publish server attributes in the Active Directory. Which
means that the client applications should be able to use ADSI
to get all the information about the server application.
To do this, you need to be able to extend ADSI. I'll talk
about two ways you could extend ADSI. But first, lets see
how the ADSI Schema works.
The predefined ADSI objects have very few properties: these
may not be enough for a particular provider. So, ADSI allows
your provider to extend the basic interface by adding properties
to objects within your namespace. The schema objects are special
ADSI objects : They allow you to :
- Browse the definition of objects
- Extend the definition of objects
The schema object contains definitions of :
- What kind of objects will be present (eg. in WinNT:,
you have Users, Groups, Services etc),
- What properties these objects will have (For Users in
WinNT:, FullName, Description etc. are properties) and which
properties are mandatory and which are optional.
- The syntax of these properties (FullName is a String,
UserFlags is an integer etc.)
(1) above is represented by a Class Object.
(2) by a Property Object
(3) by a Syntax object
These three are placed in the Active Directory as follows:
To browse the schema of an object, you must:
1. Get the ADsPath of the schema - The IADs.Get_Schema
call does the job
2. create the schema object and browse it.
Here's an example that gets the list of Mandatory and optional
properties of all Users in the LDAP namespace.
s : WideString;
cls : IADsClass;
cont : IADsContainer;
i : integer;
IADs, obj );
s := obj.Get_Schema;
AdsGetObject(s, IADsClass, cls );
for i := VarArrayLowBound(cls.MandatoryProperties,1)
s := cls.MandatoryProperties[i];
ShowMessage('MANDATORY:' + s);
for i := VarArrayLowBound(cls.OptionalProperties,1)
s := cls.OptionalProperties[i];
ShowMessage('Optional:' + s);
Here's a small application screenshot that does this:
ISVs or corporate developers can extend the object semantics
by adding interfaces to the existing ADSI interfaces. ADSI
combines the COM Aggregation model and directory technology
to bring a powerful extension model. You can thus support
more functions than provided by any of the standard ADSI interfaces.
Anyone that needs to extend ADSI can do so by writing an
extension - which is nothing but a COM object. A backup vendor,
for instance, could write an extension that supports "Backup"
and "Restore" functions that extend the IADsComputer
interface - This would be useful for administrator
to write scripts for automatically backing up computers to
a tape drive.
To provide access to a totally new namespace, you must implement
an ADSI provider. An ADSI provider could just be
a single COM DLL containing many COM classes that you implement.
Any provider needs to support:
1. A top level namespace object that supports IADsOpenDSObject
and IParseDisplayName. (And of course, IADs)
YOu will need to parse any AdsPath given and detect syntax
errors, if any. 2. A few other interfaces-IADsPropertyList,
IDirectoryObject. (I won't go into detail
on these interfaces)
3. IDispatch - this is very important because
you must support late binding.
4. IADsContainer on all object containers.
5. IEnumVariant on all enumeratable objects
(containers, collections).- You need to support those Visual
Basic and VBScript users using for each.
6. A schema class container object with appropriate
class, syntax and property objects for your namespace.
This seems like quite a big task, and though there is a sample
in the Windows 2000 platform SDK, it isn't quite easy. I have
written a sample in order to make it easier for you to begin
- it's available along with this article.
Windows 2000 supports Active Directory natively-there
are four providers (WinNT:, LDAP:, IIS:, NDS:) already present
in the Windows 2000 server install. At the time of writing
this paper, the other applications supporting ADSI are Microsoft
Exchange Server 5.5 and Microsoft Site Server 3.0. Many third-party
products are coming out with ADSI support.
There will be a lot of focus on ADSI in the future - you will
see a number of components that will give you easier access
to ADSI objects. You can begin to perform administrative tasks
using ADSI, and identify areas of your applications where
it will be better to use ADSI rather than a native Directory
Active Directory is not something to be ignored because it
forms a part of the most recent operating system that you
will support - Windows 2000.
1. ADSI White Papers - part of the Windows Platform SDK
2. Windows 2000 Developers Readiness Kit.
3. Newsgroups : (at msnews.microsoft.com)