Downloads
4732.zip

Through the next 12 months, I'll explore the abilities of Microsoft's Active Directory Service Interfaces (ADSI). This month, I'll introduce ADSI and explain some basic ADSI concepts before running through a few simple scripts. For the first few articles, I'll concentrate on scripting with Visual Basic Script (VBScript) under the Windows Scripting Host (WSH); future articles will use other scripting languages, such as Perl and JScript.

What Is ADSI?
In February 1997, Microsoft released a set of generic interfaces, called the ADSI Software Development Kit (SDK), that access and manipulate different directory services. You can download the free ADSI SDK from Microsoft's Web site. (For this URL and others dealing with ADSI, see the sidebar "ADSI Web Sites.") The interfaces are broad-based. The ADSI SDK natively supports many types of directories and the Lightweight Directory Access Protocol (LDAP).

Support for Microsoft's Windows 2000 (Win2K—formerly NT 5.0), NT 4.0, and NT 3.51 directories and for Novell NetWare 3.x and intraNetWare 5.x and 4.x directories. ADSI abstracts the function calls that you make to access these directory services. As a result, you can, for example, use the same ADSI call to create a user on an NT server and an intraNetWare server. Previously, you had to make separate, sometimes complex calls. Thus, ADSI saves you time because you're not duplicating your efforts.

You can save even more time by using scripts to manipulate directories. Previously, calls weren't scriptable, but with ADSI they are scriptable, because WSH supports ADSI. An added bonus is that ADSI is language-independent, which means you can use a host of compiled and scripting languages, such as Rexx, Perl, and Visual C++ (VC++). In other words, you can use ADSI in any language that supports the Component Object Model (COM).

Support for other proprietary directories. Microsoft designed ADSI so that companies that develop non-LDAP-compliant directory services can support ADSI calls to their directories. These companies, or service providers, need only to create a provider-specific DLL that interprets the ADSI calls to their directory service. For example, Microsoft is adapting its products with directories (e.g., Exchange Server, Internet Information Server—IIS) to support ADSI.

Support for LDAP 3.0 and 2.0. Many developers and administrators consider LDAP support ADSI's major strength. LDAP is a network protocol that proposes a mechanism for accessing directory services over TCP/IP stacks. Researchers at the University of Michigan, with support from the National Science Foundation, originally developed LDAP in response to previous slow and cumbersome protocols, such as X.500's Directory Access Protocol (DAP). Consequently, LDAP is fast and small. (For more background information about LDAP, see Craig Zacker, "LDAP and the Future of Directory Services," Parts 1 and 2, Windows NT Magazine, October and November 1997.)

LDAP is becoming the de facto standard for directory service access on the Internet. LDAP's use in ADSI is clever. A service provider simply has to support LDAP 2.0 or later, and ADSI can instantly access the directory service without a provider-specific DLL. The list of LDAP servers that ADSI can access includes Win2K's Active Directory (AD), Exchange Server 5.x, intraNetWare 5.x, Netscape Directory Server 1.0, and Microsoft Commercial Internet System's (MCIS's) Address Book Server.

In the last quarter of 1998, Microsoft released a beta of ADSI 2.5, which Microsoft includes with NT 5.0 Beta 2. This point release is a major leap forward because of its extra interfaces, bug fixes, and much improved documentation. (Previously, the poorly written documentation prevented many administrators from even attempting to use ADSI.) In this series, I'll use ADSI 2.5 in my examples. But before you can start writing scripts that use ADSI, you first need to understand the basic COM concept of interfaces and ADSI's concepts of namespaces, programmatic identifiers (ProgIDs), and ADsPaths.

Interfaces
Each ADSI object has an interface that provides basic information about that object, such as the object's name, path, and path to the parent object. The most basic interface is IADs. Other interfaces, of which there are many, begin with IADs. Interfaces can relate to many different types of objects, including objects that reside in directory services (e.g., IADsUser, IADsGroup), transient objects that don't exist in a directory service (e.g., IADsPrintJob), and objects that help ensure security (e.g., IADsOpenDSObject, IADsAccessControlList).

Because each directory service is slightly different, not every ADSI method and property works in every directory service. If you make a method call to a directory service that doesn't support that method, you'll receive a specific error message specifying that the provider doesn't support that method. This message is part of the ADSI specification. If a service provider supports ADSI, the provider must reject all inappropriate calls with the correct ADSI error message.

Namespaces, ProgIDs, and ADsPaths
To reference different types of servers (e.g., NT 4.0, intraNetWare, Exchange Server) with ADSI, you use namespaces to distinguish between the different providers' directory services. ADSI uses a unique prefix called a ProgID to distinguish between these namespaces. Each ProgID is synonymous with a particular namespace.

In a script, you specify the ProgID to tell the ADSI method which namespace you want to bind. For example, you specify WinNT to access NT 4.0 and NT 3.51 systems. When ADSI encounters the ProgID, ADSI loads an appropriate ADSI-provider DLL to process the bind request.

Telling ADSI you want to bind via a particular namespace isn't enough. You also need to reference the object that you want to access in that namespace. A reference to an object via a namespace is an ADsPath. Each object has a unique ADsPath. Take, for example, these WinNT namespace ADsPaths:

  • WinNT://NT4DOMAIN/JoeB, which references JoeB, a user in NT4DOMAIN
  • WinNT://NT4DOMAIN/JoeB, User, which references JoeB, a user in NT4DOMAIN
  • WinNT://NT4DOMAIN/COMP12345, which references COMP12345, a computer in NT4DOMAIN
  • WinNT://NT4DOMAIN/COMP12345, Computer, which references COMP12345, a computer in NT4DOMAIN
  • WinNT://NT4DOMAIN/Users, which references Users, a group in NT4DOMAIN
  • WinNT://NT4DOMAIN/Users, Group, which references Users, a group in NT4DOMAIN
  • WinNT://NT4DOMAIN/MOOSE/JoeB, which references JoeB, a user on server MOOSE in NT4DOMAIN

As these examples show, you can reference each object by using only its name or, more properly, by using its name and type to make sure that the system can identify the appropriate object if two or three identically named objects with different types exist.

Each namespace has a unique format for the ADsPath string, so you need to make sure that you're using the correct ADsPath notation. For example, each of these ADsPaths reference a unique object:

  • WinNT://NT4DOMAIN/MOOSE/JoeB, User, which references JoeB, a user on server MOOSE in NT4DOMAIN
  • NDS://MyNetWareTree/O=MYCORP/OU=FINANCE/CN=JoeB, which references JoeB, a user in the Finance organizational unit (OU) within the Mycorp organization of the intraNetWare tree called MyNetWareTree
  • NWCOMPAT://MYSERVER/JoeB, which references JoeB, a NetWare 3.x or 4.x (bindery services) user that exists on server MYSERVER
  • IIS://localhost/w3svc/1, which references the WWW service component of IIS running on the local host

In this example, NDS: refers to intraNetWare 5.x and 4.x. (Because intraNetWare 5.x is LDAP-compliant, you can also use LDAP paths with it.) NWCOMPAT: refers to NetWare 4.x, 3.2, 3.12 and 3.11 servers in bindery-emulation mode. IIS: refers to metabase paths on a host running IIS 3.0 or later.

One of the most commonly used namespaces is the LDAP namespace. You can use LDAP with ADSI to access a variety of directory services, including Win2K's AD. Although you can use the WinNT namespace to access AD, you need to use the LDAP namespace to fully utilize all of ADSI's methods and properties. Throughout this series, I'll primarily use the LDAP namespace.

You can use several formats to refer to LDAP directories. For example, all the following ADsPaths reference the administrator object within the users container of the moose directory server in the w2000.mycorp.com zone:

  • LDAP://cn=administrator,cn=users,dc=w2000,dc=mycorp,dc=com
  • LDAP://moose.w2000.mycorp.com/cn=administrator,cn=users,dc=w2000,dc=mycorp,dc=com
  • LDAP://moose/cn=administrator,cn=users,dc=w2000,dc=mycorp,dc=com
  • LDAP://DC=com/DC=mycorp/DC=w2000/CN=Users/CN=Administrator
  • LDAP://moose.w2000.mycorp.com/DC=com/DC=mycorp/DC=w2000/CN=Users/CN=Administrator

In these examples, cn stands for common name and dc stands for domain controller. These examples show that you can specify the LDAP namespace ADsPath going down or up the hierarchical Directory Information Tree (DIT). You can also specify a fully qualified Domain Name System (DNS) server name after LDAP://, using a forward slash character (/) to separate the DNS server name from the rest of the path.

Authentication
Now that you know how to use ADsPaths to distinguish between different namespaces, I'll demonstrate how to establish a connection to (i.e., authenticate to) the server containing the directory you want to access. Authenticating a connection isn't always necessary; some directories, such as Win2K's AD, allow read-only access to certain parts of the data from an anonymous connection. However, if you want to update information or access a restricted object or property in the directory, you need to authenticate first.

If you don't want to authenticate. If you just want to bind anonymously to a directory server to get a reference to an object, you use ADSI's GetObject method. Listing 1 contains an example of how to use this method. You begin by declaring two variables with VBScript Dim statements. (If you are unfamiliar with Dim and other commonly used VBScript statements, see Michael Otey, "An Introduction to WSH," December 1998.) The first variable, strPath, is an ADsPath. The prefix str specifies that this ADsPath is a text string. The second variable, adsMyObject, is a pointer to the object in the directory that the path represents. The prefix ads specifies that the variable is an object. I use the prefix ads for all the ADSI objects in my scripts; however, you can also use other prefixes, such as o and obj.

Next, you assign the strPath variable to the path of the directory server you want to bind to—in this case, "LDAP://dc=w2000,dc=mycorp,dc=com". You need to enclose this path in quotation marks, because it's a text string.

Finally, you use VBScript's Set statement with the GetObject method to create a reference between the variable you declared and the existing object you want to interact with. In this case, you're creating a reference between adsMyObject and the existing object that the ADsPath "LDAP://dc=w2000,dc=mycorp,dc=com" represents (i.e., the root object of the LDAP-enabled directory). After you've established this reference, you can use other interfaces to interact with that object.

If you want to authenticate. To authenticate to a directory server, you use the IADsOpenDSObject interface, which contains only one method: OpenDSObject.IADsOpenDSObject::OpenDSObject takes four arguments: the ADsPath to authenticate to, the username, the password, and a security setting.

Listing 2 shows how to use OpenDSObject to authenticate to a directory server. You begin by declaring three string variables (i.e., strPath, strUsername, and strPassword) and two object variables (i.e., adsNamespaceLDAP and adsMyObject).

You then assign the strPath, strUsername, and strPassword variables the appropriate ADsPath, username, and password strings. The username string, which is also called the distinguished name (DN), references the username's exact location in the directory. (For more information about DN, go to http://www.faqs.org/rfcs/rfc1484.html.) The ampersand character (&) concatenates two strings.

Next, you use a Set statement with GetObject to create a reference for adsNamespaceLDAP. Notice that you're using "LDAP:" rather than strPath as an argument to GetObject. Using the LDAP namespace might seem unusual, but it is necessary so that, in the next line, you can call the OpenDSObject method on the LDAP namespace that ADSI returns. The ADsPath is a necessary argument to OpenDSObject, because the user might have permissions to only part of the DIT. The last OpenDSObject argument is the security setting. This setting is typically 0, which denotes that the script will use neither encryption nor secure authentication. (I'll explain secure authentication in more depth in a future article.)

Obviously, including an administrator's password in a script can compromise security. If you don't want to include plain-text passwords, you have several options. The first option is to assign strPassword an InputBox instead of a plain-text password, as Listing 3 shows. When you run the script, the InputBox prompts you to enter the administrator's password. However, when you enter the password, the InputBox echoes the password in plain text into the Password entry box, so this approach isn't too secure.

Three other options are secure. However, because VBScript doesn't natively support password retrieval boxes, you can't use these solutions without some work. One solution requires that you obtain a custom ActiveX component for VBScript that natively supports password dialog boxes. (For more information about ActiveX components, see Robert Richardson, "Using ActiveX Objects to Extend WSH's Functionality," page 1.) I haven't found such an ActiveX component yet, but that doesn't mean one doesn't exist. (If you know of any ActiveX component for VBScript that natively supports password dialog boxes, please let me know.)

The second solution is to write a script in a language other than VBScript that supports password boxes natively. You use the Perl/Tk extension modules for ActiveState Perl and standard Perl to create an Entry widget with the -show parameter as an asterisk. For the Perl aficionados, this Entry widget would look like

$dlg->Entry(qw/-show * -width 35/)->pack(); # arbitrary width

The third solution requires that you write the script from within Active Server Pages (ASP). You use the password field in an ASP form to retrieve the password.

If you want to authenticate a connection, but already have logged on to the directory, you can use the default credentials for your existing connection. You simply specify vbNullString in the username and password fields, as Listing 4 shows.

Until Next Time
Now that you're familiar with ADSI, understand its basic concepts, and know how to authenticate to a directory server, you're ready to use ADSI to manipulate objects in a directory. In Part 2 of this series, I'll show you how to use the IADs interface to manipulate the ADSI property cache.

This article is adapted from a forthcoming book on the Windows 2000 Active Directory to be published by O'Reilly and Associates.