Extending the Active Directory Schema
At a Glance:
- Understanding the default Active Directory schema
- Adding classSchema or attributeSchema objects
- Getting and using object identifiers
- Extending your schema using .ldif files
Download the code for this article: Schema2008_05.exe (151KB)
Since the introduction of Active Directory with the release of Windows 2000, Microsoft has provided customers with the base schema definition for implementing Active Directory.
The presence of Active Directory® also marked a shift in the way many applications were written and implemented on Windows®. Before then, applications such as Microsoft® Exchange 5.5 were built to contain their own directory structure. As Active Directory became available, many applications (both from Microsoft and other companies) started to take advantage of the underlying structure it provided, instead of building their own schema from the ground up.
These applications began with the basic architecture provided by Active Directory, then extended it as needed. Microsoft Exchange 2000, for example, utilized Active Directory for messaging implementations, thereby defining the future of the Microsoft messaging architecture.
Today, many applications written to work in an Active Directory environment rely on its underlying schema in order to function, and many also define their own changes to the schema as needed. This, of course, requires a schema capable of extension, as I'll discuss in this article. Moreover, since so many applications are dependent on the base definitions in Active Directory, the continued stability of the core schema is critical. Since many applications need to work alongside one another in the same Active Directory, changes made for any one application must not impact the others.
What Is a Schema?
To many, the Active Directory schema is a black box, and the idea of modifying the schema on one's own can be intimidating. Of course, extending your Active Directory schema is not something you'll need to do every day, but certain applications or business needs may require doing so. Thus it is very important to understand what a schema is and what it contains, since Active Directory is a vital asset in many organizations, and having it malfunction due to an incorrect update can have a very significant impact.
As a strategy, there are many organizations that consider using Active Directory Lightweight Directory Service (ADLDS) in Windows Server® 2008 (or Active Directory Application Mode, ADAM, in Windows Server 2003) as an alternative for testing or for directly implementing the customized schema definitions instead of extending the Active Directory schema.
A schema is the underlying structure that provides the format for a directory service. The Active Directory schema defines the object classes and attributes that are used in Active Directory Domain Services (ADDS). The core schema provides definitions for many well-known classes (such as user, computer, and organizationalUnit) and attributes (such as telephoneNumber and objectSID). The objects present in the core schema definition are known as Category 1 objects, and objects that are added are called Category 2 objects.
An Active Directory schema can be found in the container defined at the path cn=Schema, cn=Configuration,dc=X, where X is the namespace of the Active Directory forest. Note that an Active Directory forest contains just a single schema; making changes to the schema definition in a forest affects all the domains in that forest. Figure 1 shows the number of classes and attributes that are added in the Active Directory schema in different versions of Windows Server.
Figure 1 Number of classes and attributes
|Version||Number of Attributes Added||Number of Classes Added||Schema Extension Files|
|Windows Server 2003||208||49||Sch14.ldf to sch30.ldf|
|Windows Server 2003 R2||81||29||Sch31.ldf|
|Windows Server 2008||136||10||Sch32.ldf to sch44.ldf|
The schema update for different versions of Windows Server is done using a utility named Adprep. With the updates for Windows Server 2003 R2, the schema version gets updated to 31, and it changes to 44 with Windows Server 2008.
This can be verified by checking the value of the objectVersion attribute of cn=Schema,cn=Configuration,dc=X in your Active Directory using a tool such as ADSIEdit. Note that some applications such as Exchange Server, System Management Server (SMS), or others that are dependent on Active Directory may modify the schema according to the application needs.
Bricks and Mortar
Active Directory consists of two types of objects: classSchema (class for short) and attributeSchema (attribute for short). Extending the Active Directory schema is typically considered when an organization wants to store data in certain attributes that are not available in the existing schema. You create an attribute in an Active Directory schema by first specifying an attributeSchema object in the schema container and then defining the necessary properties for the new object.
You'll find a list of properties for attributeSchema objects along with information about them at go.microsoft.com/fwlink/?LinkId=110445. As you can see, there are many properties that can be defined for attributeSchema objects; a number of these properties are required.
Apart from the regular attributes, there are also special attributes called Linked attributes within a schema that are implemented in pairs by providing a forward link and a backward link. As an example, consider group membership in Active Directory. The member attribute of any group (for example, a group named ContosoEmployees with a member named John Doe) is the forward link, and the corresponding memberOf attribute of the member object is the back link (so that, for example, when the memberOf attribute of John Doe is queried, the distinguished name, or DN, of ContosoEmployees group is calculated).
The forward link behaves much like any other attribute. The values can be single-valued or multivalued (as with the member attribute, which can contain multiple objects as members of a group) and are stored together with the parent object in the directory.
Back links, on the other hand, are maintained by the system to ensure referential integrity. When you query for the value of a back link attribute, the results will be calculated from all the matching forward link values. Back links are always multivalued.
Each object class in ADDS is defined by a classSchema object in the schema container. You'll find a list of the attributes critical to the successful definition of the classSchema object at go.microsoft.com/fwlink/?LinkId=110445.
There are three types of classes that can be specified: Structural, Abstract, and Auxiliary. These are defined by the value of objectClassCategory attribute. (A fourth category, known as 88, includes classes defined before X.500 1993 standards. This type of class is specified by a value of 0 in the objectClassCategory attribute. This type of class should not be defined any longer.)
Getting and Using Identifiers
The identities of each classSchema and attributeSchema object in the directory are defined using a mandatory object identifier (OID), which is defined against governsID for classSchema objects and attributeID for attributeSchema objects. These are unique numeric values supplied by certain issuing authorities to identify the objects. The numbering is governed by definition of the LDAP protocol (RFC 2251). Some of the OIDs in the Active Directory schema are issued by the International Organization for Standardization (ISO) and some are issued by Microsoft. An OID must be unique for an object within the directory.
The OID is a string of numbers, for instance 1.2.840.113556.1.y.z, as described in Figure 2. Thus an OID for a user classSchema object, for example, is 1.2.840.1135184.108.40.206.
Figure 2 Identifier for the user object
|1||ISO||Identifies the root authority.|
|2||ANSI||Group designation assigned by ISO.|
|840||USA||Country/region code assigned by the organization.|
|113556||Microsoft||Organization designation assigned by the country/region.|
|1||Active Directory||Assigned by organization (by Microsoft, in this case).|
|Y||Object Type||Number defining the different object type (category) such as classSchema or attributeSchema. For example, 5 defines object class.|
|Z||Object||Number identifying a particular object within the category. For example, the user class has the number 9 assigned to it.|
When an organization intends to extend the schema, it ensures that the OID is unique by obtaining its own OID root number, which is then branched off to provide unique IDs to the new object classes and attributes that the organization creates. The OID root may be obtained directly from an ISO National Registration Authority (NRA), which in the United States is the American National Standards Institute (ANSI).
You can get the procedure and fee schedule for obtaining a root OID at ansi.org. For other regions, contact the corresponding ISO member organization; ISO offers a list at iso.org/iso/about/iso_members.htm.
Organizations used to be able to obtain an OID from Microsoft by sending e-mail to firstname.lastname@example.org. However, that now results in an automated reply prompting the requester to download and run the VBScript from go.microsoft.com/fwlink/?LinkId=110453.
For the OIDs that are issued by Microsoft, the number is assigned under the Microsoft OID number space: 1.2.840.113556.1.8000.x, where x is a unique number assigned to the organization. The organization may further divide these OIDs to specify the objects. Thus an organization might use 1.2.840.113556.1.8000.x.1.y for new classSchema objects and 1.2.840.113556.1.8000.x.2.z for attributeSchema objects (where x represents the organization's unique number, and y and z, respectively, are the numbers assigned for specific classSchema and attributeSchema objects). It is also recommended that an organization-specific prefix be used in the names of these objects to distinguish them.
Defining Linked Attributes
It is important that the attributeSyntax value of a forward link be 220.127.116.11, 18.104.22.168, or 22.214.171.124. These values correspond to syntaxes that contain a distinguished name, such as the Object (DS-DN) syntax.
The attributeSyntax value of a back link must be 126.96.36.199, which is the Object (DS-DN) syntax. By convention, back link attributes are added to the mayContain value of the top abstract class. This enables the back link attribute to be read from objects of any class because back link attributes are not actually stored with the object; instead they are calculated based on the forward link values.
Windows Server 2003 introduced a feature organizations could use to link two objects in a schema: auto-generation of linkIDs. With this feature, the system automatically generates a linkID for a new linked attribute when the attribute's linkID attribute is set to 1.2.840.1135188.8.131.52. A corresponding back link is created by setting the linkID to the attributeId or ldapDisplayName of the forward link. The schema cache must be reloaded after creating the forward link and before creating the back link. Otherwise, the forward link's attributeId or ldapDisplayName will not be found when the back link is created. The schema cache is reloaded on demand, a few minutes after a schema change is made or when the domain controller is rebooted.
If your Active Directory is at Windows 2000 level, you will need to request linkIDs from Microsoft by sending e-mail to email@example.com. Within the automated response, you'll see this line: "E-mails sent to firstname.lastname@example.org will be processed only if they are related to linkID registrations for legacy environments." For this, provide the following information in the e-mail: company name, contact name, e-mail address, phone number, prefix registered (if applicable), OID registered (if applicable).
Ready to Extend the Schema
Let's say you've decided to extend your Active Directory schema. This analysis may have involved discounting the use of an alternate directory implemented using ADLDS (or ADAM in Windows Server 2003) after making sure it would not meet the requirements. As a next step, you have identified the new attributeSchema objects you want to add to the schema and, in doing so, you have defined all the required values (such as cn, ldapDisplayName, and so forth) that specify these new objects. In defining the attribute values for the object, you have also obtained the OID from Microsoft or from another source. You have essentially documented the above as business requirements and technical specifications. Moreover, you have also implemented a lab environment that mimics your Active Directory and is ready for testing.
Many organizations actually set up committees to approve or deny such changes and to set the process for implementing them. The need for such checks and balances is crucial since Active Directory is used as an authoritative information source in many organizations, and the importance of keeping it up and running after changes are made cannot be stressed enough.
Once the organization decides to move ahead, you must define test and implementation plans for this project. You may extend the schema by either adding the new objects using the Active Directory schema snap-in within the Microsoft Management Console (MMC) or by using programmatic or semi-programmatic methods to extend the schema (such as using LDIFDE to import .ldif format files; CSVDE to import .csv format files; or with Active Directory Service Interfaces, ADSI, scripting).
Whichever method you use, this function must be performed on a server that is either connected to or holds the Flexible Single Master Operations (FSMO) role of Schema Master in the Active Directory forest. Also, the account you use for the schema update needs sufficient admin privileges in order to perform the update, so make it a member of the Schema Administrators group. Finally, you must enable schema updates for the forest (this is disabled by default).
Unless the change is very simple, it should be made in an automated fashion in order to foster standardization between the test and production implementation phases and to reduce the sorts of errors likely to result by performing the steps manually. Let's suppose you decide to implement your change using LDIFDE. To apply updates when extending the schema, you would add the new attributes and classes, add the new attributes to classes, and then trigger a cache reload. Let's walk through a couple of scenarios.
Suppose, for the sake of argument, an organization named Contoso wants to add an attribute into its Active Directory that identifies the shoe size of each employee. The Active Directory forest has two domains: contoso.com and employees.contoso.com. The requirement is that all the objects created using the user class definition should also contain this new attribute.
It's important for you to keep in mind that the schema change will affect both domains since they are in the same forest. Let's assume you have received the OID of 1.2.840.113556.8000.9999 from Microsoft and that you have further subdivided it into 1.2.840.113556.8000.9999.1 for classSchema and 1.2.840.113556.8000.9999.2 for attributeSchema objects for Contoso. Now you are going to define all the attribute values for this new object, as shown in Figure 3.
Figure 3 Defining the contosoEmpShoe attribute
|attributeSyntax||184.108.40.206||Specifies a Unicode string.|
|oMSyntax||64||Indicates a Unicode string.|
|attributeID||1.2.840.113556.8000.9999.2.1||As defined by the organization.|
|isSingleValued||TRUE||Only one shoe size value can be stored.|
|searchFlags||1||Your analysis shows that you want to index this attribute. Note: you will perform the stress test analysis in the lab environment.|
|isMemberOfPartialAttributeSet||TRUE||You want this attribute to be available in the Global Catalog.|
Further, while the contosoEmpShoe attribute needs to be available for all objects created as user class objects, it is not recommended that you modify the default definition of the user class. Instead, you should define an auxiliary class named contosoUser that has the value of mayContain specified as contosoEmpShoe, as shown in Figure 4. Then you add the attributes defined for the contosoUser auxiliary class to the user class.
Figure 4 Defining contosoUser class
|governsID||1.2.840.113556.8000.9999.1.1 (as defined by the organization)|
|objectClassCategory||3 (defines an auxiliary class)|
Now that you have performed the analysis and defined the values, it is time to create the .ldif file, which will look like the code in Figure 5. You can copy the contents of Figure 5 into Notepad and save the file as contosoUser.ldif (or you can also find a copy in the code download on technetmagazine.com).
Figure 5 .ldif file for extending your schema
#Attribute definition for contosoEmpShoe dn: CN=contosoEmpShoe,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemaadd objectClass: top objectClass: attributeSchema cn: contosoEmpShoe attributeID: 1.2.840.113556.1.8000.9999.2.1 attributeSyntax: 220.127.116.11 isSingleValued: TRUE adminDisplayName: contosoEmpShoe adminDescription: contosoEmpShoe oMSyntax: 64 searchFlags: 1 lDAPDisplayName: contosoEmpShoe systemOnly: FALSE dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - # Classes dn: CN=contosoUser,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemaadd objectClass: top objectClass: classSchema cn: contosoUser governsID: 1.2.840.113556.1.8000.9999.1.1 mayContain: contosoEmpShoe rDNAttID: cn adminDisplayName: contosoUser adminDescription: contosoUser objectClassCategory: 3 lDAPDisplayName: contosoUser name: contosoUser systemOnly: FALSE dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - dn: CN=User,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemamodify add: auxiliaryClass auxiliaryClass: contosoUser - dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1
After generating the .ldif file, you'll want to test the implementation thoroughly in a lab environment, verify the end-to-end replication of your domain and forest, and enable the updating of the schema in the forest. At this point, you should login with an account that has Schema Admin privileges. You may want to disable outbound replication on the schema master (where the change will be performed) and then run the following command to import the .ldif file:
ldifde –i –f <Path>\contosoUser.ldif –b <username> <domain> <password> -k –j. –c "CN=schema,CN=Configuration,DC=X" #schemaNamingContext
Once the change takes place, enable outbound replication on the schema master and verify that the replication has happened for all domain controllers.
Take a deep breath—you're finished! You defined a new attribute in the schema that will be associated with the objects created using the user class (that is, user accounts).
To verify the change, open Active Directory Users and Computers, connect to the employees.contoso.com domain, browse to the Users organizational unit (OU), and create a new user account named ContosoTestUser. Now open the adsiedit.msc console and connect to domain partition dc=employees,dc=contoso,dc=com, expand the Users OU, right-click on the ContosoTestUser, and open the Properties page. Browse to find the contosoEmpShoe attribute. You may edit this attribute to enter a value. You can also use the Ldp.exe utility to verify and modify the attributes.
Now let's look at an example that defines and links two attributes, and let us imagine a scenario where Contoso puts high importance on employee shoe sizes and wants the annual performance of the people who take shoe sizes in the company to be tracked somehow. While this may sound funny, let's further suppose Contoso wants to keep track of not only who was responsible for measuring employee shoe sizes but also whose shoe sizes they measured and how many—all by querying one attribute. (You may think this data is more appropriately stored in database tables, but the idea here is simply to explain the workings of forward and backward links.)
Of course, you would first go through the same kind of analysis I noted in the earlier example. Here, however, let's move forward to generate the .ldif files linkids1.ldif and linkids2.ldif, as shown in Figure 6. Then run this command to import the .ldif files:
Figure 6 Forward and backward link .ldif files
#linkids1.ldif #Attribute definition for Forward Link Attribute dn: CN=ContosoShoeSizeTaker,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemaadd objectClass: top objectClass: attributeSchema cn: ContosoShoeSizeTaker attributeID: 1.2.840.113556.1.8000.9999.2.2 LinkID: 1.2.840.113518.104.22.168 attributeSyntax: 22.214.171.124 isSingleValued: TRUE adminDisplayName: ContosoShoeSizeTaker adminDescription: ContosoShoeSizeTaker oMSyntax: 64 searchFlags: 1 lDAPDisplayName: ContosoShoeSizeTaker systemOnly: FALSE dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - #Reload schema #linkids2.ldif #Attribute definition for Backward Link Attribute dn: CN=ContosoShoeSizesTakenByMe,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemaadd objectClass: top objectClass: attributeSchema cn: ContosoShoeSizesTakenByMe attributeID: 1.2.840.113556.1.8000.9999.2.3 LinkID: 1.2.840.113556.8000.9999.2.2 attributeSyntax: 126.96.36.199 isSingleValued: FALSE adminDisplayName: ContosoShoeSizesTakenByMe adminDescription: ContosoShoeSizesTakenByMe oMSyntax: 64 searchFlags: 1 lDAPDisplayName: ContosoShoeSizesTakenByMe systemOnly: FALSE dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - #Add ContosoShoeSizeTaker and ContosoShoeSizesTakenByMe Attribute as MayContain in #contosoUser class dn: CN= contosoUser,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemamodify add: mayContain mayContain: ContosoShoeSizeTaker mayContain: ContosoShoeSizesTakenByMe dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 - #Add Backward Link Attribute as MayContain in Top dn: CN=Top,CN=Schema,CN=Configuration,DC=X changetype: ntdsschemamodify add: mayContain mayContain: ContosoShoeSizesTakenByMe dn: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1
ldifde –i –f <Path>\linkedids<>.ldif –b <username> <domain> <password> -k –j. –c "CN=schema,CN=Configuration,DC=X" #schemaNamingContext
Now when a user object is created, it will also have ContosoShoeSizeTaker and ContosoShoeSizesTakenByMe as its attributes. While creating the user object for, say, John, the ContosoShoeSizeTaker attribute is filled with the DN of the person who took the shoe size, the person named Frank. Now when you go into Frank's user object's properties and query the ContosoShoeSizesTakenByMe attribute, the result will contain John's DN and any others that Frank may have taken. To complete our scenario, the management may reward Frank based on how many DNs exist in his user account's ContosoShoeSizesTakenByMe attribute.
System Checks and Balances
A critical update like schema modification cannot be performed without checks against architectural requirements. These consistency checks and safety checks are used by Active Directory to verify that the changes do not cause any inconsistencies or other problems whenever an addition or modification is made to the Active Directory schema.
First, the value of governsID for each class must be unique within the schema. When defining a schemaClass object, all the attributes that are defined in the systemMayContain, mayContain, systemMustContain, and mustContain lists must already exist. At the same time, all classes that are defined in the subClassOf, systemAuxiliaryClass, auxiliaryClass, systemPossSuperiors, and possSuperiors lists must already exist, too.
Further, the objectClassCategory of all classes in the systemAuxiliaryClass and auxiliaryClass lists must be either 88 class or Auxiliary class. Similarly, the objectClassCategory of all classes in the systemPossSuperiors and possSuperiors lists must be specified as either 88 class or Structural class.
While defining various classes, Abstract classes can inherit only from other Abstract classes, Auxiliary classes cannot inherit from Structural classes, and Structural classes cannot inherit from Auxiliary classes. Also, the attribute specified in the rDNAttID attribute must have Unicode-string as its syntax and be single-valued.
These are some of the rules that relate to classSchema objects. What about those for attributeSchema objects? As with the governsID value for classes, the value of attributeID must be unique. Also, the value of mAPIID, if any, must be unique. Further, if rangeLower and rangeUpper are present, rangeLower must be smaller than rangeUpper. The values of attributeSyntax and oMSyntax must match. If the attribute is object-syntaxed (oMSyntax =127), it must have the correct oMObjectClass. The linkID, if any, must be unique. In addition, a back link must have a corresponding forward link.
What if You Goof?
Once the schema has been extended with the new objects (classes and attributes), they cannot be deleted. However, a class or attribute can be deactivated by setting the attribute isDefunct to TRUE on the schema object. You cannot deactivate schema objects that are part of the default schema that ships with Active Directory (Category 1 objects). You can only deactivate objects that have been added to the default schema; that is, only Category 2 objects can be disabled and only when Active Directory has verified that the class is not used in the subClassOf, auxiliaryClass, or possSuperiors list of any existing non-defunct class.
With any attempt to make an attribute defunct, Active Directory checks that the attribute is not used in the mustContain or mayContain of any existing non-defunct class. The disabled objects can be resurrected by setting the attribute isDefunct to FALSE. If your Active Directory is at the Windows Server 2003 level, you can reuse the ldapDisplayName, schemaIdGuid, OID, and mapiID values of the disabled object.
Adding or modifying class or attribute definitions in the schema involves adding or modifying the corresponding classSchema object or attributeSchema object. This process is similar to adding or modifying any object in Active Directory, except that additional checks are performed to ensure that changes do not cause inconsistencies or problems in the schema in the future.
While modifying the Active Directory schema is straightforward, it's important to understand the formation of schema and how it functions to implement these changes. Any change to production Active Directory schema requires a lot of planning and must be done carefully. It's critical that you define the business requirements and technical specifications for new objects and perform a great deal of lab testing. Since changes can have profound effects, it is recommended that you only extend the Active Directory schema when it is absolutely necessary.
Vikas Malhotra is with Microsoft Consulting Services in New York. During his 12 years in the business, he has worked with many IT organizations in large and mid-range companies on their infrastructure architecture needs such as directory, messaging, security, and data networking. He holds a Bachelor of Engineering in Electronics and Telecommunications and a Master of Science in Telecom (Tech) Management. Contact him at email@example.com.
© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.