Recently, I needed to modify an application I'd written that provides Web-based delegated administration for Windows NT. I needed to make the application work with Active Directory (AD). I set a short time line for the project because functions that work with the NT SAM usually differ from those that work against AD only by their binding string. Although that assumption proved generally true, I became frustrated trying to convert one particular function.
The troublesome function reset an account lockout condition for NT accounts. The function simply binds to the User object, calls IADsUser::IsAccountLocked property, and returns a Boolean value that represents the result. As I had with every other function in the class module, I changed the binding string from WinNT: to an appropriate LDAP: binding string and assumed it would work as all the others had before it. When it didn't, I searched the Microsoft Developer Network (MSDN) and found the Microsoft article "Programmatically Changing the Lockout Flag in Windows 2000" (http://support.microsoft.com/default.aspx?scid=kb;en-us;q250873), which explains that the Active Directory Service Interfaces (ADSI) LDAP: provider doesn't properly report the account-locked condition for AD accounts. Instead, the article offers, "To read or reset the lockout bit, use the WinNT: provider to gain access to Active Directory." The workaround is easy, but how could I convert from the Lightweight Directory Access Protocol (LDAP) type of distinguished name (DN) that the ADsPath key provides to a name that the WinNT: provider could use in the most generic, reusable way?
I found my answer in the Windows 2000 Support Tools. Among the tools, I found the IADsTools (iadstool.dll) object in the support.cab file. This COM object lets you convert from an LDAP-type DN (e.g., cn=Thomas Eck, ou=Americas,ou=Users,dc=Fiction, dc=com) to an NT-type DN of the form domain\username.
But that's not all the IADsTools object can do. I discovered that this relatively unadvertised DLL provides several capabilities, such as
- general functions for working with ADSI and AD and, to a lesser extent, native LDAP namespaces
- management and monitoring of AD replication
- management of Group Policy links
Here, I explain some general functions in the IADsTools DCFunctions class that you can use as the foundation for projects that involve AD and Group Policy. In an upcoming article, I'll talk about some of the DLL's other capabilities.
To obtain the Win2K Support Tools, navigate to support\tools on the Win2K CD-ROM. Execute setup.exe to install the tools. Then, you access the Microsoft WordPad document iadstools.doc and the COM object iadstools.dll. If you have either Microsoft Visual Basic (VB) or the VB Macro Editor that comes with most of the Microsoft Office suite tools, you can simply set a reference to IADsTools and use VB's Object Browser to look at the object model.
Instantiation and General Network Functions
You can use Windows Script Host (WSH) to run from the command line all the sample code that I discuss. The code that Listing 1 shows creates an instance of the DCFunctions class. You should include this code in all other listings.
To begin working with IADsTools, let's start with a simple example. In the same way that you use the Net Send command from the command line to send an instant message to a user, you can use the IADsTools NetSendMessage() function to send an instant message natively (i.e., without using the Shell method) from within VB and VBScript functions. After you instantiate the DCFunctions class, you call the NetSendMessage() function and specify the target machine for the message, the message originator, and the message body, as Listing 2 shows.
To query IP configuration, you can use IADsTools and Win2K Server. Simply call the GetIPConfiguration() function and specify the target server's IP address as the first argument. Listing 3 shows an example. The 0 value I specified for the second parameter tells GetIPConfiguration() to use the credentials of the currently logged-on user.
Name Conversion: NT and DNs
In working with ADSI, you might (as I discovered) occasionally need to translate between naming formats used to bind objects in AD. Typically, you need to bind a User object by using the object's LDAP DN; however, in some cases you might need to use the WinNT: provider to perform the binding or you might need to provide the downlevel DN to complete a function.
To bind the User object that Figure 1 shows, you can use one of two LDAP DNs to perform the binding. To bind using the little endian form (which ADSI uses by default), you'd use
cn=Thomas Eck,ou=Americas, ou=Users,dc=Fiction,dc=com
To bind using the big endian form, you'd use
dc=com/dc=Fiction/ou=Users/ ou=Americas/cn=Thomas Eck
You can also use the WinNT: provider and the syntax
to bind to any object in the directory. For example, you could bind the user object in Figure 1 as Fiction\teck. The object might be in an organizational unit (OU) dozens of levels below the domain's root, yet you can still use this simple syntax because domain\username will always be globally unique in the domain.
If you've ever created a user account or security group in AD, you know that several properties are necessary before you can create the object. For all user accounts and groups, AD maintains the sAMAccountName attribute, which allows backward compatibility for NT and NT LAN Manager (NTLM) clients. Although you can create objects with common names (CNs) that aren't globally unique (CNs need to be unique only within a container), AD requires a globally unique sAMAccountName.
Given the multitude of options available for binding an object, how can you programmatically translate between DN formats? If you know the target object's DN and use the LDAP: provider for the binding, you can write a function to bind to the User object, get the object's sAMAccountName, then pass the domain name that you want to use to the WinNT: provider to bind. However, the IADsTools TranslateDNToNT4() function provides a much better solution. Listing 4 shows how to use this function to perform the conversion in just a few lines of code.
Now, consider the opposite situation: You have a user object's domain name and sAMAccountName, but you need the full ADsPath in LDAP DN format. A solution that uses traditional methods would require passing significant amounts of data as arguments to your function. Instead, you can use the IADsTools TranslateNT4ToDN() function to translate between the object's downlevel and LDAP DNs. Listing 5 shows how to use TranslateNT4ToDN(), which takes four arguments. The first argument specifies the name of the account to be converted. The second argument lets you provide a password to connect to the server. The third argument lets you specify the source of the name resolution. A value of 1 causes the function to extract the information from the domain and username you pass to the function. A value of 2 uses an API call to get the domain name. The fourth argument lets you tell the function to use an alternative set of credentials for the binding. When you specify 0, the function uses the current user's credentials; when you specify 1, the function uses the credentials SetUserCredentials(), which I discuss later.
Name Conversion: LDAP and DNS DNs
IADsTools provides two functions for converting between LDAP and DNS DNs: ConvertDNSToLDAP() and ConvertLDAPToDNS(). When used carefully, these functions do a good job; however, you need to keep a few caveats in mind when you use them.
First, let's say you want to transform dc=Microsoft,dc=com into Microsoft.com. For this conversion, the IADsTools functions work quite well in both directions. But trouble arises if you pass the ConvertLDAPToDNS() function a big endian LDAP DN, such as dc=com/dc=Microsoft. In this case, the function returns an unexpected result: com/dc=Microsoft.
Additionally, the LDAP specification doesn't require you to use the form DC=x,DC=y to name the root of a directory. In a native LDAP namespace, you can also use the form O=something—doing so is common practice in both Novell Directory Services (NDS) and native LDAP namespaces such as Netscape/iPlanet Directory Server. For example, consider converting O=Airius.com. In this case, instead of returning the expected value of Airius.com, the ConvertLDAPToDNS() function returns irius.com. This problem occurs because ConvertLDAPToDNS() is merely a text-replacement tool. The function assumes that the domain name begins at position 4, but when you use the O= syntax, the domain name begins at position 3. The ConvertDNSToLDAP() function also causes problems with this type of root, converting airius.com to dc= airius,dc=com instead of to O=Airius.com, which would be proper for the namespace.
These caveats aside, when you use these functions properly, they provide reliable conversion between LDAP and DNS DNs. Listing 6 shows conversion examples and sample output.
Often, you might want to run a function under a user account that's different from the logged-on user. ADSI lets you use the IADsOpenDSObject method to specify the user and password you want to use for the object binding. The IADsOpenDS-Object method lets you use either the DN or user principal name (UPN) that represents the user. However, IADs-Tools isn't part of ADSI and doesn't take advantage of the impersonation technique that ADSI's IADsOpenDS-Object method provides.
Many of the IADsTools functions let you use an alternative set of credentials to perform an action. You use the SetUserCredentials() function to specify the alternative credentials you want to use. Listing 7 shows how to use the SetUserCredentials() function. The first and second string parameters let you specify a username and domain, respectively, whereas the third parameter lets you specify an LDAP DN—you simply use the format that fits your situation.
After the system validates the credentials, you can use them by passing the value 1 for the UserCreds argument of any IADsTools function that uses the argument. For example, Listing 8 shows code that uses the current user's credentials to translate a downlevel DN to an LDAP DN. Listing 9 shows code that uses alternative credentials for the same function. To stop impersonating another account, simply call ClearUserCredentials(), as Listing 10 shows.
A Valuable Resource
Now you've seen some of the elements of the IADsTools library, including basic network functions, impersonation, and conversion between naming formats for various types of DNs. In a future article, I'll show you how to put some of these functions to practical use managing and monitoring AD replication.