Many Windows 2000 and Windows NT systems administrators find that developing custom scripts to manage their infrastructure is a radical departure from using traditional graphical toolsets. If you agree, you might be reluctant to learn Windows Script (WS) and supporting technologies such as Active Directory Service Interfaces (ADSI). Although you don't need to write custom scripts to manage your environment, scripting Active Directory (AD) administration tasks is much easier than you might think. To prove my point, I introduce three core interfaces in ADSI—IADsOpenDSObject, IADs, and IADsContainer—in a two-part series about ADSI. I also show you how to use these core interfaces to accomplish 80 percent of the most common AD management tasks.
What Is ADSI?
ADSI is a set of automation-enabled COM objects that let you uniformly manage and manipulate multiple heterogeneous directories. ADSI is Microsoft's primary programmatic interface into AD, and all of Microsoft's graphical AD management tools use ADSI.
Out of the box, ADSI supports Lightweight Directory Access Protocol version 3 (LDAPv3), the NT 4.0 SAM, Novell Directory Services (NDS), and the NetWare Bindery. By virtue of ADSI's LDAP support, ADSI supports a range of LDAP/X.500-based directories, including Win2K AD, Microsoft Exchange Server 5.5, and Site Server 3.0. Although I focus on AD, many of the concepts I discuss in this article apply to the other supported directories.
Because ADSI is automation-savvy, you can use ADSI to write directory management scripts in any COM-compliant scripting environment, including Windows Script Host (WSH), VBScript, JScript, and ActivePerl. ADSI also supports fast, read-only ActiveX Data Objects (ADO) queries through OLE DB.
ADSI is also a set of client-side interfaces, which means you need to install ADSI only on the computer that runs your scripts. You don't need to install ADSI on target domain controllers. Win2K includes ADSI, but you need to download and install it if you plan to run ADSI scripts on NT or Windows 9x computers.
ADSI 2.5 is the current version, which you can download from Microsoft's ADSI Web site (http://www.microsoft.com/adsi). The Web site also has links to ADSI white papers and developer resources such as the ADSI software development kit (SDK).
AD and ADSI Terminology
Before you can effectively use ADSI, you must learn AD scripting terminology. Let's examine some of the terms you need to know.
AD is the Win2K directory service (DS). A DS is a network service that identifies and stores information about all resources on a network and that makes the information accessible to users and applications. AD is a multimaster replicated directory information tree (DIT) named ntds.dit that physically resides in the \%systemroot%\ntds directory on all Win2K domain controllers. You can update any AD replica, and AD will replicate the changes to all other domain controllers.
AD resembles a database in several ways (e.g., AD stores data). However, unlike relational databases that are optimized for insert, update, and delete operations, AD is a hierarchical structure optimized for read operations.
Every Win2K domain controller maintains a writeable directory replica, and each replica consists of a minimum of three partitions (known as naming contexts): the default naming context, the schema naming context, and the configuration naming context. The default naming context stores information about common domain objects (e.g., computers, groups, organizational units—OUs, users). The schema naming context stores the AD schema (which I describe below), and the configuration naming context stores site and subnet configuration information. The core interfaces provide a common and consistent approach to managing objects in all three naming contexts.
The schema is a collection of class and attribute definitions; it defines information that AD can store. A class is similar to a template for an object, and an attribute is an object property (e.g., common name, description, distinguished name—DN). The schema defines attributes that an object must have (i.e., mandatory properties) and additional attributes that an object can have (i.e., optional properties). You can access the schema from within your ADSI scripts to determine an object's mandatory and optional properties. The schema also defines the DIT structure. Objects that can contain other objects are container objects; objects that can't contain other objects are leaf objects.
AD represents network resources as objects. From AD's perspective, an object is a distinct named set of properties and associated values that represent a specific network resource (e.g., a computer named US-E-FN-01, a group named Investors, an OU named Finance, a user named Judy). From a programmatic or scripting perspective, an object is an instance of a schema class definition.
The interfaces in ADSI are the mechanisms you use to modify an object's properties. The term interface isn't unique to ADSI. In the COM programming discipline and vocabulary, an interface is a collection of methods and properties that a COM object exposes. Methods perform some action on an object, and properties describe an object's state. COM objects can expose multiple interfaces. For example, ADSI provides more than 50 interfaces. However, don't let this number alarm you; the three core interfaces let you accomplish many AD tasks.
In summary, ADSI is a set of programmatic interfaces you use to manage AD objects. The interfaces in ADSI provide the methods and properties for creating, deleting, and modifying objects. An object is a collection of properties that represent a network resource. The object's class definition, which the schema stores, governs the properties that make up an object. The schema class defines the object's mandatory and optional attributes. ADSI scripting is the process of using ADSI methods to create and delete objects and to modify properties.
What's in a Name?
Every object in AD has several names. DN and relative distinguished name (RDN) are the primary naming conventions for referencing objects in ADSI scripts. The DN identifies an object's precise location in the DIT based on an attributed name. The RDN is the object's mandatory name attribute. The relationship between the DN and RDN in AD is similar to the relationship between fully qualified paths and filenames in a file system.
Each DN component is an RDN, and each RDN is a string that consists of two parts—a key and a value—that you express as "key=value". The key identifies a valid attribute type, and the value is the attribute's value.
The Internet Engineering Task Force's (IETF's) Request for Comments (RFC) 2253, "Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names," defines the keys and attribute types that you use to construct LDAPv3 DNs. Table 1 lists several of the more commonly used keys and corresponding attribute types.
Let's use an example to demonstrate the relationship between DN and RDN. Figure 1 shows the DN and RDN for user Judy Schneider. Judy's RDN is "cn=Judy Schneider"; cn is the key for the common name attribute, and the string Judy Schneider is the value. We can examine Judy's DN to determine its exact location in the DIT. Judy belongs to the Finance OU that resides in the acme.com domain. You can think of Judy's DN as the combination of all the object's RDNs as you move from the object to the root of the tree.
An object's DN must be unique throughout the directory, and an object's RDN must be unique within the current container. The key and the equal operator are mandatory RDN components. Object types that don't have a specific key use the default "cn=" common name key.
Common AD Tasks
You can use the three core interfaces in ADSI to create, modify, and delete almost any AD object (e.g., user accounts, computer accounts, groups, OUs, sites, subnets). You can enumerate containers and generate custom reports, as well as add new classes and attributes to extend the schema.
Writing any ADSI script consists of a few basic steps. In the following scenario, I've listed the typical steps for creating and maintaining a user object.
Acme Widgets hires Judy Schneider as the new comptroller. Judy will belong to Acme Widgets' Finance OU. As Acme Widgets' systems administrator, you complete these four steps to create the new user:
- Use VBScript's GetObject function to connect to the target container ("ou=Finance" in this example) that will hold the new user.
- Use ADSI's Create method to create the new user object in the local property cache. ADSI's IADsContainer interface exposes the Create method.
- Use ADSI's Put and PutEx methods to set the new user object's mandatory and optional properties. ADSI's IADs interface provides the Put and PutEx methods.
- Use ADSI's SetInfo method to commit (write) the new object to the directory. SetInfo is also part of ADSI's IADs interface.
Judy buys a new home and moves. You need to update her home address and home telephone numbers, so you perform the next four steps:
- Use VBScript's GetObject function to connect to the target object ("cn=Judy Schneider") whose properties you want to change.
- Use ADSI's IADs methods—GetInfo, GetInfoEx, Get, or GetEx—to optionally initialize the object's local property cache.
- Use the Put or PutEx method to update the properties in the local property cache with the new values.
- Use SetInfo to commit the changed properties to the directory.
Acme Widgets promotes Judy to chief financial officer (CFO). You must move Judy from the Finance OU to the Executive OU, so you complete these two steps:
- Connect to the target container ("ou=Executive") that you will move the user to.
- Use ADSI's MoveHere method to move the existing user object. ADSI's IADsContainer interface exposes MoveHere.
Judy accepts a position with a competitor. You now need to delete the user object, so you complete these last two steps:
- Use VBScript's GetObject function to connect to the target container ("ou= Executive") that holds the user object.
- Use ADSI's Delete method to delete the user object. ADSI's IADsContainer interface exposes Delete.
This example incorporates the fundamental steps that are common to most ADSI scripts. You connect to an object to start every task. After you connect to an object, you use methods that two core interfaces, IADs and IADsContainer, expose to manage the object's entire life cycle.
You can apply the same basic steps to other objects in the directory. For now, let's take a closer look at the core interfaces in ADSI and apply these steps in a functional script. I use easyadsi.vbs as a reference for the discussion that follows and examine the core IADsOpenDSObject interface. Listing 1 shows the portion of the script that completes the first four steps for creating a new user object. You can download complete listings from the Windows 2000 Magazine Web site at http://www.win2000mag .com/. (Enter 9168 in the InstantDoc ID text box, and click the 9168.zip file.)
Connecting to AD
Connecting is the first step to communicating with AD; connecting to an object is known as an LDAP bind request. Binding is the point in your ADSI script at which credential authentication occurs. ADSI's in-process LDAP providers (adsldp.dll and wldap32.dll) create and return a reference to a specified object (e.g., domain, OU, group, user, printer, service, share, site, schema class).
You use VBScript's GetObject function to bind to an AD object. The string you pass to GetObject is the ADSI LDAP path that identifies the target object. To bind to an object, you can use several approaches, which are based on the syntax of the LDAP path you provide. You must begin ADSI LDAP paths with "LDAP:". You can follow the string with optional elements, as the examples in Figure 2 show.
The mandatory "LDAP:" prefix is the programmatic identifier (ProgID) for ADSI's LDAP provider. ADSI ProgIDs identify the ADSI provider DLL through which your script communicates with the underlying directory. ADSI ProgIDs are case sensitive, so you must always use all caps for the "LDAP:" prefix. The remainder of the ADSI LDAP path isn't case sensitive.
Optional LDAP path elements include the target server's name, IP address, LDAP port number, and DN. The elements you append to the "LDAP:" prefix determine how ADSI binds to AD.
Server-based binding occurs when you supply a target server name or IP address as part of the LDAP path, as the examples in Figure 3 show. You should avoid binding to a hard-coded server name or IP address because your script will fail if the server is offline. You can specify the LDAP port number if your DS is listening on a port other than the default LDAP port (port 389). However, you don't need to worry about AD listening on another port because Win2K domain controllers reserve port 389 for AD.
Serverless bind requests remove the dependency associated with LDAP paths that contain hard-coded server names, which is why using serverless paths is advisable. When you omit the target server name or IP address, ADSI issues a call to a new Win32 API—DSGetDCName—that queries DNS to locate a domain controller in the domain that the current user is logged on to. ADSI attempts to locate and connect to a domain controller at the workstation's local site based on the IP subnet. If ADSI can't locate a site domain controller, ADSI uses the first directory server that responds.
Serverless LDAP paths commonly include a DN that identifies the target object. In the bind request that callout A in Listing 1 shows, I bind to the Finance OU that resides in the acme.com domain. The string value in the strContainer variable provides this location.
Figure 4 shows bind requests that contain other forms of serverless LDAP paths. In the first example in Figure 4, I explicitly bind to the defaultNamingContext (where user, computer, group, and OU objects reside) of the acme.com domain. In the second example, I bind to a specific user object. The third example is as a DN-less bind request. In this type of request, ADSI binds to the Root Directory Service Entry (rootDSE) object (a special LDAPv3 object) and uses the rootDSE defaultNamingContext property to bind to the DIT's root. The result is identical to that of the first example, but the DN-less approach is independent of a specific domain.
You can also explicitly bind to the rootDSE object. Introduced in LDAPv3, rootDSE resides at the top of every domain controller's DIT. Unlike the DN-less (i.e., "LDAP:") approach, in which rootDSE returns only the DN for the defaultNamingContext, binding directly to rootDSE lets you access all the directory server information that the rootDSE object provides. You'll find the DNs for all the directories' naming contexts in this information. As Listing 2 shows, I use the rootDSE defaultNamingContext, schemaNamingContext, and configurationNamingContext properties at callouts A, B, and C in Listing 2 to bind to the root of each naming context and echo the top-level objects. RootDSE provides the most reliable and robust mechanism to bind to a directory.
Thus far, I've relied on my current credentials to authenticate my bind request. Although this approach is the preferred authentication method, you might encounter situations in which you need to provide alternative credentials or specify the authentication type required. ADSI's IADSOpenDSObject interface exposes the OpenDSObject method that employs user-supplied credentials to bind to an object. OpenDSObject accepts four parameters, which Table 2 lists.
Callout B in Listing 1 illustrates how to use OpenDSObject in lieu of the current user's credentials. Replace the code at callout A in Listing 1 with the code at callout B in Listing 1 to use OpenDSObject. You must use
to obtain a reference to the LDAP provider before you call OpenDSObject. You can use DN, User Principal Name (UPN), downlevel SAM account name (pre-Win2K), or Anonymous as your username.
ADSI provides a great deal of flexibility in terms of binding to AD objects. As you begin developing your own ADSI scripts, keep the following tips in mind:
- Use your current credentials whenever possible.
- Never hard-code passwords in your scripts. If you must use a password, prompt the user or retrieve the password from a secure location, store the password in a temporary variable, and destroy the variable's contents immediately following the bind request.
- Use rootDSE when your script calls for binding to the current domain's root.
- Avoid hard-coding server names in LDAP paths.
- Bind to container objects to create, move, and delete objects in the container.
- Bind to a leaf object to modify the object's properties.
In my next ADSI article, I'll examine the two remaining core interfaces in ADSI— IADs and IADsContainer. In the meantime, try connecting to some objects in your directory. For some real fun, fire up Network Monitor and try several different binding methods. The worst that can happen is that you'll learn more about the role DNS plays and about LDAP protocol mechanics.