# Easy Access to Active Directory Using ADSI

Andy Harjanto and Ajay Ramachandran
Microsoft Corporation

August 1999

Summary: Describes how to access Active Directory™ using Active Directory Service Interfaces (ADSI), included with the Microsoft® Windows® 2000 operating system. (20 printed pages).

#### Contents

Introduction
Installing Active Directory
Start Connecting to Active Directory
Fabrikam Corporation
Advanced Topics
Summary
For More Information

## Introduction

By now, you've probably heard about one of the coolest features in the Microsoft Windows 2000 operating system—Active Directory. If not, you might be asking why you should care, why it's so important, why it is such cool technology. Plenty of reasons—when you log on to a Windows 2000 domain, Active Directory is put into action; when you want to search for the closest color printer in your building, Active Directory is at your service. How about an address book lookup or a search for users who work in Building 40? No sweat. Worried about deploying your applications? Active Directory will locate the applications and components for you transparently. Can you use a smart card to log on to a Windows 2000 domain? You bet!

Still need more examples? How about this: Do you want to impress your boss with a cool looking report? With Active Directory, you should be able to do it in less than 15 minutes. How about joining data from Microsoft SQL Server™ and Active Directory? By now, you know the answer. Can it sing and dance? No, but hey, you can make this a feature request.

On top of these reasons to take notice, many other Microsoft technologies, such as Microsoft Message Queue Server (MSMQ), COM (Class Store), IP Sec, Group Policy Objects (GPO), and Exchange are integrated with Active Directory. Equally important are your future applications.

Okay, so what is Active Directory? It would take a white paper to explain that, but we'll try to tell you in a single paragraph. Active Directory is a special-purpose database—it is not a registry replacement, nor is it a general-purpose database. The directory is designed to handle a large number of read/search operations and a significantly smaller number of changes and updates. The data in the Active Directory is hierarchical, replicated, and extensible. Because it's replicated, you don't want to store dynamic data, such as Internet stock prices, CPU performance, and the like. If your data were machine-specific, you'd be better off storing them in the registry. Typical examples of data stored in the directory include printer queue data, user contact information, and network/computer configuration information. The Active Directory database consists of objects and attributes. The objects and attribute definitions are stored in the Active Directory schema. In summary, if you have relatively static, global, sharable data, a directory is a good place to store it.

You're probably scratching your head, wondering, "What objects are currently stored in Active Directory?" In Windows 2000, Active Directory has three partitions (also known as naming contexts): domain, schema, and configuration. The domain partition is shown in Figure 1. It contains users, groups, contacts, computers, organizational units, and many other object types. Because Active Directory is extensible, you can also add your own classes and/or attributes. The second partition, schema, contains classes and attribute definitions. The last partition is the configuration partition, which includes configuration data for services, partitions, and sites.

Figure 1. The domain partition of the Active Directory

That's a quick (and rather dirty) five-minute introduction to Active Directory. You may want to consult other documentation that talks about the Active Directory in detail, some of which is available from MSDN Online. For now, though, are you ready to have some fun coding?

Go to top.

## Installing Active Directory

First, you will need to install the Windows 2000 Server or Enterprise Edition. Once the installation is done, you will need to install Active Directory by following the Configure Your Server Wizard or running dcpromo.exe directly.

For the client, you can either use the same computer, or you can install Windows 2000 Professional on a separate computer. ADSI is a part of the Windows 2000 operating system. Optionally, you can use Microsoft Windows NT® 4.0 or Windows 9.x to communicate with Active Directory. You must, however, install ADSI 2.5 on those computers. Note that some of the features in Windows 2000 are not available in ADSI 2.5.

Go to top.

## Start Connecting to Active Directory

There are a few different ways to access Active Directory. Microsoft recommends using ADSI as the strategic API to access Active Directory. Underneath, ADSI uses the LDAP protocol to communicate to Active Directory. Now, let's get started with some easy and fun access to Active Directory. For brevity, all samples are written in Microsoft Visual Basic®. A guideline for converting the samples to Microsoft Visual C++® for C++ fans is provided at the end of the article.

    Set ns = GetObject("LDAP:")

That's it. You will be connecting to one of the Active Directory domain controllers via ADSI. ADSI, with the help of the locator service, will attempt to find the best domain controller (DC) for you. (Note that the locator service algorithm is beyond the scope of this article. Essentially, the locator service will use site information to determine the best domain controller for a given client.) You don't need to worry about trivialities like the server name. This is what we refer to as server-less binding.

But wait, you say, I like server names. Well, ADSI also allows you to specify the server name.

    Set obj = GetObject("LDAP://mysrv01")

In another scenario, you may only know the domain name (but not the specific server name). Again, ADSI allows you to specify the domain name. In Windows 2000, the domain name is represented as a DNS name.

    Set obj = GetObject("LDAP://fabrikam.com")

ADSI will connect you to one of the domain controllers in the fabrikam.com domain.

Look too easy for you? Not convinced? Follow these steps to see what's in Active Directory:

1. Run adsvw.exe, located in the SDK Samples directory. ADSI Viewer is a testing tool for ADSI.
2. Select ObjectViewer. Type "LDAP:" or "LDAP://yourServer" or "LDAP://yourDomain.com".
3. Make sure you have cleared the check box for Use OpenObject.

You'll see Active Directory objects in your domain.

Figure 2. ADSI Viewer

Go to top.

## Fabrikam Corporation

Fabrikam is a fictitious company. We will use this company to illustrate typical scenarios for an organization.

Fabrikam just upgraded their domain from Windows NT 4.0 to Windows 2000. Their administrator, Joe, is excited about administering Active Directory. Of course, he can use Active Directory Tools to manage the Active Directory, but this article is not about using the administrative tools, it's about programmatic access. During the installation, Joe was asked the domain DNS name and he typed "fabrikam.com." The existing users, groups, and computers will be migrated to Active Directory into the new domain: fabrikam.com.

Before we continue with our scenario, let's switch our discussion. First we need to know how objects are named in Active Directory. To start with, let us look at how files are named, since most of you are familiar with the file system. Each file has a name and a path. The file name must be unique among siblings. Consider the following example: "c:\public\specs\adsi25.doc." In this case, the file name is "adsi25.doc" and the file path is "c:\public\specs\adsi25.doc."

Objects in Active Directory are named in a somewhat similar manner. Every object has an object name or relative distinguished name (RDN), and an object path or distinguished name (DN). The RDN or object name has two parts: attribute IDand the value itself. For example, DC=Fabrikam. DC is an RDN attribute ID and stands for domain component, and Fabrikam is the value of the attribute. In our case, the Fabrikam domain object's distinguished name will be DC=Fabrikam, DC=Com. A DN is composed of multiple RDNs.

Now, you can bind to the domain object as follows:

    Set dom = GetObject("LDAP://DC=Fabrikam,DC=Com")

Given that we have the domain object, we can now print some its attributes:

    Debug.Print dom.Get("Name")
    Debug.Print dom.Get("whenCreated")

### Creating an Organizational Unit

Hold on to that domain object! Let's make the scenario more interesting. Fabrikam has two divisions: Sales and Production. The company is planning to hire two Windows 2000 administrators to manage each division. Joe, as the enterprise administrator, will create two new organizational units under Fabrikam domain. By creating an organizational unit, Joe can group multiple objects together and let someone else manage these objects. Here is the code that does the Sales Organizational Unit (OU) creation:

    Set salesOrg = dom.Create("organizationalUnit", "OU=Sales")
    salesOrg.Put "description", "Sales Headquarter,SF"
    salesOrg.Put "wwwHomePage", "http://fabrikam.com/sales"
    salesOrg.SetInfo

Let's help you`` digest that code. The Create method accepts the class name and the name of the new object. At this point, the object is not committed to Active Directory. You will, however, have an ADSI/COM object reference on the client. With this ADSI object, you'll be able to set or modify attributes using the Put method. The Put method accepts the attribute name and the value of the attribute. Still, nothing is committed to the directory; everything is cached at the client side. When you call the SetInfo method, the changes (in this case, object creation and attribute modification) are committed to the directory. These changes are transacted, meaning you'll either see the new object with all attributes you set, or no object at all.

We will leave the Production Division organizational unit creation as your homework exercise. Can you nest organizational units? You bet! Let's assume the Sales division is divided further into the East and West regions.

    Set east = salesOrg.Create("organizationalUnit", "OU=East")
    …
    east.SetInfo

We can do the same thing for the West region.

If you want to bind directly to the East region in the Sales organization, you need to specify the distinguished name.

    Set east = GetObject("LDAP://OU=East, OU=Sales, DC=Fabrikam,DC=COM")
    Debug.Print east.Get "description"
    east.Put "wwwHomePage", "http://fabrikam.com/sales/east"

If you already have the parent object (Sales), you can bind to the child object (East) from the parent object.

    Set east = salesOU.GetObject("organizationalUnit", "OU=East")

"How do I know that I really created these new objects?" you ask. Well, you can use the Active Directory Users and Computers MMC snap-in and presto! The new organizational units should be there.

### Moving Existing Users to the Organizational Unit

When Joe upgraded the Windows NT 4.0 domain to Active Directory, all the users and groups were migrated to the "Users" containers in the Fabrikam domain. Let's move some of the users to the appropriate organizational units. Note that you can also move an object between related Windows 2000 domains using ADSI.

    Set usr = salesOU.MoveHere("LDAP://CN=mikeg,CN=Users,DC=Fabrikam,DC=COM", vbNullString)

Let's look at what MoveHere does. MoveHere takes the ADsPath (the provider moniker, that is, LDAP, and a distinguished name) of the object you want to move and the new object name (RDN). If you want to keep the same name, you can specify NULL. If you want to rename the object, you can specify the new name as the second parameter. In this example we are moving Michael Gray (mikeg) to the Sales organizational unit.

Note AdsPAths are beyond the scope of this article but are discussed in detail in the ADSI documentation.

### Creating New Users in the Organizational Unit

A new user, Julie Adam, was just hired in the sales organization. She'll be Michael Gray's new boss. Joe, the administrator, was asked to create a new account for her.

    Set salesOU = GetObject("LDAP://OU=Sales,DC=Fabrikam,DC=COM")
    Set usr = salesOU.Create("user", "CN=Julie Adam")
    usr.Put "samAccountName", "juliea"
    usr.Put "userPrincipalName", "julie@fabrikam.com"
    usr.Put "title" "Marketing Manager"
    usr.SetInfo

    usr.SetPassword "seahorse"
    usr.AccountDisabled = False
    usr.SetInfo

What's new here? Notice that you have to specify samAccountName. This is a mandatory attribute for the user class. Before an instance of an object can be created, all mandatory attributes must be set. The user samAccountName is used to log on from pre-Windows 2000 computers (that is, Windows NT 4.0 and Win 9.x). Windows 2000 computers continue to understand the samAccountName. So, what's the userPrincipalName? In a Windows 2000 environment (both client and DC are running Windows 2000), you can log on using the user principal name (UPN). In this example, Julie's UPN is set to julie@fabrikam.com. If Julie moves to a different domain in the same enterprise (Active Directory refers to this as Forest), she can continue to use her user principal name.

Joe, as an administrator, will also be able to assign her password using the SetPassword method. The SetPassword method only works when the actual object has been created in the directory. We therefore need to call SetInfo before attempting to set the user's password. Finally, we enable the user's account by setting the AccountDisabled property to FALSE.

Now, let's make Julie Michael's manager.

    Set usr = GetObject("LDAP://CN=mikeg, OU=Sales, DC=Fabrikam, DC=COM")
    usr.Put "manager", "CN=Julie Adam,OU=Sales, DC=Fabrikam,DC=COM"
    usr.SetInfo

You've probably seen a potential problem here. What happens if Julie changes her name, moves to a different organization, or leaves the company? Who maintains this manager-direct report link? We'll revisit this later when we do the reorganization exercise. One more note—because the Active Directory schema is extensible, you can model your objects to include similar manager-direct report style relationships.

Before we go onto the next task, let's look at Julie's direct reports.

    Set usr = GetObject("LDAP://CN=Julie Adam, OU=Sales, DC=Fabrikam, DC=COM")
    reports = usr.GetEx ("directReports")

    For each directReport in reports
       Debug.Print directReport
    Next

You should see MichaelG as her direct report, even though you never modified the directReports attribute. Active Directory "automagically" does this for you.

But, what's the GetEx method ? In the directory world, an attribute can be single or multivalued. Since we know that directReports is multivalued (you can get this information by looking at the schema), it's easier to use GetEx, which always returns an array of variants regardless of whether single or multiple values are returned.

The Active Directory Users and Computers snap-in lets you see direct reports and manager relationships on the user's property page.

### Creating a New Group

Joe needs to create a new group. He would like to secure some resources (file, Active Directory objects, or other objects) based on the membership of this group.

    Set ou = GetObject("LDAP://OU=Sales,DC=Fabrikam,DC=COM")
    Set grp = ou.Create("group", "CN=Management")
    grp.Put "samAccountName", "mgmt"
    grp.Put "groupType", ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP Or ADS_GROUP_TYPE_SECURITY_ENABLED
    grp.SetInfo

This group, "Management," will be created in the Sales organizational unit. The first thing we need is the ADSI object for the Sales organizational unit. The samAccountName is a mandatory attribute for backward compatibility. In this example, Windows NT 4.0 tools, like User Manager, will see "mgmt" instead of "Management." Finally, you should also specify the type of group. In a Windows 2000 domain, there are three types of groups: Global, Domain Local, and Universal. In addition, the group carries its security characteristic. A group can be either security-enabled or a non-secured group. Essentially, security-enabled groups are those that can be granted/denied access rights to resources (just like a user). Granting a group access to a file share, for example, implies that all members of the group can access the file share. Distribution lists cannot be used in a similar manner—you cannot, for example, grant a distribution list rights to access a file share. During the upgrade, Windows NT 4.0 groups will be migrated as the security enabled groups. Non-secured groups in Active Directory are similar to distribution lists in Exchange. Hence, creating groups or distribution lists are very similar operations in Windows 2000.In Windows 2000 native mode (native mode means all domain controllers in a domain are Windows 2000 servers), the groups can be nested to any level.

### Adding Users to a Group

Let's add Julie to the Management group.

    Set grp = GetObject("LDAP://CN=Management, OU=Sales, DC=Fabrikam, DC=COM")
    grp.Add ("LDAP://CN=Julie Adam, OU=Sales, DC=Fabrikam, DC=COM")

The Add method adds an object to a group. Julie has now been added to the Management group.

### Enumerating Objects

Oftentimes, you'd like to enumerate children in a container or organizational unit. Enumerating the children of a container is the operation by which you view objects that exist directly under this container. In the file system this would correspond to files in the current directory. You may also want to traverse up one level to get the parent object of a given object.

To enumerate the children of a container:

    Set ou = GetObject("LDAP://OU=Sales, DC=Fabrikam, DC=COM")
    For each child in ou
       Debug.Print child.Name
    Next

You can filter the types of objects returned from the enumeration. For example, if you want to display only users and groups, you can use Ou.Filter = Array("user", "group") before the enumeration.

If you have an object reference, you can get its parent using the Parent property. You can use this information to bind to the parent object.

    parentPath = obj.Parent
    Set parent = GetObject(parentPath)

### Searching for Objects

Julie needs to find telephone numbers for all Program Managers who work in Department 101. Let's help Julie create a script that uses ADO and ADSI to achieve this.

    'Create connection and command object
    Set con = CreateObject("ADODB.Connection")
    Set com = CreateObject("ADODB.Command")
    ' — Opening the connection
    con.Provider = "ADsDSOObject"  'this is the ADSI-OLEDB provider name
    con.Open "Active Directory Provider"
     ' Create a command object for this connection
    Set Com.ActiveConnection = con

    'Compose a search string
    Com.CommandText = "select name,telephoneNumber from 'LDAP://DC=Fabrikam, DC=com' WHERE
    objectCategory='Person'
    AND objectClass = 'user'
    AND title='Program Manager'
    AND department=101"

    ' — Execute the query
    Set rs = Com.Execute
    '--------------------------------------
    ' Navigate the record set
    '----------------------------------------
    While Not rs.EOF
      Debug.Print rs.Fields("Name") & " , " & rs.Fields("telephoneNumber")
       rs.MoveNext
    Wend

Okay, okay, we know this is the first time you are seeing working code that is more than 10 lines long in this article. If you're familiar with Microsoft ActiveX® Data Objects (ADO), this should be a no-brainer. In order to do an ADSI search in Visual Basic or a scripting environment, you will need three components: Connection, Command, and Recordset. The Connection object allows you to specify the provider name, alternate credentials (if applicable), and other flags. The Command object allows you to specify search preferences and the query string. You need to associate the Connection object to a Command object before the execution of the query. Lastly, the Recordset object is used to iterate the result set.

ADSI supports two types of query strings or dialects. The preceding example uses the SQL dialect. You can also use the LDAP dialect. The LDAP dialect query string is based on RFC 2254 (RFC = Request For Comments). The preceding example can be translated to:

    Com.CommandText = "<LDAP://DC=fabrikam, DC=COM>;(&(objectCategory=Person)(objectClass=user)(title=ProgramManager)(department=101);name,telephoneNumber;subTree"

What's that subtree word at the end of the string? In the directory world, you can specify the scope of search. The choices are: base, oneLevel and subtree. Base is basically to read the object itself; oneLevel refers to the immediate children, similar to the "dir" command; subtree is to search deep or down multiple levels (similar to dir /s).

If you're using the SQL dialect, you can specify scope in the command's property. For example, Command.Properties("Search Scope") = ADS_SCOPE_ONELEVEL. If no scope is specified by default, it's a subtree search.

Note If you use C++, you can use the IDirectorySearch interface from ADSI.

### Reorganization

The whole sales organization is moved to a new organization—"Sales and Support." Julie has been promoted to Vice President and is going to lead the new organization.

    Set dom = GetObject("LDAP://DC=Fabrikam, DC=COM")
    Set salesSupport = dom.Create("organizationalUnit", "CN=Sales and Support")
    Set sales = salesSupport.MoveHere("LDAP://OU=Sales, DC=Fabrikam, DC=COM", vbNullString)

Three lines of code for a reorganization isn't too bad, is it? All objects in the sales organizational unit, including the sub-organizational units, are moved to the new organizational unit.

Now, let's move Julie into the Sales and Support organizational unit.

    Set usr = salesSupport.MoveHere("LDAP://CN=Julie Adam, OU=Sales, OU=Sales and Support, DC=Fabrikam,DC=COM")
    usr.Put "title", "Vice President"
    usr.SetInfo

You may wonder whatever happened to the manager-direct report link between Julie and Michael. "Who gets to fix this link after the reorganization?" you ask. As it turns out, Active Directory automagically fixes the links for you We told you Active Directory and ADSI are cool, didn't we?

### Creating a Report

Creating an Active Directory report using ADSI is a snap. Follow these steps:

1. Open Visual Basic version 6.0.
2. When asked for the project type, select Data Project.
3. On Data Project, double-click Data Environment1.
4. On the Data Environment window, right-click on the Connection Object (Connection1) and select Properties.
5. Select OLE DB Provider for Microsoft Directory Services, and click the Next button.
6. Select Use NT Integrated Environment, and we're done creating a connection object.
7. On the Data Environment window again, right-click to select Add Command. Right click on Command1 object and select property. A dialog box will appear (see Figure 3).
8. Check SQL Statement, and type the following (see Figure 3):

       SELECT Name,telephoneNumber from 'LDAP://DC=Fabrikam,DC=com'
       WHERE objectClass='user' AND objectCategory='Person'

9. We're done creating a Command object. It's time to add the Command object to the report.

   Figure 3. Command1 Properties dialog box

10. Double-click Data Report1 from the Project window.
11. Drag and drop the Command1 object on Data Environment to the Detail section in the Data Report.
12. On DataReport1 Properties, make DataSource=DataEnvironment1 and DataMember=Command1.
13. Right-click Data Project, and select DataProject Properties.
14. On the DataProject Properties Dialog - Startup Object, select Data Report1.
15. Run. You should see a similar report with data obtained from Active Directory.

    Figure 4. DataReport1 dialog box

### Joining Heterogeneous Data

Typical organizations store the data in multiple heterogeneous databases. Human resources data may be stored in SQL Server, while account management information is stored in the directory. Other information may be stored in proprietary formats. It's increasingly difficult to merge data from two or more sources into one usable report because of different APIs, protocols, and data formats.

With, SQL Server 7.0 Distributed Query, ADSI, OLEDB, and Active Directory, it is possible to join information from Active Directory to data that resides in SQL Server. You can even create a view of the joined data.

Here are the step-by-step instructions:

In SQL Server

1. Run the Query Analyzer (Start | Programs | Microsoft SQL Server 7.0)
2. Log on to the SQL Server computer.
3. Execute the following line (by highlighting it and pressing CTRL+E):

       sp_addlinkedserver 'ADSI', 'Active Directory Service Interfaces', 'ADSDSOObject', 'adsdatasource'
       go

   This tells SQL Server to associate the word "ADSI" with the ADSI OLE DB provider, "ADSDSOObject."

   Now we are ready to access Active Directory from SQL Server.

4. Type and execute:

       SELECT * FROM OpenQuery( ADSI, 'SELECT name, adsPath FROM "LDAP://DC=Fabrikam,DC=com" WHERE objectCategory = "Person" AND objectClass= "user"")

5. You may also use the ADSI LDAP dialect. For example:

       SELECT * FROM OpenQuery(ADSI,'<LDAP://DC=Fabrikam;DC=COM>;(&(objectCategory=Person)(objectClass=user));name, adspath;subtree')

#### Creating and Executing a View

You can create a view for data obtained from Active Directory. Note that only the view definition is stored in SQL Server, not the actual result set. Hence, you may get a different result when you execute the view later. To create a view, type and execute the following:

    CREATE VIEW viewADUsers AS
    SELECT * FROM OpenQuery( ADSI,'<LDAP://DC=Fabrikam,DC=com>;(&(objectCategory=Person)(objectClass=user));name, adspath,title;subtree')

To execute a view, type the following:

    SELECT * from viewADUsers

#### Heterogeneous Join between SQL Server and Active Directory

All employees in Fabrikam are reviewed every six months. The review ratings will be stored in the Human Resource database in SQL Server. First, we need to create an employee performance review table:

    CREATE TABLE EMP_REVIEW
    (
    userName varChar(40),
    reviewDate datetime,
    rating decimal
    )

Insert a few records:

    INSERT EMP_REVIEW VALUES('Julie Adam', '2/15/1999', 4 )
    INSERT EMP_REVIEW VALUES('Julie Adam', '7/15/1999', 5 )
    INSERT EMP_REVIEW VALUES('Michael Gray', '2/15/1999', 3 )
    INSERT EMP_REVIEW VALUES('Michael Gray', '7/15/1999', 4 )

Now, let's join the two—the Active Directory user objects to the SQL Server table:

    SELECT ADsPath, userName, title, ReviewDate, Rating
    FROM EMP_REVIEW, viewADUsers
    WHERE userName = Name

Voila! You should get the result from both SQL Server and Active Directory. AdsPath and Title columns are from Active Directory, whereas username, ReviewDate, and Rating are from the SQL table. You can even create another view for this join.

    CREATE VIEW reviewReport
    SELECT ADsPath, userName, title ReviewDate, Rating
    FROM EMP_REVIEW, viewADUsers
    WHERE userName = Name

Go to top.

## Advanced Topics

There are still plenty of Active Directory and ADSI features we have not discussed. Next, we will selectively and briefly touch on a few advanced topics.

### RootDSE

Each directory server has a unique entry called RootDSE. It gives you information about the server—information such as what the server capabilities are, which LDAP version the server supports, what naming contexts exist under this server, and other useful data.

Let's suppose you want to create a script or application that can run on any Windows 2000 domain environment. We can specify either the distinguished name, server name, or domain name when connecting to Active Directory. What if you don't have this information? The RootDSE object comes to the rescue. The following code example changes the domain description in any domain:

    Set rootDSE = GetObject("LDAP://RootDSE")
    Set dom = GetObject( "LDAP://" & rootDSE.Get("defaultNamingContext"))
    dom.Put "description", "My domain"
    dom.SetInfo

By getting the defaultNamingContext attribute from RootDSE, you will be able to bind to the current domain (for Arcadia Bay the defaultNamingContext would have been dc=Fabrikam, DC=COM).

### Delegating Organizational Units

Fabrikam just hired two administrators, Mike and Paul, to manage the East and West organizational units respectively. Joe will delegate his administrative responsibilities to them so that they can create and delete users in their respective organizational units.

    Set ou = GetObject("LDAP://OU=East, OU=Sales, DC=Fabrikam,DC=COM")
    Set sec = ou.Get("ntSecurityDescriptor")
    Set acl = sec.DiscretionaryAcl

    '---Or you can use Set ace = new ADsAccessControlEntry
    Set ace = CreateObject("AccessControlEntry")

    'Allow to--
    ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
    '--- Create and Delete…
    ace.AccessMask = ADS_RIGHT_DS_CREATE_CHILD Or ADS_RIGHT_DS_DELETE_CHILD
    '---User object----
    ace.ObjectType = "{BF967ABA-0DE6-11D0-A285-00AA003049E2}" 'User's schema IDGUID ace.AceFlags = ADS_ACEFLAG_INHERIT_ACE 'Propogate the ace down
    ace.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT 'Tells that objectType is filled
    ace.Trustee = "FABRIKAM\Mike" 'Who is the beneficiary of this ace
    acl.AddAce ace

    sec.DiscretionaryAcl = acl
    ou.Put "ntSecurityDescriptor", Array(sec)
    ou.SetInfo 'Commit to Active Directory

    Set ace = Nothing
    Set acl  = Nothing
    Set sec = Nothing

A touch complicated isn't it?

Each object in Active Directory has a security descriptor. With the security descriptor, you'll be able to modify permissions on the object, propagate permissions, enable auditing, and so on. The security descriptor itself has two Access Control Lists (ACLs), called Discretionary ACL (DACL) and System ACL (SACL). Each ACL can contain Access Control Entries (ACEs). With an ACE, you can set allowed or denied access on an object. In addition, you can specify which actions are to be allowed or denied. Examples of actions include Create Child, Delete Child, Read Property, Write Property. Next, you may specify which classes or attributes this ACE will take effect upon. In our example, we pick the user class. Next, you have to answer the question: "Who will be the beneficiary of this ACE?" In our example, we specify Mike. Finally, you can set the ACE inheritance behavior—for example, ACE's can be specified to be propagated down the hierarchy. In summary, the previous example will result in Mike being able to create and delete user objects under the East Sales organizational unit. Figure 5 shows the Active Directory Create New View menu after we run the code. When you logon as Joe (Administrator), you will see quite a few classes you can create. However, when you log on as Mike, you can only create user objects.

Figure 5. Create New View Menu from two different users perspectives

### Global Catalog

Multiple domains may be joined together to form a tree of domains, and multiple trees joined to form a forest. Each forest shares the same schema and configuration partitions. All objects in a forest are replicated to a global catalog (GC). You may have more than one global catalog for a given forest. A global catalog is always a domain controller, but a domain controller is not necessarily a global catalog. Not all attributes are replicated to the global catalog. Only selected attributes are replicated to the GC, and you can select attributes to be replicated to GC.

The primary purpose of the global catalog is to enable enterprise-wide searches. For example, Joe wants to find all users in the forest hired in the last 30 days.

To bind to a global catalog programmatically, use the GC: moniker instead of the LDAP: moniker.

    Set gc = GetObject("GC://srv01.fabrikam.com")
    Set gc = GetObject("GC://DC=Fabrikam, DC=COM")

If Europe.Fabrikam.com is a child of Fabrikam.com, searching from GC://DC=Fabrikam,DC=Com will include objects in the Europe.fabrikam.com as well.

### Extending ADSI

With the ADSI extension model, you can associate a directory class with your own COM object. From an ADSI programmer/script writer's perspective, the extension becomes an integral part of ADSI.

Let's revisit our scenario. When a new employee joins Fabrikam, the Windows NT administrator will create a user object in the directory and the payroll administrator will need to set up some entries in the human resource systems for this user. With an ADSI extension, this process can be streamlined into one single script.

    Set usr = ou.Create("user", "CN=Alice Johnson")
    // setting some attributes…
    usr.SetInfo

    usr.AddToPayroll  'this is a custom method from an ADSI Extension
    Debug.Print "User: " & usr.Name & "has been created"

### Note for C++ Programmers

ADSI consists of more than 50 interfaces. The good news is you can do almost 90 percent of directory operations using only five interfaces. They are:

- IADsOpenDSObject
- IADs
- IADsContainer
- IDirectoryObject
- IDirectorySearch

Here are some mappings from ADSI VB/VBS code to C++ code. Note that this is not a complete list.

VBS code | VC code
Set obj = GetObject() | hr = AdsGetObject()
obj.Put obj.Get obj.Parent | IADs or IDirectoryObject
obj.Create obj.Delete obj.MoveHere | IADsContainer
For each … in … | AdsBuildEnumerator() ADsEnumerateNext
Connection, Command, RecordSet | IDirectorySearch
Security descriptor, ACL, ACE | IADsSecurityDescriptor, IADsAccessControlList, IADsAccessControlEntry

Go to top.

## Summary

Accessing Active Directory using ADSI is easy, simple, and fun. ADSI is well integrated with Active Directory. Now, it's the time to integrate your applications to Active Directory and make good use of this excellent technology.

Before we close the article, let's look at few integration possibilities. Active Directory is an enabling distributed technology for you to explore. The possibilities are truly endless, only your creativity and imagination are the limit.

- Meet me at the Active Directory Cafe. Let's say you're creating a client server application. Your server application written as a Windows NT service can be installed on a number of servers. During the server installation, the service publishes some information about itself (enough information for the client to connect to the service). When the client starts, it can query Active Directory for specific services. After getting the result, the client can examine the information published by the service. This information is enough for the client to connect to the service.
- Follow me anywhere I go. When an Fabrikam employee logs on in the morning, he's greeted with the up-to-date information from his organization's Web site. When he moves to a different organization, the Web site changes correspondingly. How is this possible? Easily done. You can create an ADSI logon script. In the script, you will ask who the user is and where the user object is located in Active Directory. Once you bind to the object, you can ask for its parent, which most likely is an organizational unit. Then you can display the URL obtained from the wwwHomePage attribute on the organizational unit object.
- Tell me, who should I talk to? Active Directory includes site and subnet information. Your application may leverage this information to match the servers and clients based on the site.
- Don't share my information. Julie is working on a secret project, the "X-File." She doesn't want anybody to know about this project. Only members of "The X" group can see this project. She can extend the Active Directory schema to add a project class and a few attributes related to the project. Now, she can set the permissions on the "X-File" object so that only the "The X Group" can read the object.
- Share my information. You create a server application that can be deployed on many servers. Let's say you store "Protocol of Choice" data in the Active Directory. Servers will obey and use the preferred protocol based on this information for communication between them. When an admin changes the protocol information in the Active Directory, sooner or later this information will be replicated to other Active Directory replicas. The servers will then pick up this change from their closest Active Directory replica and finally they will all talk the same language again. Without Active Directory, an Admin may have to visit each server (remotely or locally) and reconfigure the information.

Go to top.

## For More Information

Active Directory Programmer's Guide:
http://msdn.microsoft.com/developer/windows2000/adsi/actdirguide.asp

ADSI:
http://www.microsoft.com/windows/server/Technical/directory/adsilinks.asp

Windows 2000 Server:
http://www.microsoft.com/windows/server/

Go to top.

© 1999 Microsoft Corporation. All rights reserved. Terms of use.