Platform SDK: Active Directory, ADSI, and Directory Services

Example Code for Extending the Schema Programmatically

/*
**  schema.cxx: Example of installing an Active Directory Schema extension.
**
**    This example creates 6 new attributes, 1 new class, and modifies
**    two existing classes by adding new MAY HAVE attributes. These are used
**    in the DS Enabled Applications sample programs for the 1998 PDC.
**
**    Attributes:    courseBattleCruiser       integer
**                   speedBattleCruiser        integer
**                   maxPayloadBattleCruiser   integer
**                   allowedBattleCruiser      integer
**                   consistencyGUID           octet string
**                   consistencyChildCount     integer
**
**                Class:    policyParametersBattleCruiser
**
**                Classes Modified: container, groupPolicyContainer
**
**                The modified classes have the two consistency attributes added.
**
**  Notes:        Must be run on a DC which is the current Schema Master and has
**                schema updates enabled using the registry key and value:
**
**                KEY:  HKLM\CurrentControlSet\Services\NTDS\Parameters
**                Value:    Schema Update Allowed, REG_DWORD, 1
**
**  Libraries:  activeds.lib, adsiid.lib
**
**  Copyright (c) 1997, 1998 Microsoft Corporation
**  All Rights Reserved
**
*/
 
#include "stdafx.h"
 
// forward declaration of error report
 
void ReportError(TCHAR *defaultMsg, DWORD dwErr);
 
// GUIDs for schema extensions. Provide your own GUID for each extension so
// they are the same in every installation. If the DS assigns them your GUIDs
// will be different in every installation, which will hurt you down the road
// when we move to an identity-based schema.
 
// GUIDS for Battle Cruiser Policy attributes
 
// {C45F05B2-4D16-11d2-800E-0080C76670C0}
static const GUID attrGuid1 = 
{ 0xc45f05b2, 0x4d16, 0x11d2, { 0x80, 0xe, 0x0, 0x80, 0xc7, 0x66, 0x70, 0xc0 } };
// {C45F05B3-4D16-11d2-800E-0080C76670C0}
static const GUID attrGuid2 = 
{ 0xc45f05b3, 0x4d16, 0x11d2, { 0x80, 0xe, 0x0, 0x80, 0xc7, 0x66, 0x70, 0xc0 } };
// {C45F05B4-4D16-11d2-800E-0080C76670C0}
static const GUID attrGuid3 = 
{ 0xc45f05b4, 0x4d16, 0x11d2, { 0x80, 0xe, 0x0, 0x80, 0xc7, 0x66, 0x70, 0xc0 } };
// {C45F05B6-4D16-11d2-800E-0080C76670C0}
static const GUID attrGuid4 = 
{ 0xc45f05b6, 0x4d16, 0x11d2, { 0x80, 0xe, 0x0, 0x80, 0xc7, 0x66, 0x70, 0xc0 } };
 
// GUIDs for Consistency checking attributes
 
// {7707464B-4D9D-11d2-AF2D-00C04FB9624E}
static const GUID attrGuid5 = 
{ 0x7707464b, 0x4d9d, 0x11d2, { 0xaf, 0x2d, 0x0, 0xc0, 0x4f, 0xb9, 0x62, 0x4e } };
// {7707464C-4D9D-11d2-AF2D-00C04FB9624E}
static const GUID attrGuid6 = 
{ 0x7707464c, 0x4d9d, 0x11d2, { 0xaf, 0x2d, 0x0, 0xc0, 0x4f, 0xb9, 0x62, 0x4e } };
 
 
// {C45F05B5-4D16-11d2-800E-0080C76670C0}
static const GUID classGuid1 = 
{ 0xc45f05b5, 0x4d16, 0x11d2, { 0x80, 0xe, 0x0, 0x80, 0xc7, 0x66, 0x70, 0xc0 } };
 
 
void __cdecl
main()
{
    HRESULT hr;
    IADs    *pRoot;
    VARIANT varDSRoot, varSchemaUpdate;
    TCHAR   dsPath[MAX_PATH];
    TCHAR   gpcPath[MAX_PATH];
    TCHAR   contPath[MAX_PATH];
 
    // Returned by CreateDSObject
    IDispatch           *pDisp;
 
    // Pointers to schema objects
    IDirectoryObject    *pSchema, 
                        *pGpc;
 
    // Data structures for creating schema objects
    //
    // attribute values: these are unions and cannot be statically initialized.
    //
    ADSVALUE        cn,
                    singleValued,
                    oid,
                    syntax,
                    omSyntax,
                    ldapname,
                    idGuid,
                    objectClass,
                    objectClassCategory,
                    subClassOf,
                    defaultSecurityDesc,
                    defaultHidingValue,
                    mayContain[6];
 
    // ATTR_INFO for creating an attributeSchema object
    // Each ADS_ATTR_INFO describes one attribute of an object to be
    // stored in the DS.
 
    ADS_ATTR_INFO   attrArray[] = {
        {L"cn",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&cn,1},
        {L"isSingleValued",ADS_ATTR_UPDATE,ADSTYPE_BOOLEAN,&singleValued,1},
        {L"objectClass",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&objectClass,1},
        {L"attributeID",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&oid,1},
        {L"attributeSyntax",ADS_ATTR_UPDATE,ADSTYPE_INTEGER,&syntax,1},
        {L"oMSyntax",ADS_ATTR_UPDATE,ADSTYPE_INTEGER,&omSyntax,1},
        {L"lDAPDisplayName",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&ldapname,1},
        {L"schemaIdGUID",ADS_ATTR_UPDATE,ADSTYPE_OCTET_STRING,&idGuid,1},
    };
 
    // ATTR_INFO for creating a classSchema object
 
    ADS_ATTR_INFO   classArray[] = {
        {L"cn",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&cn,1},
        {L"objectClass",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&objectClass,1},
        {L"governsID",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&oid,1},
        {L"objectClassCategory",ADS_ATTR_UPDATE,ADSTYPE_INTEGER,&objectClassCategory,1},
        {L"schemaIdGUID",ADS_ATTR_UPDATE,ADSTYPE_OCTET_STRING,&idGuid,1},
        {L"defaultSecurityDescriptor",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&defaultSecurityDesc,1},
        {L"defaultHidingValue",ADS_ATTR_UPDATE,ADSTYPE_BOOLEAN,&defaultHidingValue,1},
        {L"subClassOf",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&subClassOf,1},
        {L"mayContain",ADS_ATTR_UPDATE,ADSTYPE_CASE_IGNORE_STRING,&mayContain[0],6},
    };
 
    // ATTR_INFO for adding attributes to Group Policy Container
    ADS_ATTR_INFO gpcUpdate[] = {
        {L"mayContain",ADS_ATTR_APPEND,ADSTYPE_CASE_IGNORE_STRING,&mayContain[0],2},
    };
 
 
    DWORD           dwAttrs;
    ULONG           iAttrsMod;
 
    hr = CoInitializeEx(NULL,COINIT_MULTITHREADED| COINIT_DISABLE_OLE1DDE);
 
    //  Get the name of the schema container for this domain. 
    //  Read the Root DSE from the default DS,  which will be the DS for 
    //  the local domain. This will get us the name of the schema container,
    //  which is stored in the "schemaNamingContext" operational attribute.
 
    hr = ADsGetObject(TEXT("LDAP://RootDSE"),
                      IID_IADs,
                      (void**)&pRoot);
 
    // Get IDirectoryObject on the root DSE as well, we will use this for 
    // forcing a schema update later on...
 
 
    hr = pRoot->Get(L"schemaNamingContext",&varDSRoot);
    printf("\nDS Root:%S\n",varDSRoot.bstrVal);
    
    // ADsPath of the schema container
 
    _tcscpy(dsPath,TEXT("LDAP://"));
    _tcscat(dsPath,varDSRoot.bstrVal);
 
    // ADsPath of the Group Policy Container class-schema object
    _tcscpy(gpcPath,TEXT("LDAP://"));
    _tcscat(gpcPath,TEXT("CN=Group-Policy-Container,"));
    _tcscat(gpcPath,varDSRoot.bstrVal);
 
    // ADsPath of the Container class-schema object
    _tcscpy(contPath,TEXT("LDAP://"));
    _tcscat(contPath,TEXT("CN=Container,"));
    _tcscat(contPath,varDSRoot.bstrVal);
 
    // Done with variant
    hr = VariantClear(&varDSRoot);
    
    // Bind to the schema container and get the IDirectoryObject interface
    // on it.
    hr = ADsGetObject(dsPath,
                      IID_IDirectoryObject,
                      (void**)&pSchema);
 
 
 
    //**********************************************************************
    // Consistency-Child-Count
    //**********************************************************************
 
    // Create a new attribute object. Set the values into the attribute
    // unions, then call the method to create the object.
    //
 
 
    dwAttrs = sizeof(attrArray)/sizeof(ADS_ATTR_INFO); // Figure out attribute count
 
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Consistency-Child-Count");
    singleValued.dwType = ADSTYPE_BOOLEAN;
    singleValued.Boolean = VARIANT_TRUE;
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.4.7000.161"); // reserved test OID
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("attributeSchema");
    syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
    syntax.CaseIgnoreString = TEXT("2.5.5.9");              // 2.5.5.9 = Integer
    omSyntax.dwType = ADSTYPE_INTEGER;
    omSyntax.Integer = 2;
    //
    // The ldap display name will be defaulted by the server and should
    // not be provided unless different than the name computed from the CN
    // attribute - the LDAP name is the CN with the hyphens removed and 
    // case delimiting substituted. The initial character is always lowercased.
    // For this example we are providing an explicit LDAP display name to illustrate
    // how it is done.
    ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
    ldapname.CaseIgnoreString = TEXT("consistencyChildCount");
    //
    // Schema-ID-Guid is provided by the server is the client does not
    // provide it. This is a good example of how to write an Octet String
    // to the DS.
    idGuid.dwType = ADSTYPE_OCTET_STRING;
    idGuid.OctetString.dwLength = sizeof(attrGuid6);
    idGuid.OctetString.lpValue = (LPBYTE)&attrGuid6;
 
    hr = pSchema->CreateDSObject(L"cn=Consistency-Child-Count",
                                 attrArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Attribute Consistency-Child-Count failed."),hr);
        // return;
    } else {
        printf("\nConsistency-Child-Count Attribute defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
 
    //**********************************************************************
    // Consistency-GUID
    //**********************************************************************
 
    // Create a new attribute object. Set the values into the attribute
    // unions, then call the method to create the object.
    //
 
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Consistency-GUID");
    singleValued.dwType = ADSTYPE_BOOLEAN;
    singleValued.Boolean = VARIANT_TRUE;
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.4.7000.160"); // reserved test OID
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("attributeSchema");
    syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
    syntax.CaseIgnoreString = TEXT("2.5.5.10");              // 2.5.5.10 = Octet String
    omSyntax.dwType = ADSTYPE_INTEGER;
    omSyntax.Integer = 4;
    //
    // The ldap display name will be defaulted by the server and should
    // not be provided unless different than the name computed from the CN
    // attribute - the LDAP name is the CN with the hyphens removed and 
    // case delimiting substituted. The initial character is always lowercased.
    // For this example we are providing an explicit LDAP display name to illustrate
    // how it is done.
    ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
    ldapname.CaseIgnoreString = TEXT("consistencyGUID");
    //
    // Schema-ID-Guid is provided by the server is the client does not
    // provide it. This is a good example of how to write an Octet String
    // to the DS.
    idGuid.dwType = ADSTYPE_OCTET_STRING;
    idGuid.OctetString.dwLength = sizeof(attrGuid5);
    idGuid.OctetString.lpValue = (LPBYTE)&attrGuid5;
 
    hr = pSchema->CreateDSObject(L"cn=Consistency-GUID",
                                 attrArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Attribute Consistency-GUID failed."),hr);
        // return;
    } else {
        printf("\nConsistency-GUID Attribute defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
    //**********************************************************************
    // Course-Battle-Cruiser
    //**********************************************************************
 
    // Create a new attribute object. Set the values into the attribute
    // unions, then call the method to create the object.
    //
 
 
    dwAttrs = sizeof(attrArray)/sizeof(ADS_ATTR_INFO); // Figure out attribute count
 
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Course-Battle-Cruiser");
    singleValued.dwType = ADSTYPE_BOOLEAN;
    singleValued.Boolean = VARIANT_TRUE;
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.4.7000.155"); // reserved test OID
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("attributeSchema");
    syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
    syntax.CaseIgnoreString = TEXT("2.5.5.9");              // 2.5.5.9 = Integer
    omSyntax.dwType = ADSTYPE_INTEGER;
    omSyntax.Integer = 2;
    //
    // The ldap display name will be defaulted by the server and should
    // not be provided unless different than the name computed from the CN
    // attribute - the LDAP name is the CN with the hyphens removed and 
    // case delimiting substituted. The initial character is always lowercased.
    // For this example we are providing an explicit LDAP display name to illustrate
    // how it is done.
    ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
    ldapname.CaseIgnoreString = TEXT("courseBattleCruiser");
    //
    // Schema-ID-Guid is provided by the server is the client does not
    // provide it. This is a good example of how to write an Octet String
    // to the DS.
    idGuid.dwType = ADSTYPE_OCTET_STRING;
    idGuid.OctetString.dwLength = sizeof(attrGuid1);
    idGuid.OctetString.lpValue = (LPBYTE)&attrGuid1;
 
    hr = pSchema->CreateDSObject(L"cn=Course-Battle-Cruiser",
                                 attrArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Attribute Course-Battle-Cruiser failed."),hr);
        //return;
    } else { 
        printf("\nCourse-Battle-Cruiser Attribute defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
 
    //**********************************************************************
    // Speed-Battle-Cruiser
    //**********************************************************************
 
    // Create a new attribute object. Set the values into the attribute
    // unions, then call the method to create the object.
    //
 
    dwAttrs = sizeof(attrArray)/sizeof(ADS_ATTR_INFO); // Figure out attribute count
 
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Speed-Battle-Cruiser");
    singleValued.dwType = ADSTYPE_BOOLEAN;
    singleValued.Boolean = VARIANT_TRUE;
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.4.7000.156"); // reserved test OID
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("attributeSchema");
    syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
    syntax.CaseIgnoreString = TEXT("2.5.5.9");              // 2.5.5.9 = Integer
    omSyntax.dwType = ADSTYPE_INTEGER;
    omSyntax.Integer = 2;
    //
    // The ldap display name will be defaulted by the server and should
    // not be provided unless different than the name computed from the CN
    // attribute - the LDAP name is the CN with the hyphens removed and 
    // case delimiting substituted. The initial character is always lowercased.
    // For this example we are providing an explicit LDAP display name to illustrate
    // how it is done.
    ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
    ldapname.CaseIgnoreString = TEXT("speedBattleCruiser");
    //
    // Schema-ID-Guid is provided by the server is the client does not
    // provide it. This is a good example of how to write an Octet String
    // to the DS.
    idGuid.dwType = ADSTYPE_OCTET_STRING;
    idGuid.OctetString.dwLength = sizeof(attrGuid2);
    idGuid.OctetString.lpValue = (LPBYTE)&attrGuid2;
 
    hr = pSchema->CreateDSObject(L"cn=Speed-Battle-Cruiser",
                                 attrArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Attribute Speed-Battle-Cruiser failed."),hr);
        //return;
    } else {
        printf("\nSpeed-Battle-Cruiser Attribute defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
 
    //**********************************************************************
    // Max-Payload-Battle-Cruiser
    //**********************************************************************
 
    // Create a new attribute object. Set the values into the attribute
    // unions, then call the method to create the object.
    //
 
    dwAttrs = sizeof(attrArray)/sizeof(ADS_ATTR_INFO); // Figure out attribute count
 
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Max-Payload-Battle-Cruiser");
    singleValued.dwType = ADSTYPE_BOOLEAN;
    singleValued.Boolean = VARIANT_TRUE;
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.4.7000.157"); // reserved test OID
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("attributeSchema");
    syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
    syntax.CaseIgnoreString = TEXT("2.5.5.9");              // 2.5.5.9 = Integer
    omSyntax.dwType = ADSTYPE_INTEGER;
    omSyntax.Integer = 2;
    //
    // The ldap display name will be defaulted by the server and should
    // not be provided unless different than the name computed from the CN
    // attribute - the LDAP name is the CN with the hyphens removed and 
    // case delimiting substituted. The initial character is always lowercased.
    // For this example we are providing an explicit LDAP display name to illustrate
    // how it is done.
    ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
    ldapname.CaseIgnoreString = TEXT("maxPayloadBattleCruiser");
    //
    // Schema-ID-Guid is provided by the server is the client does not
    // provide it. This is a good example of how to write an Octet String
    // to the DS.
    idGuid.dwType = ADSTYPE_OCTET_STRING;
    idGuid.OctetString.dwLength = sizeof(attrGuid3);
    idGuid.OctetString.lpValue = (LPBYTE)&attrGuid3;
 
    hr = pSchema->CreateDSObject(L"cn=Max-Payload-Battle-Cruiser",
                                 attrArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Attribute Max-Payload-Battle-Cruiser failed."),hr);
        // return;
    } else {
        printf("\nMax-Payload-Battle-Cruiser Attribute defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
 
    //**********************************************************************
    // Allowed-Battle-Cruiser
    //**********************************************************************
 
    // Create a new attribute object. Set the values into the attribute
    // unions, then call the method to create the object.
    //
 
    dwAttrs = sizeof(attrArray)/sizeof(ADS_ATTR_INFO); // Figure out attribute count
 
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Allowed-Battle-Cruiser");
    singleValued.dwType = ADSTYPE_BOOLEAN;
    singleValued.Boolean = VARIANT_TRUE;
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.4.7000.159"); // reserved test OID
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("attributeSchema");
    syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
    syntax.CaseIgnoreString = TEXT("2.5.5.8");              // 2.5.5.8 = Boolean
    omSyntax.dwType = ADSTYPE_INTEGER;
    omSyntax.Integer = 1;
    //
    // The ldap display name will be defaulted by the server and should
    // not be provided unless different than the name computed from the CN
    // attribute - the LDAP name is the CN with the hyphens removed and 
    // case delimiting substituted. The initial character is always lowercased.
    // For this example we are providing an explicit LDAP display name to illustrate
    // how it is done.
    ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
    ldapname.CaseIgnoreString = TEXT("allowedBattleCruiser");
    //
    // Schema-ID-Guid is provided by the server is the client does not
    // provide it. This is a good example of how to write an Octet String
    // to the DS.
    idGuid.dwType = ADSTYPE_OCTET_STRING;
    idGuid.OctetString.dwLength = sizeof(attrGuid4);
    idGuid.OctetString.lpValue = (LPBYTE)&attrGuid4;
 
    hr = pSchema->CreateDSObject(L"cn=Allowed-Battle-Cruiser",
                                 attrArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Attribute Allowed-Battle-Cruiser failed."),hr);
        //return;
    } else {
        printf("\nAllowed-Battle-Cruiser Attribute defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
 
    // Force an update of the schema cache so we can create the class that
    // includes these attributes. We force a synchronous schema update by writing
    // the operational attribute "schemaUpdateNow" to the Root DSE.
    //
    varSchemaUpdate.vt = VT_I4;
    varSchemaUpdate.intVal = 1;
    hr = pRoot->Put(TEXT("schemaUpdateNow"),varSchemaUpdate);
    hr = pRoot->SetInfo();
    if (hr != NO_ERROR) {
        ReportError(TEXT("Force Schema Recalc failed."),hr);
    }
 
 
    //**********************************************************************
    // Policy-Parameters-Battle-Cruiser
    //**********************************************************************
    //
    // Create a new class object and add attributes 
    // (including the new ones) to the class
    //
    cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
    cn.CaseIgnoreString=TEXT("Policy-Parameters-Battle-Cruiser");
    objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
    objectClass.CaseIgnoreString=TEXT("classSchema");
    oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
    oid.CaseIgnoreString=TEXT("1.2.840.113556.1.5.7000.92"); // Reserved Test OID
    subClassOf.dwType = ADSTYPE_CASE_IGNORE_STRING;
    subClassOf.CaseIgnoreString = TEXT("serviceConnectionPoint");
    defaultSecurityDesc.dwType = ADSTYPE_CASE_IGNORE_STRING;
    defaultSecurityDesc.CaseIgnoreString = TEXT("D:(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;;RPLCLORC;;;AU)S:(AU;SAFA;WDWOSDDTWPCRCCDCSW;;;WD)");
    defaultHidingValue.dwType = ADSTYPE_BOOLEAN;
    defaultHidingValue.Boolean = -1;
    mayContain[0].dwType = ADSTYPE_CASE_IGNORE_STRING;
    mayContain[0].CaseIgnoreString = TEXT("courseBattleCruiser");
    mayContain[1].dwType = ADSTYPE_CASE_IGNORE_STRING;
    mayContain[1].CaseIgnoreString = TEXT("speedBattleCruiser");
    mayContain[2].dwType = ADSTYPE_CASE_IGNORE_STRING;
    mayContain[2].CaseIgnoreString = TEXT("maxPayloadBattleCruiser");
    mayContain[3].dwType = ADSTYPE_CASE_IGNORE_STRING;
    mayContain[3].CaseIgnoreString = TEXT("allowedBattleCruiser");
    mayContain[4].dwType = ADSTYPE_CASE_IGNORE_STRING;
    mayContain[4].CaseIgnoreString = TEXT("consistencyGUID");
    mayContain[5].dwType = ADSTYPE_CASE_IGNORE_STRING;
    mayContain[5].CaseIgnoreString = TEXT("consistencyChildCount");
 
    // Object-Class-Category=
    // 88_CLASS           0
    // STRUCTURAL_CLASS   1
    // ABSTRACT_CLASS     2
    // AUXILIARY_CLASS    3
    objectClassCategory.dwType = ADSTYPE_INTEGER;
    objectClassCategory.Integer = 1; 
 
    dwAttrs = sizeof(classArray)/sizeof(ADS_ATTR_INFO); // Figure out attribute count
 
    hr = pSchema->CreateDSObject(L"cn=Policy-Parameters-Battle-Cruiser",
                                 classArray,
                                 dwAttrs,
                                 &pDisp);
    if (hr != NO_ERROR) {
        ReportError(TEXT("Create Class Policy-Parameters-Battle-Cruiser failed."),hr);
    } else {
        printf("\nBattle Cruiser Class defined.\n");
 
    // We are not going to use the IDispatch interface returned by the 
    // CreateDSObject call,  so release it now.
        pDisp->Release();
    }
 
    //**************************************************************************
    // Add the consistency attributes to Group-Policy-Container and Container
    //**************************************************************************
 
    hr = ADsGetObject(gpcPath,
                      IID_IDirectoryObject,
                      (void**)&pGpc);
 
    if (hr != NO_ERROR) {
        ReportError(TEXT("Read GPC class object failed."),hr);
        return;
    } else {
        printf("\nRetrieved GPC class object.\n");
 
        mayContain[0].dwType = ADSTYPE_CASE_IGNORE_STRING;
        mayContain[0].CaseIgnoreString = TEXT("consistencyGUID");
        mayContain[1].dwType = ADSTYPE_CASE_IGNORE_STRING;
        mayContain[1].CaseIgnoreString = TEXT("consistencyChildCount");
        hr = pGpc->SetObjectAttributes(gpcUpdate,1,&iAttrsMod);
        if (hr != NO_ERROR) {
            ReportError(TEXT("Update GPC Class object failed."),hr);
        } else {
            printf("\nUpdated GPC Class object.\n");
        }
    }
 
    // Done with class object
    pGpc->Release();
 
    //
    // Now apply the consistency attributes to Container. The ATTR_INFO
    // we need is already filled in, we just need to apply it.
    //
    hr = ADsGetObject(contPath,
                      IID_IDirectoryObject,
                      (void**)&pGpc);
 
    if (hr != NO_ERROR) {
        ReportError(TEXT("Read Container class object failed."),hr);
        return;
    } else {
        printf("\nRetrieved Container class object.\n");
 
        hr = pGpc->SetObjectAttributes(gpcUpdate,1,&iAttrsMod);
        if (hr != NO_ERROR) {
        ReportError(TEXT("Update Container Class object failed."),hr);
        } else {
            printf("\nUpdated Container Class object.\n");
        }
    }
 
    // Force an update of the schema cache so we can use the changes immediately.
    // We force a synchronous schema update by writing
    // the operational attribute "schemaUpdateNow" to the Root DSE.
    //
    varSchemaUpdate.vt = VT_I4;
    varSchemaUpdate.intVal = 1;
    hr = pRoot->Put(TEXT("schemaUpdateNow"),varSchemaUpdate);
    hr = pRoot->SetInfo();
    if (hr != NO_ERROR) {
        ReportError(TEXT("Force Schema Recalc failed."),hr);
    }
 
    // Done with Schema Container
 
    pSchema->Release();
    
    // Done with Root DSE
    pRoot->Release();
 
    //
 
    
 
    CoUninitialize();
 
}
 
// Simple error message reporter
 
void ReportError(TCHAR *pTxt,DWORD err)
{
    DWORD   dwStatus;
    TCHAR   *pBuf;
 
    dwStatus = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|
                             FORMAT_MESSAGE_ALLOCATE_BUFFER|
                             FORMAT_MESSAGE_IGNORE_INSERTS,
                             NULL,
                             err,
                             LANG_NEUTRAL,
                             (USHORT*)&pBuf,
                             64,
                             NULL); 
    if (dwStatus != 0) {
        _tprintf(TEXT("%s %s"),pTxt,pBuf);
        LocalFree(pBuf);
    } else {
        _tprintf(TEXT("%s %X\n"),pTxt,err);
    }
}