Using LDIF Files to Extend the AD Schema

The Active Directory (AD) schema contains the classes and attributes that define the types of objects that you can create in AD and the properties that you can configure with them. Domain controllers (DCs) store the schema as a set of objects inside the directory, whereas most Lightweight Directory Access Protocol (LDAP) servers store the schema as a collection of text files outside the directory. Storing the schema as objects in AD's Schema container has several advantages. Most notably, you can use tools and scripts to modify the schema as you would other types of objects.

A popular way to extend the AD schema is to use the LDAP Data Interchange Format (LDIF), which the Internet Engineering Task Force (IETF) Request for Comments (RFC) 2849 defines. All major directory vendors support LDIF, so tools that use LDIF to import and export directory data are readily available. For example, the LDIF Directory Exchange utility (Ldifde—a command-line tool in Windows 2000 and later) and the Perl Net::LDAP modules use LDIF files to import and export AD data. Let's look at how you can use Net::LDAP to automate not only importing LDIF files into the schema but also verifying the schema extensions to reduce the potential for errors during the import. But first, let's take a quick look at how LDIF files work and how to install the Net::LDAP modules.

An LDIF Primer
Directory-enabled applications that store data in AD typically come with LDIF files that you can use to extend the schema. Figure 1 shows a sample LDIF file that extends the AD schema by creating a new attribute object and adding it to the User class.

The first LDIF entry in Figure 1, which callout A shows, creates an object that represents a new attribute called rallencorp-LanguagesSpoken. The first line of every LDIF entry begins with dn followed by the object's distinguished name (DN). The next line starts with changetype followed by one of three options: add (if you want to create an object), modify (if you want to modify an object), or delete (if you want to delete an object). When you select add, the rest of the lines in the entry set the object's attributes. These lines follow the format

attributeName: Value

where attributeName is the attribute's name and Value is the value that you want to assign to that attribute. For multivalued attributes, you specify the attribute multiple times. For example, the lines

description: My description 1
description: My description 2

set two values (i.e., My description 1 and My description 2) for the multivalued attribute named description. All LDIF entries end with a blank line.

The second LDIF entry in Figure 1, which callout B shows, is necessary to reload the schema cache. DCs maintain a copy of the schema on disk in the ntds.dit file and in memory in the schema cache. Whenever you add or modify the schema, the system immediately writes the changes to disk but doesn't update the schema cache for about 5 minutes. If you create an object, then reference it later, as I do in Figure 1, you must reload the schema cache or an error will occur.

Although the entry at callout B is only six lines long, it has several important characteristics worth discussing. You might have noticed that the dn line doesn't reference a DN. This omission isn't an error. A blank dn line equates to the RootDSE object, which you can use to retrieve the Schema container's DN (which I discuss how to do later). The changetype line specifies that I want to modify that object's attributes. The next line specifies the type of modify operation to perform. You can specify add (if you want to add a value to an attribute that isn't currently populated), replace (if you want to replace an attribute's current value with a new value), or delete (if you want to remove the attribute's current value). In this case, I'm adding a value to the schemaUpdateNow attribute, which is an operational attribute that dynamically reloads the schema cache. The fourth line assigns the value 1, which enables the schema cache reload. The fifth line contains a hyphen (-), which you must include in all modify entries. If you want to modify a different attribute of the same object, you simply place the information about that attribute's modify operation directly after the line containing the hyphen. In other words, you don't have to include the dn and changetype lines again. Web Figure 1 (http://www.winnetmag.com/windowsscripting, InstantDoc ID 41194) shows a sample LDIF entry that modifies two attributes of the same user object. If you don't want to modify any more attributes of an object, you simply add a blank line after the hyphen line to signify the end of the modify entry.

The last entry in Figure 1, which callout C shows, modifies the User class to include the new rallencorp-LanguagesSpoken object as part of the mayContain attribute. This entry is similar to the entry at callout B. When setting any attribute's value, you need to know its syntax. In this case, mayContain requires that you specify an lDAPDisplayName as its value.

You might have noticed that Figure 1 doesn't include an example of a delete entry. Unlike other types of objects, AD doesn't allow class and attribute object deletions.

This review of LDIF was fairly quick. If you have questions about LDIF, you can check out RFC 2849 at http://www.faqs.org/rfcs/rfc2849.html. For information about the AD schema attributes and their syntax, go to http://msdn.microsoft.com/library/en-us/adschema/adschema/active_directory_schema.asp.

Installing Net::LDAP
Before I show you how to use the Net::LDAP modules to automate importing LDIF files, let's review how to install the modules. If you've set up the Comprehensive Perl Archive Network (CPAN) shell, you can install the modules from it by running the following two commands:

perl -MCPAN -e shell
install Net::LDAP

The CPAN shell requires quite a few programs to work correctly. Consequently, if you haven't set it up before, you might find directly downloading the Net::LDAP modules easier. You can find the latest Net::LDAP version and its online documentation on the Perl-LDAP Homepage (http://perl-ldap .sourceforge.net). If you're new to installing Perl modules, I recommend that you go to the "What To Do Once You've Downloaded A Module From The CPAN" Web page (http://www.cpan.org/modules/INSTALL.html) for instructions about how to install modules for your particular platform.

At the time of this writing, Net::LDAP 0.29 was the latest version, so the script ImportLdapFile.pl, which Listing 1 shows, uses this version. To determine which Net::LDAP version (if any) is installed on your machine, run the command

perl -MNet::LDAP -e "print
$Net::LDAP::VERSION"

(Although this command appears on two lines here, you would enter it on one line on the command line. The same holds true for the other multiline commands in this article.)

Understanding the Script
ImportLdapFile.pl provides a template from which you can learn the basic steps to extend the schema by importing an LDIF file. This script handles input from the command line, retrieves the Schema container's DN, verifies the LDIF file's schema extensions to ensure the file imports correctly, and imports the file.

Step 1: Handling command-line input. The code at callout A in Listing 1 makes sure that users provide four parameters when they launch the script from the command line. The first parameter must be the name or IP address of the DC that has the Schema Flexible Single-Master Operation (FSMO) role. The second parameter must be the user principal name (UPN—e.g., administrator@rallencorp.com) or DN (e.g., cn=administrator,cn=users,dc=rallencorp,dc=com) of a user in the Schema Admins group. The third parameter must be the user's password. The fourth parameter must be the path to the LDIF file. When all four parameters are present, the script stores them in the $server, $user, $passwd, and $ldif_file variables, respectively. If one or more parameters are missing, the script returns an error message and quits.

Step 2: Getting the Schema container's DN. Using the name and password specified on the command line, the code at callout B employs the Net::LDAP module to connect and bind to the specified server. The script then performs a search against the RootDSE object to retrieve the schemaNamingContext attribute's value, which is the Schema container's DN.

Step 3: Verifying the schema extensions. The code at callout C uses the retrieved DN to open and read the specified LDIF file, then verifies the schema extensions. The script opens the file with the Net::LDAP::LDIF module's new() method, which takes three parameters. The first parameter is the path to the LDIF file, which the $ldif_file variable provides in this case. The second parameter indicates whether to read ("r") or write ("w") to the file. ImportLdapFile.pl is configured to read the LDIF file because the script is importing the file's contents. If you write a script to export an LDIF file's contents, you would use "w" as the parameter. The third parameter, called onerror, determines what happens if the Net::LDAP::LDIF module's read_entry() method (which the script uses to read the LDIF file) encounters an error. The script sets onerror to 'undef', which means that the read_entry() method will return the value undef if it encounters an error. If you prefer to have the script print out an error message and terminate if an error occurs, you can set onerror to 'die'. If you want the script to only print out an error message, you can set onerror to 'warn'.

After opening the LDIF file, the script uses the read_entry() method in a while loop to read each LDIF entry. If the script finds an error while reading an entry, the script exits the loop. When no error occurs, the script performs a search in AD. When the LDIF entry's changetype line specifies add, the search verifies that the object to be added doesn't already exist. If the entry exists, the script reports an error. When the changetype line specifies modify or delete, the search verifies the existence of the object to be modified or deleted. If the object doesn't exist, the script reports an error.

The Net::LDAP::LDIF module's eof() method returns true when the loop reaches the end of the file. At that point, assuming that no errors occurred, the script prints the message Verification complete. If errors occurred, the script aborts.

Step 4: Importing the LDIF file. When the verification process in Step 3 is successful, ImportLdapFile.pl imports the LDIF file in AD, as the code at callout D shows. Like the verification code, the import code uses the new() method to open the LDIF file and uses the read_entry() method in a while loop to read each entry in the file. When no read errors occur, the script calls the update() method. This method submits class and attribute additions and modifications to the AD schema. The while loop continues until the end of the file unless the code() method returns a value, indicating that an error occurred during the update process. In that case, the script aborts.

If you want to use ImportLdapFile.pl to import an LDIF file like that in Figure 1, I highly recommend that you do so in a test forest first. You should never use LDIF or any other tool to extend the schema in a production forest until you thoroughly test the schema extensions. To import the LDIF file in Figure 1 into your forest, you need to customize that file, including replacing dc=rallencorp,dc=com in the DN with your forest's root domain.

You don't need to customize any code in ImportLdapFile.pl because you specify all the customized information on the command line. For example, the following command imports the LDIF file C:\myldif.ldf into the schema on a DC named dc01 while running under the administrator user account, which has the password xyz123:

After the script successfully finishes, you should verify the changes in AD. You can view the classes and attributes you added or modified in the Microsoft Management Console (MMC) Schema Manager snap-in.

The Basics and Beyond
You should now understand the LDIF basics and how to use the Net::LDAP modules to automate importing LDIF files to extend the schema. With this basic understanding, you can adapt ImportLdapFile.pl to meet your needs. For example, you might make the verification portion of the script more robust by adding more elaborate checks. Or you might expand the script's capabilities through such tools as the Schema Manager snap-in or the Ldifde utility. Ultimately, by automating the schema verification and extension process, you should have more peace of mind about extending the schema and allowing applications to take advantage of AD's power.