A case of a disappeared schema attribute or schema limbo in progress

Disclaimer: Do NOT in any way attempt to perform the steps outlined in this post in your production environment. If you do this, things will break. Take my word on it !

Disappeared attribute ? How that can happen ? I mean, either the attribute is in the schema or not, and there is no way to delete something from schema. The only option available to prevent attribute from being used is to defunct it, effectively making it impossible to use the attribute. The only way to do that, that I was aware till now, was to set isDefunct attribute of the schema object to True (given that it is not used by another active schema object), but you live and learn and sometimes your assumptions turn out to be not quite correct. Here is a story of a case I was involved in, where we had a customer loosing an attribute without setting its isDefunct to True.

Customer was trying to make user pictures available in the GAL using the instructions in the following blog post. The only caveat was that the customer did not run forestprep to extend the AD schema to W2K8 or W2K8 R2 version, which is a requirement for the Picture attribute to become available in the GAL. If you do some research, you will actually find that W2K8 schema extension has the following in Sch40.ldf file:

  1: dn: CN=Picture,CN=Schema,CN=Configuration,DC=X
  2: changetype: ntdsSchemaModify
  3: replace: mapiId
  4: mapiId: 35998
  5: -

What this particular part does is to set the mapiId attribute of Picture classSchema object in the Schema to the value of 35998.

“Well, if that is the only requirement needed for the change, why do I need to run the forestprep ?” – the customer asked himself and started looking for ways to perform this single change without rolling out fully blown W2K8/R2 schema extension. Now that IS dangerous, as we are dealing here with the base schema, which is expected to be modified only by controlled procedures and tools shipped by the base AD schema owner (read: “forestprep by Microsoft”) and if you change something, forestprep comes in later and overrides whatever you changed and you loose data or functionality, you will find yourself in the position where you can blame only yourself. Right… We warned you. Remember all those disclaimers that things might break ? Well, this is how that can happen.

What are the base Schema objects (aka Category 1 classes and attributes) ?

From http://technet.microsoft.com/en-us/magazine/2008.05.schema.aspx:

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.


Are there any restrictions imposed on Category 1 schema objects ?

From http://msdn.microsoft.com/en-us/library/ms677924(VS.85).aspx:

  • You cannot change the following attributes of a Category 1 attribute:

    • rangeLower and rangeUpper (value range).
    • attributeSecurityGuid (determines which property set the attribute belongs in, if any).
  • You cannot change the defaultObjectCategory of a Category 1 class.
  • You cannot change the objectCategory of any instance of a Category 1 class.
  • You cannot make a Category 1 class or attribute defunct.
  • You cannot change the lDAPDisplayName of a Category 1 class or attribute.
  • You cannot rename a Category 1 class or attribute.

At that point the customer decides that he manually edits the schema (should I repeat myself and remind that editing base schema objects is a bad idea ?) and tries to edit the “CN=Picture,CN=Schema,CN=Configuration,DC=X” attributeSchema object and set mapiId attribute to 35998, but is faced with a nice dialog from ADSIEDIT that informs that the attribute is owned by System and can not be edited:

image

Remember those Category 1 schema objects ? Well, thumbnailPhoto (aka Picture) attribute is Category 1 schema object:

image

But that does not scare our customer and he digs the net and discovers the “s c h e m a U p g r a d e I n P r o g r e s s” trick (the spaces are intentional. Just another step to make it harder for people to shoot themselves in the foot)… Now let me tell you something about that functionality: You should never ever use it. It is dangerous as it relaxes a number of restrictions and consistency checks that are performed during schema modifications. If you come to a conclusion that this is your only option, do yourself a favor and open a support incident with our customer support, or else, you might get into the situation where your only option would be a full forest recovery. Let me repeat myself: FULL FOREST RECOVERY.

And this is where things went wrong… Customer used the above mentioned trick and accidentally set mapiID attribute to 35998 on 2 objects:

  • CN=Picture
  • CN=MAPI-ID

See the irony ? MAPI-ID attributeSchema object in the schema has an attribute mapiId. So basically the attribute kind of describes itself:

image

Want to guess what happens when two attributes in the schema have the same MapiId ?

Here we go:

Event Type:        Warning Event Source:    NTDS General Event Category:                DS Schema Event ID: 1853 Date:                     1/19/2011 Time:                    6:44:20 AM User:                     NT AUTHORITY\ANONYMOUS LOGON Computer:          SRVDCHQ01 Description: The mapiID value for the following attribute duplicates the mapiID value for an existing attribute.   Attribute: thumbnailPhoto (160023, 160023) Existing attribute: mAPIID (20031, 20031)   Both attributes are considered defunct (as if the isDefunct attribute value were TRUE). The condition will resolve itself after the schema directory partition has replicated successfully.   User Action If this event continues to occur, initiate a replication cycle with all replication partners of the local domain controller. If the condition persists, deactivate one of the above classes by setting the isDefunct value to TRUE.   For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

Huston, we have a problem ! We have two attributes auto-defuncted because of the MapiID collision. But hey, the event ID is providing us a solution: defunct one of those two attributes and you are done. Well… there is a catch: you can’t defunct an attribute that is used by schema classes and as it happens, this is the case for both attributes in question.

So what are our options ? Well… roughly we have to perform the following surgery:

  1. Identify the attribute that is used less in the schema (we do want to minimize the schema changes)

    • Selecting thumbnailPhoto (CN=Picture,CN=Schema,CN=Configuration,DC=X) is quite natural as MapiID is used all over the board
  2. Find in what classes the attribute is used and save this information (we will need it later)

    • To identify the classes you can use the following queries:

      C:\>dsquery * "cn=schema,cn=configuration,dc=w2k8r2,dc=local" -filter "systemMayContain=2.16.840.1.113730.3.1.35" "CN=Organizational-Person,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local"

      C:\>dsquery * "cn=schema,cn=configuration,dc=w2k8r2,dc=local" -filter "mayContain=2.16.840.1.113730.3.1.35"

      C:\>dsquery * "cn=schema,cn=configuration,dc=w2k8r2,dc=local" -filter "mustContain=2.16.840.1.113730.3.1.35"

      C:\>dsquery * "cn=schema,cn=configuration,dc=w2k8r2,dc=local" -filter "systemMustContain=2.16.840.1.113730.3.1.35"

      C:\>

    • As my lab environment has only vanilla W2K8R2 schema, you will notice that only organizationalPerson object class is using the thumbnailPhoto attribute, but if you run this against a forest with Exchange schema, you will notice that also the group object class has thumbnailPhoto in mayContain.

  3. Use the “s c h e m a U p g r a d e I n P r o g r e s s” trick (instructions NOT included)

  4. Remove the attribute from mayContain/mustContain/systemMayContain/systemMustContain of the classes we found in [2]

  5. Defunct the thumbnailPhoto attribute

    dn: CN=Picture,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaModify replace: isDefunct isDefunct: TRUE -

    Side note: when an attribute is defuncted, the information stored in the attribute is not lost, so if you have already populated thumbnailPhoto attribute, after you bring it back to life, you will have the values back on the objects

  6. Resolve the MapiID conflict by altering the MapiID attribute of MapiID attributeSchema object

    dn: CN=MAPI-ID,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaModify replace: MapIID MapiID: 32974 -

    dn: CN=Picture,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaModify replace: MapIID MapiID: 35998 -

    DN: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 -

  7. Un-defunct thumbnailPhoto attribute

    dn: CN=Picture,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaModify replace: isDefunct isDefunct: FALSE -

      

  8. Update the schema cache

    DN: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 -

  9. Add thumbnailPhoto attribute back to the classes we removed it from at [3]

    dn: CN=Organizational-Person,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaModify add: systemMayContain systemMayContain: thumbnailPhoto -

    dn: CN=Group,CN=Schema,CN=Configuration,DC=X changetype: ntdsSchemaModify add: mayContain mayContain: thumbnailPhoto -

  10. Update the schema cache:

    DN: changetype: modify add: schemaUpdateNow schemaUpdateNow: 1 -

  11. Pray to the schema gods (this part is crucial Smile)

  12. Verify the results:

    Expanding base 'CN=Picture,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local'... Result <0>: (null) Matched DNs: Getting 1 entries: >> Dn: CN=Picture,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local                 2> objectClass: top; attributeSchema;                 1> cn: Picture;                 1> distinguishedName: CN=Picture,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local;                 1> instanceType: 0x4 = ( IT_WRITE );                 1> whenCreated: 10/23/2002 03:50:19 Jerusalem Standard Time Jerusalem Daylight Time;                 1> whenChanged: 01/24/2011 14:52:45 Jerusalem Standard Time Jerusalem Daylight Time;                 1> uSNCreated: 4876;                 1> attributeID: 2.16.840.1.113730.3.1.35;                 1> attributeSyntax: 2.5.5.10;                 1> isSingleValued: TRUE;                 1> rangeLower: 0;                 1> rangeUpper: 102400;                 1> mAPIID: 35998;                 1> uSNChanged: 12157493;                 1> showInAdvancedViewOnly: TRUE;                 1> adminDisplayName: Picture;                 1> adminDescription: Picture;                 1> oMSyntax: 4;                 1> searchFlags: 0;                 1> lDAPDisplayName: thumbnailPhoto;                 1> name: Picture;                 1> objectGUID: 85e62def-5ba7-4f4e-9539-64d18246582b;                 1> schemaIDGUID: 8d3bca50-1d7e-11d0-a081-00aa006c33ed;                 1> attributeSecurityGUID: 77b5b886-944a-11d1-aebd-0000f80367c1;                 1> systemOnly: FALSE;                 1> systemFlags: 0x10 = ( FLAG_SCHEMA_BASE_OBJECT );                 1> isMemberOfPartialAttributeSet: TRUE;                 1> isDefunct: FALSE;                 1> objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local;                 2> dSCorePropagationData: 01/18/2011 18:16:28 Jerusalem Standard Time Jerusalem Daylight Time; 1/1/1601 2:0:1 Jerusalem Standard Time Jerusalem Daylight Time;

    Expanding base 'CN=Mapi-ID,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local... Result <0>: (null) Matched DNs: Getting 1 entries: >> Dn: CN=Mapi-ID,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local                 2> objectClass: top; attributeSchema;                 1> cn: MAPI-ID;                 1> distinguishedName: CN=MAPI-ID,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local;                 1> instanceType: 0x4 = ( IT_WRITE );                 1> whenCreated: 10/23/2002 03:50:16 Jerusalem Standard Time Jerusalem Daylight Time;                 1> whenChanged: 01/24/2011 14:52:43 Jerusalem Standard Time Jerusalem Daylight Time;                 1> uSNCreated: 4461;                 1> attributeID: 1.2.840.113556.1.2.49;                 1> attributeSyntax: 2.5.5.9;                 1> isSingleValued: TRUE;                 1> mAPIID: 32974;                 1> uSNChanged: 12157489;                 1> showInAdvancedViewOnly: TRUE;                 1> adminDisplayName: MAPI-ID;                 1> adminDescription: MAPI-ID;                 1> oMSyntax: 2;                 1> searchFlags: 0;                 1> lDAPDisplayName: mAPIID;                 1> name: MAPI-ID;                 1> objectGUID: 9cc0d7fa-822a-44e4-a891-eca43c55ddb0;                 1> schemaFlagsEx: 1;                 1> schemaIDGUID: bf9679b7-0de6-11d0-a285-00aa003049e2;                 1> systemOnly: TRUE;                 1> systemFlags: 0x10 = ( FLAG_SCHEMA_BASE_OBJECT );                 1> objectCategory: CN=Attribute-Schema,CN=Schema,CN=Configuration,DC=w2k8r2,DC=local;                 2> dSCorePropagationData: 01/18/2011 18:16:27 Jerusalem Standard Time Jerusalem Daylight Time; 1/1/1601 2:0:1 Jerusalem Standard Time Jerusalem Daylight Time;

  13. Done…

 

Bottom line: NEVER EVER perform changes like this directly in the production environment. Test, test, test and it will save you a lot of grief if/when something goes wrong…

Till next time,
GuyTe