Directory Services

Example Code for Extending the Schema Programmatically

/*
**  schema.cxx: The following code example installs 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.
**
**	Attributes:	courseTugboat		integer
**				 speedTugboat		 integer
**				 maxPayloadTugboat	integer
**				 allowedTugboat		 integer
**				 consistencyGUID		octet string
**				 consistencyChildCount  integer
**
**				Class:	policyParametersTugboat
**
**				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) 2003 Microsoft Corporation
**  All Rights Reserved
**
*/
 
#include <windows.h>
#include <ole2.h>
#include <iads.h>
#include <activeds.h>
#include <stdio.h>
#include <atlbase.h>

#define COURSE_ATTR_NAME L"Course-Tugboat"
#define COURSE_ATTR_RDN L"CN=Course-Tugboat"
#define COURSE_ATTR_LDAP_NAME L"courseTugboat"

#define SPEED_ATTR_NAME L"Speed-Tugboat"
#define SPEED_ATTR_RDN L"CN=Speed-Tugboat"
#define SPEED_ATTR_LDAP_NAME L"speedTugboat"

#define MAX_PAYLOAD_ATTR_NAME L"Max-Payload-Tugboat"
#define MAX_PAYLOAD_ATTR_RDN L"CN=Max-Payload-Tugboat"
#define MAX_PAYLOAD_ATTR_LDAP_NAME L"maxPayloadTugboat"

#define ALLOWED_ATTR_NAME L"Allowed-Tugboat"
#define ALLOWED_ATTR_RDN L"CN=Allowed-Tugboat"
#define ALLOWED_ATTR_LDAP_NAME L"allowedTugboat"

#define CONSISTENCY_GUID_ATTR_NAME L"Consistency-GUID"
#define CONSISTENCY_GUID_ATTR_RDN L"CN=Consistency-GUID"
#define CONSISTENCY_GUID_ATTR_LDAP_NAME L"consistencyGUID"

#define CONSISTENCY_CHILD_ATTR_NAME L"Consistency-Child-Count"
#define CONSISTENCY_CHILD_ATTR_RDN L"CN=Consistency-Child-Count"
#define CONSISTENCY_CHILD_ATTR_LDAP_NAME L"consistencyChildCount"

#define TUGBOAT_CLASS_NAME L"Policy-Parameters-Tugboat" 
#define TUGBOAT_CLASS_RDN L"CN=Policy-Parameters-Tugboat" 

// Forward declaration of error report
 
void ReportErrorW(LPCWSTR pwszDefaultMsg, 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; this will affect system performance
// when moving to an identity-based schema.
 
// GUIDS for the 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 main(void)
{
	HRESULT hr;
	IADs	*pRoot = NULL;
	VARIANT varDSRoot, 
			varSchemaUpdate;
	LPWSTR  pwszDSPath = NULL;
	LPWSTR  pwszGPCPath = NULL;
	LPWSTR  pwszContPath = NULL;
 
	// Returned by CreateDSObject
	IDispatch *pDisp = NULL;
 
	// Pointers to schema objects
	IDirectoryObject	*pSchema = NULL, 
						*pGpc = NULL;
 
	// 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 the name of the schema container,
	//  stored in the "schemaNamingContext" operational attribute.
 
	hr = ADsGetObject(L"LDAP://RootDSE",
					IID_IADs,
					(void**)&pRoot);
	if(FAILED(hr))
	{
		goto cleanup;
}
 
	// Get IDirectoryObject on the root DSE as well; use this for 
	// forcing a schema update later.
 
	VariantInit(&varDSRoot);
	hr = pRoot->Get(CComBSTR("schemaNamingContext"), &varDSRoot);
	if(FAILED(hr))
	{
		goto cleanup;
}
	wprintf(L"\nDS Root:%S\n", varDSRoot.bstrVal);

	// ADsPath of the schema container
	pwszDSPath = new WCHAR[7 + lstrlenW(varDSRoot.bstrVal) + 1];
	if(!pwszDSPath)
	{
		goto cleanup;
}
	lstrcpyW(pwszDSPath, L"LDAP://");
	lstrcatW(pwszDSPath, varDSRoot.bstrVal);
 
	// ADsPath of the Group Policy Container class-schema object
	pwszGPCPath = new WCHAR[33 + lstrlenW(varDSRoot.bstrVal) + 1];
	if(!pwszGPCPath)
	{
		goto cleanup;
}
	lstrcpyW(pwszGPCPath, L"LDAP://CN=Group-Policy-Container,");
	lstrcatW(pwszGPCPath, varDSRoot.bstrVal);
 
	// ADsPath of the Container class-schema object
	pwszContPath = new WCHAR[20 + lstrlenW(varDSRoot.bstrVal) + 1];
	if(!pwszContPath)
	{
		goto cleanup;
}
	lstrcpyW(pwszContPath, L"LDAP://CN=Container,");
	lstrcatW(pwszContPath, varDSRoot.bstrVal);
 
	// Bind to the schema container and get the IDirectoryObject interface
	// for it.
	hr = ADsGetObject(pwszDSPath,
					IID_IDirectoryObject,
					(void**)&pSchema);
	if(FAILED(hr))
	{
		goto cleanup;
}
 
 
	//**********************************************************************
	// 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); // Calculate attribute count.
 
	cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
	cn.CaseIgnoreString = CONSISTENCY_CHILD_ATTR_NAME;
	singleValued.dwType = ADSTYPE_BOOLEAN;
	singleValued.Boolean = VARIANT_TRUE;
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.4.7000.161"; // Reserved. Test OID.
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"attributeSchema";
	syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
	syntax.CaseIgnoreString = L"2.5.5.9"; 		// 2.5.5.9 = Integer
	omSyntax.dwType = ADSTYPE_INTEGER;
	omSyntax.Integer = 2;
	//
	// The ldap display name is 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 lowercase.
	// For this example, an explicit LDAP display name is provided to show
	// how it is done.
	ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
	ldapname.CaseIgnoreString = CONSISTENCY_CHILD_ATTR_LDAP_NAME;
	//
	// 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(CONSISTENCY_CHILD_ATTR_RDN,
								 attrArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{
		wprintf(L"\n");
		wprintf(CONSISTENCY_CHILD_ATTR_NAME);
		wprintf(L" Attribute defined.\n");
 
		// The IDispatch interface returned by the CreateDSObject call is not used.
		// Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
 
	//**********************************************************************
	// 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 = L"Consistency-GUID";
	singleValued.dwType = ADSTYPE_BOOLEAN;
	singleValued.Boolean = VARIANT_TRUE;
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.4.7000.160"; // Reserved. Test OID.
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"attributeSchema";
	syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
	syntax.CaseIgnoreString = L"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 lowercase.
	// For this example, an explicit LDAP display name is provided to show
	// how it is done.
	ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
	ldapname.CaseIgnoreString = CONSISTENCY_GUID_ATTR_LDAP_NAME;
	//
	// 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(CONSISTENCY_GUID_ATTR_RDN,
								 attrArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{
		wprintf(L"\n");
		wprintf(CONSISTENCY_GUID_ATTR_NAME);
		wprintf(L" Attribute defined.\n");
 
		// The IDispatch interface, returned by the CreateDSObject call, is not used.
		// Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
	//**********************************************************************
	// Course-Tugboat
	//**********************************************************************
 
	// 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); // Calculate attribute count.
 
	cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
	cn.CaseIgnoreString = COURSE_ATTR_NAME;
	singleValued.dwType = ADSTYPE_BOOLEAN;
	singleValued.Boolean = VARIANT_TRUE;
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.4.7000.155"; // Reserved. Test OID.
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"attributeSchema";
	syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
	syntax.CaseIgnoreString = L"2.5.5.9"; 		// 2.5.5.9 = Integer
	omSyntax.dwType = ADSTYPE_INTEGER;
	omSyntax.Integer = 2;
	//
	// The LDAP display name is 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 lowercase.
	// For this example, an explicit LDAP display name is provided to show
	// how it is done.
	ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
	ldapname.CaseIgnoreString = COURSE_ATTR_LDAP_NAME;
	//
	// 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(COURSE_ATTR_RDN,
								 attrArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{ 
		wprintf(L"\n");
		wprintf(COURSE_ATTR_NAME);
		wprintf(L" Attribute defined.\n");
 
		// Do not use the IDispatch interface returned by the CreateDSObject call.
		// Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
 
	//**********************************************************************
	// Speed-Tugboat
	//**********************************************************************
 
	// 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); // Calculate attribute count.
 
	cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
	cn.CaseIgnoreString = SPEED_ATTR_NAME;
	singleValued.dwType = ADSTYPE_BOOLEAN;
	singleValued.Boolean = VARIANT_TRUE;
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.4.7000.156"; // Reserved. Test OID.
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"attributeSchema";
	syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
	syntax.CaseIgnoreString = L"2.5.5.9"; 		// 2.5.5.9 = Integer
	omSyntax.dwType = ADSTYPE_INTEGER;
	omSyntax.Integer = 2;
	//
	// The LDAP display name is 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 lowercase.
	// For this example, an explicit LDAP display name is provided to show
	// how it is done.
	ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
	ldapname.CaseIgnoreString = SPEED_ATTR_LDAP_NAME;
	//
	// Schema-ID-Guid is provided by the server if 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(SPEED_ATTR_RDN,
								 attrArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{
		wprintf(L"\n");
		wprintf(SPEED_ATTR_NAME);
		wprintf(L" Attribute defined.\n");
 
		// Do not use the IDispatch interface returned by the 
		// CreateDSObject call. Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
 
	//**********************************************************************
	// Max-Payload-Tugboat
	//**********************************************************************
 
	// 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); // Calculate attribute count.
 
	cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
	cn.CaseIgnoreString = MAX_PAYLOAD_ATTR_NAME;
	singleValued.dwType = ADSTYPE_BOOLEAN;
	singleValued.Boolean = VARIANT_TRUE;
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.4.7000.157"; // Reserved. Test OID.
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"attributeSchema";
	syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
	syntax.CaseIgnoreString = L"2.5.5.9"; 		// 2.5.5.9 = Integer
	omSyntax.dwType = ADSTYPE_INTEGER;
	omSyntax.Integer = 2;
	//
	// The LDAP display name is 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 lowercase.
	// For this example, an explicit LDAP display name is provided to show
	// how it is done.
	ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
	ldapname.CaseIgnoreString = MAX_PAYLOAD_ATTR_LDAP_NAME;
	//
	// 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(MAX_PAYLOAD_ATTR_RDN,
								 attrArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{
		wprintf(L"\n");
		wprintf(MAX_PAYLOAD_ATTR_NAME);
		wprintf(L" Attribute defined.\n");
 
		// Do not use the IDispatch interface returned by the 
		// CreateDSObject call. Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
 
	//**********************************************************************
	// Allowed-Tugboat
	//**********************************************************************
 
	// 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); // Calculate attribute count.
 
	cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
	cn.CaseIgnoreString = ALLOWED_ATTR_NAME;
	singleValued.dwType = ADSTYPE_BOOLEAN;
	singleValued.Boolean = VARIANT_TRUE;
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.4.7000.159"; // Reserved. Test OID.
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"attributeSchema";
	syntax.dwType = ADSTYPE_CASE_IGNORE_STRING;
	syntax.CaseIgnoreString = L"2.5.5.8"; 		// 2.5.5.8 = Boolean
	omSyntax.dwType = ADSTYPE_INTEGER;
	omSyntax.Integer = 1;
	//
	// The LDAP display name is 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 lowercase.
	// For this example, an explicit LDAP display name is provided to show
	// how it is done.
	ldapname.dwType = ADSTYPE_CASE_IGNORE_STRING;
	ldapname.CaseIgnoreString = ALLOWED_ATTR_LDAP_NAME;
	//
	// 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(ALLOWED_ATTR_RDN,
								 attrArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{
		wprintf(L"\n");
		wprintf(ALLOWED_ATTR_NAME);
		wprintf(L" Attribute defined.\n");
 
		// Do not use the IDispatch interface returned by the 
		// CreateDSObject call. Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
 
	// Force an update of the schema cache to create the class that includes
	// these attributes. 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(CComBSTR("schemaUpdateNow"), varSchemaUpdate);
	hr = pRoot->SetInfo();
	if (FAILED(hr)) 
	{
		ReportErrorW(L"Force Schema Recalc failed.", hr);
}
 
 
	//**********************************************************************
	// Policy-Parameters-Tugboat
	//**********************************************************************
	//
	// Create a new class object and add attributes 
	// (including the new ones) to the class.
	//
	cn.dwType = ADSTYPE_CASE_IGNORE_STRING;
	cn.CaseIgnoreString = TUGBOAT_CLASS_NAME;
	objectClass.dwType = ADSTYPE_CASE_IGNORE_STRING;
	objectClass.CaseIgnoreString = L"classSchema";
	oid.dwType = ADSTYPE_CASE_IGNORE_STRING;
	oid.CaseIgnoreString = L"1.2.840.113556.1.5.7000.92"; // Reserved. Test OID.
	subClassOf.dwType = ADSTYPE_CASE_IGNORE_STRING;
	subClassOf.CaseIgnoreString = L"serviceConnectionPoint";
	defaultSecurityDesc.dwType = ADSTYPE_CASE_IGNORE_STRING;
	defaultSecurityDesc.CaseIgnoreString = L"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 = COURSE_ATTR_LDAP_NAME;
	mayContain[1].dwType = ADSTYPE_CASE_IGNORE_STRING;
	mayContain[1].CaseIgnoreString = SPEED_ATTR_LDAP_NAME;
	mayContain[2].dwType = ADSTYPE_CASE_IGNORE_STRING;
	mayContain[2].CaseIgnoreString = MAX_PAYLOAD_ATTR_LDAP_NAME;
	mayContain[3].dwType = ADSTYPE_CASE_IGNORE_STRING;
	mayContain[3].CaseIgnoreString = ALLOWED_ATTR_LDAP_NAME;
	mayContain[4].dwType = ADSTYPE_CASE_IGNORE_STRING;
	mayContain[4].CaseIgnoreString = CONSISTENCY_GUID_ATTR_LDAP_NAME;
	mayContain[5].dwType = ADSTYPE_CASE_IGNORE_STRING;
	mayContain[5].CaseIgnoreString = CONSISTENCY_CHILD_ATTR_LDAP_NAME;
 
	// 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); // Calculate attribute count.
 
	hr = pSchema->CreateDSObject(TUGBOAT_CLASS_RDN,
								 classArray,
								 dwAttrs,
								 &pDisp);
	if (FAILED(hr)) 
	{
		ReportErrorW(L"CreateDSObject failed.", hr);
} 
	else 
	{
		wprintf(L"\nClass defined.\n");
 
		// Do not use the IDispatch interface returned by the 
		// CreateDSObject call. Release it now.
		pDisp->Release();
		pDisp = NULL;
}
 
	//**************************************************************************
	// Add the consistency attributes to Group-Policy-Container and Container
	//**************************************************************************
 
	hr = ADsGetObject(pwszGPCPath,
					IID_IDirectoryObject,
					(void**)&pGpc);
 
	if (FAILED(hr)) 
	{
		ReportErrorW(L"Read GPC class object failed.", hr);
		goto cleanup;
} 
	else 
	{
		wprintf(L"\nRetrieved GPC class object.\n");
 
		mayContain[0].dwType = ADSTYPE_CASE_IGNORE_STRING;
		mayContain[0].CaseIgnoreString = CONSISTENCY_GUID_ATTR_LDAP_NAME;
		mayContain[1].dwType = ADSTYPE_CASE_IGNORE_STRING;
		mayContain[1].CaseIgnoreString = CONSISTENCY_CHILD_ATTR_LDAP_NAME;
		hr = pGpc->SetObjectAttributes(gpcUpdate, 1, &iAttrsMod);
		if (FAILED(hr)) 
		{
			ReportErrorW(L"Update GPC Class object failed.", hr);
	} 
		else 
		{
			wprintf(L"\nUpdated GPC Class object.\n");
	}
}
 
	// Done with class object.
	pGpc->Release();
	pGpc = NULL;
 
	//
	// Apply the consistency attributes to Container. The ATTR_INFO
	// required is filled in. Apply it.
	//
	hr = ADsGetObject(pwszContPath,
					IID_IDirectoryObject,
					(void**)&pGpc);
 
	if (FAILED(hr)) 
	{
		ReportErrorW(L"Read Container class object failed.", hr);
		goto cleanup;
} 
	else 
	{
		wprintf(L"\nRetrieved Container class object.\n");
 
		hr = pGpc->SetObjectAttributes(gpcUpdate,1,&iAttrsMod);
		if (FAILED(hr)) 
		{
			ReportErrorW(L"Update Container Class object failed.", hr);
	} 
		else 
		{
			wprintf(L"\nUpdated Container Class object.\n");
	}
}
 
	// Force an update of the schema cache to use the changes immediately.
	// 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(CComBSTR("schemaUpdateNow"), varSchemaUpdate);
	hr = pRoot->SetInfo();
	if (FAILED(hr)) 
	{
		ReportErrorW(L"Force Schema Recalc failed.", hr);
}
 
cleanup:
	VariantClear(&varDSRoot);
	VariantClear(&varSchemaUpdate);

	if(pwszDSPath)
	{
		delete pwszDSPath;
}

	if(pwszGPCPath)
	{
		delete pwszGPCPath;
}

	if(pwszContPath)
	{
		delete pwszContPath;
}

	if(pDisp)
	{
		pDisp->Release();
}

	if(pSchema)
	{
		pSchema->Release();
}

	if(pRoot)
	{
		pRoot->Release();
}
 

	CoUninitialize();
}
 
// Simple error message reporter
 
void ReportErrorW(LPCWSTR pwszDefaultMsg, DWORD dwErr)
{
	DWORD   dwStatus;
	LPWSTR  pwszMsg;
 
	dwStatus = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM |
								FORMAT_MESSAGE_ALLOCATE_BUFFER |
								FORMAT_MESSAGE_IGNORE_INSERTS,
							 NULL,
							 dwErr,
							 LANG_NEUTRAL,
							 (LPWSTR)&pwszMsg,
							 64,
							 NULL); 
	if (dwStatus != 0) 
	{
		wprintf(L"%s %s", pwszDefaultMsg, pwszMsg);
		LocalFree(pwszMsg);
} 
	else 
	{
		wprintf(L"%s %X\n", pwszDefaultMsg, dwErr);
}
}