//

// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
//
// ***************************************************************************
//
//	Original Author: Rajesh Rao
//
// 	$Author: rajeshr $
//	$Date: 6/11/98 4:43p $
// 	$Workfile:ldaphelp.cpp $
//
//	$Modtime: 6/11/98 11:21a $
//	$Revision: 1 $	
//	$Nokeywords:  $
//
// 
//  Description: Contains the implementation the CLDAPHelper class. This is
//	a class that has many static helper functions pertaining to ADSI LDAP Provider
//***************************************************************************
/////////////////////////////////////////////////////////////////////////


#include "precomp.h"

LPCWSTR CLDAPHelper :: LDAP_CN_EQUALS						= L"LDAP://CN=";	
LPCWSTR CLDAPHelper :: LDAP_DISP_NAME_EQUALS				= L"(lDAPDisplayName=";
LPCWSTR CLDAPHelper :: OBJECT_CATEGORY_EQUALS_CLASS_SCHEMA		= L"(objectCategory=classSchema)";
LPCWSTR CLDAPHelper	:: SUB_CLASS_OF_EQUALS				= L"(subclassOf=";
LPCWSTR CLDAPHelper :: NOT_LDAP_NAME_EQUALS				= L"(!ldapDisplayName=";
LPCWSTR CLDAPHelper :: LEFT_BRACKET_AND					= L"(&";
LPCWSTR CLDAPHelper :: GOVERNS_ID_EQUALS				= L"(governsId=";
LPCWSTR CLDAPHelper :: CLASS_SCHEMA						= L"classSchema";
LPCWSTR CLDAPHelper :: CN_EQUALS						= L"cn=";

//***************************************************************************
//
// CLDAPHelper :: GetLDAPClassFromLDAPName
//
// Purpose : See Header
//***************************************************************************
HRESULT CLDAPHelper :: GetLDAPClassFromLDAPName(
	IDirectorySearch *pDirectorySearchSchemaContainer,
	LPCWSTR lpszSchemaContainerSuffix,
	PADS_SEARCHPREF_INFO pSearchInfo,
	DWORD dwSearchInfoCount,
	CADSIClass *pADSIClass
)
{
	// We map the object from the LDAP Display name
	// Hence we cannot directly do an ADsOpenObject().
	// We have to send an LDAP query for the instance of ClassSchema/AttributeSchema where the
	// ldapdisplayname attribute is the lpszObjectName parameter.
	HRESULT result = E_FAIL;

	// For the search filter;
	LPCWSTR lpszLDAPObjectName = pADSIClass->GetADSIClassName();
	LPWSTR lpszSearchFilter = NULL;
	if(lpszSearchFilter = new WCHAR[ wcslen(LDAP_DISP_NAME_EQUALS) + wcslen(lpszLDAPObjectName) + wcslen(RIGHT_BRACKET_STR) + 1])
	{
		try
		{
			wcscpy(lpszSearchFilter, LDAP_DISP_NAME_EQUALS);
			wcscat(lpszSearchFilter, lpszLDAPObjectName);
			wcscat(lpszSearchFilter, RIGHT_BRACKET_STR);
			ADS_SEARCH_HANDLE hADSSearch;
			if(SUCCEEDED(result = pDirectorySearchSchemaContainer->ExecuteSearch(lpszSearchFilter, NULL, -1, &hADSSearch)))
			{
				try
				{
					if(SUCCEEDED(result = pDirectorySearchSchemaContainer->GetNextRow(hADSSearch)) && result != S_ADS_NOMORE_ROWS)
					{
						// Get the column for the CN attribute
						ADS_SEARCH_COLUMN adsColumn;

						// Store each of the LDAP class attributes
						// Reset the LDAP and WBEM names to take care of change in case
						if(SUCCEEDED(result) && SUCCEEDED(result = pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)LDAP_DISPLAY_NAME_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
								{
									pADSIClass->SetADSIClassName(adsColumn.pADsValues->CaseIgnoreString);
									LPWSTR lpszWBEMName = CLDAPHelper::MangleLDAPNameToWBEM(adsColumn.pADsValues->CaseIgnoreString);

									try
									{
										pADSIClass->SetWBEMClassName(lpszWBEMName);
									}
									catch ( ... )
									{
										delete [] lpszWBEMName;
										throw;
									}

									delete [] lpszWBEMName;
								}
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						// Store each of the LDAP class attributes 
						if(SUCCEEDED(result) && SUCCEEDED(result = pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)COMMON_NAME_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetCommonName(adsColumn.pADsValues->CaseIgnoreString);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						// Special case for top since ADSI returns "top" as the parent class of "top" and we
						// will go into an infinite loop later if we dont check this
						if(pADSIClass->GetCommonName() && _wcsicmp(pADSIClass->GetCommonName(), TOP_CLASS) != 0)
						{
							if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
								result = E_FAIL;
							else
							{
								if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SUB_CLASS_OF_ATTR, &adsColumn)))
								{
									try
									{
										if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
											result = E_FAIL;
										else
											pADSIClass->SetSuperClassLDAPName(adsColumn.pADsValues->CaseIgnoreString);
									}
									catch ( ... )
									{
										pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
										throw;
									}

									pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								}
							}
						}

						if(SUCCEEDED(result) && SUCCEEDED(result = pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)GOVERNS_ID_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetGovernsID(adsColumn.pADsValues->CaseIgnoreString);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(result = pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SCHEMA_ID_GUID_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetSchemaIDGUID((adsColumn.pADsValues->OctetString).lpValue, (adsColumn.pADsValues->OctetString).dwLength);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)RDN_ATT_ID_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetRDNAttribute(adsColumn.pADsValues->CaseIgnoreString);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)DEFAULT_SECURITY_DESCRP_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetDefaultSecurityDescriptor(adsColumn.pADsValues->CaseIgnoreString);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(result = pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)OBJECT_CLASS_CATEGORY_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetObjectClassCategory(adsColumn.pADsValues->Integer);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						/*
						if(SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)NT_SECURITY_DESCRIPTOR_ATTR, &adsColumn)))
						{
							pADSIClass->SetNTSecurityDescriptor((adsColumn.pADsValues->SecurityDescriptor).lpValue, (adsColumn.pADsValues->SecurityDescriptor).dwLength);
							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
						*/
						if(SUCCEEDED(result) && SUCCEEDED(result = pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)DEFAULT_OBJECTCATEGORY_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
								{
									// Get the LDAPDIpslayName of the class
									LPWSTR lpszLDAPName = NULL;
									if(SUCCEEDED(result) && SUCCEEDED(result = GetLDAPClassNameFromCN(adsColumn.pADsValues->DNString, &lpszLDAPName)))
									{
										try
										{
											pADSIClass->SetDefaultObjectCategory(lpszLDAPName);
										}
										catch ( ... )
										{
											delete [] lpszLDAPName;
											throw;
										}

										delete [] lpszLDAPName;
									}
								}
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SYSTEM_ONLY_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetSystemOnly((BOOLEAN)adsColumn.pADsValues->Boolean);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)AUXILIARY_CLASS_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetAuxiliaryClasses(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SYSTEM_AUXILIARY_CLASS_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetSystemAuxiliaryClasses(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}

						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SYSTEM_MAY_CONTAIN_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetSystemMayContains(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)MAY_CONTAIN_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetMayContains(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SYSTEM_MUST_CONTAIN_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetSystemMustContains(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)MUST_CONTAIN_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetMustContains(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)SYSTEM_POSS_SUPERIORS_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetSystemPossibleSuperiors(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
						if(SUCCEEDED(result) && SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearch, (LPWSTR)POSS_SUPERIORS_ATTR, &adsColumn)))
						{
							try
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
									result = E_FAIL;
								else
									pADSIClass->SetPossibleSuperiors(adsColumn.pADsValues, adsColumn.dwNumValues);
							}
							catch ( ... )
							{
								pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								throw;
							}

							pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
						}
					}
					else
						result = E_FAIL;
				}
				catch ( ... )
				{
					// Close the search
					pDirectorySearchSchemaContainer->CloseSearchHandle(hADSSearch);

					throw;
				}

				// Close the search
				pDirectorySearchSchemaContainer->CloseSearchHandle(hADSSearch);
			}
		}
		catch ( ... )
		{
			if ( lpszSearchFilter )
			{
				// Delete the filter
				delete [] lpszSearchFilter;
				lpszSearchFilter = NULL;
			}

			throw;
		}

		if ( lpszSearchFilter )
		{
			// Delete the filter
			delete [] lpszSearchFilter;
			lpszSearchFilter = NULL;
		}
	}
	else
		result = E_OUTOFMEMORY;

	return result;
}


//***************************************************************************
//
// CLDAPHelper :: GetLDAPSchemaObjectFromCommonName
//
// Purpose : To fetch the IDirectoryObject interface on a class/property provided by the LDAP Provider
// Parameters:
//		lpszSchemaContainerSuffix : The suffix to be used. The actual object fetced will be:
//			LDAP://CN=<lpszCommonName>,<lpszSchemaContainerSuffix>
//		lpszCommonName : The 'cn' attribute of the LDAP class or property to be fetched. 
//		ppLDAPObject : The address where the pointer to IDirectoryObject will be stored
//			It is the caller's responsibility to delete the object when done with it
// 
//	Return Value: The COM status value indicating the status of the request.
//***************************************************************************
HRESULT CLDAPHelper :: GetLDAPSchemaObjectFromCommonName(
	LPCWSTR lpszSchemaContainerSuffix,
	LPCWSTR lpszCommonName, 
	IDirectoryObject **ppLDAPObject)
{
	HRESULT result = S_OK;

	// Form the ADSI path to the LDAP object
	LPWSTR lpszLDAPObjectPath = NULL;
	if(lpszLDAPObjectPath = new WCHAR[wcslen(LDAP_CN_EQUALS) + wcslen(lpszCommonName) + wcslen(COMMA_STR) + wcslen(lpszSchemaContainerSuffix) + 1])
	{
		wcscpy(lpszLDAPObjectPath, LDAP_CN_EQUALS);
		wcscat(lpszLDAPObjectPath, lpszCommonName);
		wcscat(lpszLDAPObjectPath, COMMA_STR);
		wcscat(lpszLDAPObjectPath, lpszSchemaContainerSuffix);

		result = ADsOpenObject(lpszLDAPObjectPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectoryObject, (LPVOID *)ppLDAPObject);

		delete[] lpszLDAPObjectPath;
	}
	else
		result = E_OUTOFMEMORY;
	return result;
}

//***************************************************************************
//
// CLDAPHelper :: GetLDAPClassNameFromCN
//
// Purpose : To fetch the LDAPDisplayNAme of a class from its path
// Parameters:
// 
//	lpszLDAPClassPath : The path to the class object without the LDAP prefix. Ex CN=user,CN=Schema, CN=COnfiguration ...
//	Return Value: The COM status value indicating the status of the request. The user should delete the
// name returned, when done
//***************************************************************************
HRESULT CLDAPHelper :: GetLDAPClassNameFromCN(LPCWSTR lpszLDAPClassPath, 
	LPWSTR *lppszLDAPName)
{
	IDirectoryObject *pLDAPClass = NULL;

	// Prepend the LDAP:// perfix
	LPWSTR lpszRealPath = NULL;
	HRESULT result = S_OK;
	if(lpszRealPath = new WCHAR[ wcslen(LDAP_PREFIX) + wcslen(lpszLDAPClassPath) + 1])
	{
		wcscpy(lpszRealPath, LDAP_PREFIX);
		wcscat(lpszRealPath, lpszLDAPClassPath);

		result = ADsOpenObject(lpszRealPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectoryObject, (LPVOID *)&pLDAPClass);
		delete [] lpszRealPath;
	}
	else
		result = E_OUTOFMEMORY;

	// Get the attribute LDAPDisplayName
	if(SUCCEEDED(result))
	{
		PADS_ATTR_INFO pAttributes = NULL;
		DWORD dwReturnCount = 0;
		if(SUCCEEDED(result = pLDAPClass->GetObjectAttributes((LPWSTR *)&LDAP_DISPLAY_NAME_ATTR, 1, &pAttributes, &dwReturnCount)) && dwReturnCount == 1)
		{
			if(pAttributes->pADsValues->dwType == ADSTYPE_PROV_SPECIFIC)
				result = E_FAIL;
			else
			{
				*lppszLDAPName = NULL;
				if(*lppszLDAPName = new WCHAR[wcslen(pAttributes->pADsValues->DNString) + 1])
					wcscpy(*lppszLDAPName, pAttributes->pADsValues->DNString);
				else
					result = E_OUTOFMEMORY;
			}
			FreeADsMem((LPVOID *)pAttributes);
		}

		pLDAPClass->Release();
	}
	return result;
}

//***************************************************************************
//
// CLDAPHelper :: EnumerateClasses
//
// Purpose : See Header
//***************************************************************************
HRESULT CLDAPHelper :: EnumerateClasses(
	IDirectorySearch *pDirectorySearchSchemaContainer,
	LPCWSTR lpszSchemaContainerSuffix,
	PADS_SEARCHPREF_INFO pSearchInfo,
	DWORD dwSearchInfoCount,
	LPCWSTR lpszLDAPSuperClass,
	BOOLEAN bDeep,
	LPWSTR **pppszClassNames,
	DWORD *pdwNumRows,
	BOOLEAN bArtificialClass)
{
	// Initialize the return values
	HRESULT result = E_FAIL;
	*pdwNumRows = 0;

	// The search filter;
	LPWSTR lpszSearchFilter = NULL;

	// There's various cases to be considered here.
	// if(lpszLDAPSuperClass is NULL)
	// then
	//		if bDeep is false, then no objects is returned (since we do not provide the LDAP base class
	//		else all the classes are returned using the filter (objectCategory=classSchema)
	//	else
	//		if bDeep is false, then the filter (&(objectCategory=classSchema)(subClassOf=lpszLDAPSuperClass)) is used
	//		else a lot of work has to be done!
	if(lpszLDAPSuperClass == NULL)
	{
		if(!bDeep)
		{
			*pppszClassNames = NULL;
			*pdwNumRows = 0;
			return S_OK;
		}
		else
		{
			if(!(lpszSearchFilter = new WCHAR[ wcslen(OBJECT_CATEGORY_EQUALS_CLASS_SCHEMA) + 1]))
				return E_OUTOFMEMORY;
			wcscpy(lpszSearchFilter, OBJECT_CATEGORY_EQUALS_CLASS_SCHEMA);
		}
	}
	else
	{
		if(!bDeep)
		{
			// One would imagine that a filter of the kind
			 //(&(objectClass=classSchema)(subClassOf=<lpszLDAPSuperClass>))
			// would be enough. Unfortunately it also gives the Top class
			//in the results when the value of lpszLDAPSuperClass is Top
			// we dont need that. Hnce we form the filter
			 //(&(objectClass=classSchema)(subClassOf=<lpszLDAPSuperClass>)(!ldapDisplayName=<lpszLDAPSuperClass>))
			if(lpszSearchFilter = new WCHAR[ wcslen(LEFT_BRACKET_AND)					// (&
									+ wcslen(OBJECT_CATEGORY_EQUALS_CLASS_SCHEMA)		// (objectCategory=classSchema)
									+ wcslen(SUB_CLASS_OF_EQUALS)					// (subClassOf=
									+ wcslen(lpszLDAPSuperClass)					// superClass
									+ wcslen(RIGHT_BRACKET_STR)							// )
									+ wcslen(NOT_LDAP_NAME_EQUALS)					// (!ldapDisplayName=
									+ wcslen(lpszLDAPSuperClass)					// superClass
									+ 2*wcslen(RIGHT_BRACKET_STR)						// ))
									+1])
			{
				wcscpy(lpszSearchFilter, LEFT_BRACKET_AND);
				wcscat(lpszSearchFilter, OBJECT_CATEGORY_EQUALS_CLASS_SCHEMA);
				wcscat(lpszSearchFilter, SUB_CLASS_OF_EQUALS);
				wcscat(lpszSearchFilter, lpszLDAPSuperClass);
				wcscat(lpszSearchFilter, RIGHT_BRACKET_STR);
				wcscat(lpszSearchFilter, NOT_LDAP_NAME_EQUALS);					// (!ldapDisplayName=
				wcscat(lpszSearchFilter, lpszLDAPSuperClass);
				wcscat(lpszSearchFilter, RIGHT_BRACKET_STR);
				wcscat(lpszSearchFilter, RIGHT_BRACKET_STR);
			}
			else
				result = E_OUTOFMEMORY;
		}
		else
			lpszSearchFilter = NULL; // THIS SPECIAL CASE IS TACKLED LATER
	}

	if(lpszSearchFilter)
	{
		ADS_SEARCH_HANDLE hADSSearchOuter;
		if(SUCCEEDED(result = pDirectorySearchSchemaContainer->ExecuteSearch(lpszSearchFilter, (LPWSTR *)&LDAP_DISPLAY_NAME_ATTR, 1, &hADSSearchOuter)))
		{
			*pdwNumRows = 0;
			DWORD dwFirstCount = 0; // Number of rows retreived on the first count

			// Calculate the number of rows first. 
			while(SUCCEEDED(result = pDirectorySearchSchemaContainer->GetNextRow(hADSSearchOuter)) &&
				result != S_ADS_NOMORE_ROWS)
				dwFirstCount ++;

			// Allocate enough memory for the classes and names
			*pppszClassNames = NULL;
			if(bArtificialClass)
			{
				dwFirstCount ++;
				if(*pppszClassNames = new LPWSTR [dwFirstCount])
					(*pppszClassNames)[0] = NULL;
				else
					result = E_OUTOFMEMORY;
			}
			else
			{
				if(!(*pppszClassNames = new LPWSTR [dwFirstCount]))
					result = E_OUTOFMEMORY;
			}

			// The index of the attribute being processed
			DWORD dwSecondCount = 0;
			if(bArtificialClass)
				dwSecondCount ++;

			// Get the columns for the attributes
			ADS_SEARCH_COLUMN adsColumn;

			// Move to the beginning of the search
			if(SUCCEEDED(result = pDirectorySearchSchemaContainer->GetFirstRow(hADSSearchOuter)) 
				&& result != S_ADS_NOMORE_ROWS)
			{
				// Store each of the LDAP class attributes 
				if(SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearchOuter, (LPWSTR)LDAP_DISPLAY_NAME_ATTR, &adsColumn)))
				{
					if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
					{
						result = E_FAIL;
						pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
					}
					else
					{
						// Create the CADSIClass
						(*pppszClassNames)[dwSecondCount] = NULL;
						if((*pppszClassNames)[dwSecondCount] = new WCHAR[wcslen(adsColumn.pADsValues->CaseIgnoreString) + 1])
							wcscpy((*pppszClassNames)[dwSecondCount], adsColumn.pADsValues->CaseIgnoreString);
						pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
	
						dwSecondCount++;

						// Get the rest of the rows
						while(SUCCEEDED(result = pDirectorySearchSchemaContainer->GetNextRow(hADSSearchOuter))&&
								result != S_ADS_NOMORE_ROWS)
						{
							// Store each of the LDAP class attributes 
							if(SUCCEEDED(pDirectorySearchSchemaContainer->GetColumn(hADSSearchOuter, (LPWSTR)LDAP_DISPLAY_NAME_ATTR, &adsColumn)))
							{
								if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
								{
									result = E_FAIL;
									pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );
								}
								else
								{
									// Create the CADSIClass
									(*pppszClassNames)[dwSecondCount] = NULL;
									if((*pppszClassNames)[dwSecondCount] = new WCHAR[wcslen(adsColumn.pADsValues->CaseIgnoreString) + 1])
										wcscpy((*pppszClassNames)[dwSecondCount], adsColumn.pADsValues->CaseIgnoreString);
									pDirectorySearchSchemaContainer->FreeColumn( &adsColumn );

									dwSecondCount++;
								}
							}
						}
					}
				}
			}

			// Something went wrong? Release allocated resources
			if(dwSecondCount != dwFirstCount)
			{
				// Delete the contents of the array
				for(DWORD j=0; j<dwSecondCount; j++)
				{
					delete [] (*pppszClassNames)[j];
				}

				// Delete the array itself
				delete [] (*pppszClassNames);

				// Set return values to empty
				*pppszClassNames = NULL;
				*pdwNumRows = 0;

				result = E_FAIL;
			}
			else
				*pdwNumRows = dwFirstCount;

			// Close the search
			pDirectorySearchSchemaContainer->CloseSearchHandle(hADSSearchOuter);

		} // ExecuteSearch() - Outer
		delete [] lpszSearchFilter;
	}
	else // THIS IS THE SPECIAL CASE WHERE ALL SUBCLASSES (RECURSIVELY) OF A GIVEN CLASS ARE REQUIRED
	{
		// A lot of work has to be done. THis is handled by CLDAPClassProvider. Hence control shold never reach here
		result = E_FAIL;
	}
	return result;
}


// Gets the IDIrectoryObject interface on an ADSI instance
HRESULT CLDAPHelper :: GetADSIInstance(LPCWSTR szADSIPath, CADSIInstance **ppADSIObject, ProvDebugLog *pLogObject)
{
	HRESULT result;
	IDirectoryObject *pDirectoryObject;
	*ppADSIObject = NULL;

	try
	{
		if(SUCCEEDED(result = ADsOpenObject((LPWSTR)szADSIPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectoryObject, (LPVOID *)&pDirectoryObject)))
		{

			if(*ppADSIObject = new CADSIInstance(szADSIPath, pDirectoryObject))
			{
				PADS_ATTR_INFO pAttributeEntries;
				DWORD dwNumAttributes;
				if(SUCCEEDED(result = pDirectoryObject->GetObjectAttributes(NULL, -1, &pAttributeEntries, &dwNumAttributes)))
				{
					(*ppADSIObject)->SetAttributes(pAttributeEntries, dwNumAttributes);
					PADS_OBJECT_INFO pObjectInfo = NULL;
					if(SUCCEEDED(result = pDirectoryObject->GetObjectInformation(&pObjectInfo)))
					{
						(*ppADSIObject)->SetObjectInfo(pObjectInfo);
					}
					else
						pLogObject->WriteW( L"CLDAPHelper :: GetADSIInstance GetObjectInformation() FAILED on %s with %x\r\n", szADSIPath, result);
				}
				else
					pLogObject->WriteW( L"CLDAPHelper :: GetADSIInstance GetObjectAttributes() FAILED on %s with %x\r\n", szADSIPath, result);
			}
			else
				result = E_OUTOFMEMORY;
			pDirectoryObject->Release();
		}
		else
			pLogObject->WriteW( L"CLDAPHelper :: GetADSIInstance ADsOpenObject() FAILED on %s with %x\r\n", szADSIPath, result);
	}
	catch ( ... )
	{
		if ( *ppADSIObject )
		{
			delete *ppADSIObject;
			*ppADSIObject = NULL;
		}

		throw;
	}

	if(!SUCCEEDED(result))
	{
		delete *ppADSIObject;
		*ppADSIObject = NULL;
	}

	return result;
}

//***************************************************************************
//
// CLDAPHelper :: CreateADSIPath
//
// Purpose : Forms the ADSI path from a class or property name
//
// Parameters:
//	lpszLDAPSchemaObjectName : The LDAP class or property name
//	lpszSchemaContainerSuffix : The suffix to be used. The actual object fetced will be:
//			LDAP://CN=<lpszLDAPSchemaObjectName>,<lpszSchemaContainerSuffix>
// 
//	Return Value: The ADSI path to the class or property object. This has to
//	be deallocated by the user
//***************************************************************************
LPWSTR CLDAPHelper :: CreateADSIPath(LPCWSTR lpszLDAPSchemaObjectName,	
									 LPCWSTR lpszSchemaContainerSuffix)
{
	LPWSTR lpszADSIObjectPath = NULL;
	if(lpszADSIObjectPath = new WCHAR[wcslen(LDAP_CN_EQUALS) + wcslen(lpszLDAPSchemaObjectName) + wcslen(COMMA_STR) + wcslen(lpszSchemaContainerSuffix) + 1])
	{
		wcscpy(lpszADSIObjectPath, LDAP_CN_EQUALS);
		wcscat(lpszADSIObjectPath, lpszLDAPSchemaObjectName);
		wcscat(lpszADSIObjectPath, COMMA_STR);
		wcscat(lpszADSIObjectPath, lpszSchemaContainerSuffix);
	}
	return lpszADSIObjectPath;
}

//***************************************************************************
//
// CLDAPHelper :: UnmangleWBEMNameToLDAP
//
// Purpose : Converts a mangled WBEM name to LDAP
//	An underscore in LDAP maps to two underscores in WBEM
//	An hyphen in LDAP maps to one underscore in WBEM
//
// Parameters:
//	lpszWBEMName : The WBEM class or property name
// 
//	Return Value: The LDAP name to the class or property object. This has to
//	be deallocated by the user
//***************************************************************************
LPWSTR CLDAPHelper :: UnmangleWBEMNameToLDAP(LPCWSTR lpszWBEMName)
{
	DWORD iPrefixLength = 0;
	if(_wcsnicmp(lpszWBEMName, LDAP_ARTIFICIAL_CLASS_NAME_PREFIX, LDAP_ARTIFICIAL_CLASS_NAME_PREFIX_LENGTH) == 0)
	{
		iPrefixLength = LDAP_ARTIFICIAL_CLASS_NAME_PREFIX_LENGTH;
	}
	else if (_wcsnicmp(lpszWBEMName, LDAP_CLASS_NAME_PREFIX, LDAP_CLASS_NAME_PREFIX_LENGTH) == 0)
	{
		iPrefixLength = LDAP_CLASS_NAME_PREFIX_LENGTH;
	}
	else
		return NULL;

	// The length of the resulting string (LDAP Name) is bound to be less than of equal to the length of WBEM name
	// So let's allocate the same as the wbem name length
	DWORD dwWbemNameLength = wcslen(lpszWBEMName) - iPrefixLength;
	LPWSTR lpszLDAPName = NULL;
	if(lpszLDAPName = new WCHAR[dwWbemNameLength + 1])
	{
		LPCWSTR lpszWBEMNameWithoutPrefix = lpszWBEMName + iPrefixLength;

		DWORD j=0;
		for(DWORD i=0; i<dwWbemNameLength; )
		{
			switch(lpszWBEMNameWithoutPrefix[i])
			{
				case (L'_'):
					if(lpszWBEMNameWithoutPrefix[i+1] == L'_')
					{
						i += 2;
						lpszLDAPName[j++] = L'_';
					}
					else
					{
						i++;
						lpszLDAPName[j++] = L'-';
					}
					break;

				default:
					lpszLDAPName[j++] = lpszWBEMNameWithoutPrefix[i++];

			}
		}
		lpszLDAPName[j] = NULL;
	}
	return lpszLDAPName;
}

//***************************************************************************
//
// CLDAPHelper :: MangleLDAPNameToWBEM
//
// Purpose : Converts a LDAP name to WBEM by mangling it
//	An underscore in LDAP maps to two underscores in WBEM
//	An hyphen in LDAP maps to one underscore in WBEM
//
// Parameters:
//	lpszLDAPName : The LDAP class or property name
// 
//	Return Value: The LDAP name to the class or property object. This has to
//	be deallocated by the user
//***************************************************************************
LPWSTR CLDAPHelper :: MangleLDAPNameToWBEM(LPCWSTR lpszLDAPName, BOOLEAN bArtificalName)
{
	if(!lpszLDAPName)
		return NULL;

	// The length of the resulting string (WBEM Name) is bound to be less than of equal to twice the length of LDAP name
	// So let's allocate double the LDAP name length
	DWORD dwLDAPNameLength = wcslen(lpszLDAPName);
	DWORD dwPrefixLength = (bArtificalName)? LDAP_ARTIFICIAL_CLASS_NAME_PREFIX_LENGTH : LDAP_CLASS_NAME_PREFIX_LENGTH;
	LPWSTR lpszWBEMName = NULL;
	
	if(lpszWBEMName = new WCHAR[2*dwLDAPNameLength + dwPrefixLength + 1])
	{
		// Prefix "DS_" or "ADS_"
		if(bArtificalName)
			wcscpy(lpszWBEMName, LDAP_ARTIFICIAL_CLASS_NAME_PREFIX);
		else
			wcscpy(lpszWBEMName, LDAP_CLASS_NAME_PREFIX);

		DWORD j=dwPrefixLength;

		for(DWORD i=0; i<dwLDAPNameLength; i++)
		{
			switch(lpszLDAPName[i])
			{
				case (__TEXT('-')):
					lpszWBEMName[j++] = L'_';
					break;

				case (__TEXT('_')):
					lpszWBEMName[j++] = L'_';
					lpszWBEMName[j++] = L'_';
					break;

				default:
					lpszWBEMName[j++] = lpszLDAPName[i];

			}
		}
		lpszWBEMName[j] = NULL;
	}
	return lpszWBEMName;
}

void CLDAPHelper :: DeleteAttributeContents(PADS_ATTR_INFO pAttribute)
{
	// delete the name
	delete [] pAttribute->pszAttrName;

	// Delete each value
	for(DWORD i=0; i<pAttribute->dwNumValues; i++)
		DeleteADsValueContents(pAttribute->pADsValues + i);

	// Delete the array of values
	delete [] pAttribute->pADsValues;
}

void CLDAPHelper :: DeleteADsValueContents(PADSVALUE pValue)
{
	switch(pValue->dwType)
	{
		// Nothing to delete
		case ADSTYPE_BOOLEAN:
		case ADSTYPE_INTEGER:
		case ADSTYPE_LARGE_INTEGER:
			break;
		
		case ADSTYPE_UTC_TIME:
		case ADSTYPE_DN_STRING:
		case ADSTYPE_CASE_EXACT_STRING:
		case ADSTYPE_CASE_IGNORE_STRING:
		case ADSTYPE_PRINTABLE_STRING:
		case ADSTYPE_NUMERIC_STRING:
			delete [] pValue->DNString;
			break;
		
		case ADSTYPE_OCTET_STRING:
		case ADSTYPE_NT_SECURITY_DESCRIPTOR:
			delete [] (pValue->OctetString.lpValue);
			break;
		case ADSTYPE_DN_WITH_BINARY:
			delete [] (pValue->pDNWithBinary->lpBinaryValue);
			delete [] (pValue->pDNWithBinary->pszDNString);
			delete pValue->pDNWithBinary;
			break;

		case ADSTYPE_DN_WITH_STRING:
			delete [] (pValue->pDNWithString->pszStringValue);
			delete [] (pValue->pDNWithString->pszDNString);
			delete pValue->pDNWithString;
			break;

		default:
		// Cause a Null Pointer violation intentionally
		// Otherwise we leak memory
		{
			assert(0);
		}
		break;
	}
}

//***************************************************************************
//
// CLDAPHelper :: ExecuteQuery
//
// Purpose : See Header
//***************************************************************************
HRESULT CLDAPHelper :: ExecuteQuery(
	LPCWSTR pszPathToRoot,
	PADS_SEARCHPREF_INFO pSearchInfo,
	DWORD dwSearchInfoCount,
	LPCWSTR pszLDAPQuery,
	CADSIInstance ***pppADSIInstances,
	DWORD *pdwNumRows,
	ProvDebugLog *pLogObject)
{
	// Initialize the return values
	HRESULT result = E_FAIL;
	*pdwNumRows = 0;
	*pppADSIInstances = NULL;

	// Bind to the node from which the search should start
	IDirectorySearch *pDirectorySearchContainer = NULL;
	if(SUCCEEDED(result = ADsOpenObject((LPWSTR)pszPathToRoot, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectorySearch, (LPVOID *)&pDirectorySearchContainer)))
	{
		try
		{
			// Now perform a search for the attribute DISTINGUISHED_NAME_ATTR name
			if(SUCCEEDED(result = pDirectorySearchContainer->SetSearchPreference(pSearchInfo, dwSearchInfoCount)))
			{
				ADS_SEARCH_HANDLE hADSSearchOuter;

				if(SUCCEEDED(result = pDirectorySearchContainer->ExecuteSearch((LPWSTR) pszLDAPQuery, (LPWSTR *)&ADS_PATH_ATTR, 1, &hADSSearchOuter)))
				{
					*pdwNumRows = 0;
					// Calculate the number of rows first. 
					while(SUCCEEDED(result = pDirectorySearchContainer->GetNextRow(hADSSearchOuter)) &&
						result != S_ADS_NOMORE_ROWS)
						(*pdwNumRows) ++;

					try
					{
						// Do only if there were any rows
						if(*pdwNumRows)
						{
							// The index of the attribute being processed
							DWORD i = 0;

							// Allocate enough memory for the classes and names
							*pppADSIInstances = NULL;
							if(*pppADSIInstances = new CADSIInstance * [*pdwNumRows])
							{
								try
								{
									// Get the columns for the attributes
									ADS_SEARCH_COLUMN adsColumn;
									CADSIInstance *pADSIInstance = NULL;

									// Move to the first row
									if (SUCCEEDED(result = pDirectorySearchContainer->GetFirstRow(hADSSearchOuter))&&
											result != S_ADS_NOMORE_ROWS)
									{
										// Store each of the LDAP class attributes 
										if(SUCCEEDED(pDirectorySearchContainer->GetColumn(hADSSearchOuter, (LPWSTR)ADS_PATH_ATTR, &adsColumn)))
										{
											try
											{
												if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
													result = E_FAIL;
												else
												{
													// Create the CADSIInstance
													// Now get the attributes on this object

													if(SUCCEEDED(result = GetADSIInstance(adsColumn.pADsValues->DNString, &pADSIInstance, pLogObject)))
													{
														(*pppADSIInstances)[i] = pADSIInstance;
														i++;
													}
													else
														pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery GetADSIInstance() FAILED on %s with %x\r\n", adsColumn.pADsValues->DNString, result);
												}
											}
											catch ( ... )
											{
												pDirectorySearchContainer->FreeColumn( &adsColumn );
												throw;
											}

											// Free resouces
											pDirectorySearchContainer->FreeColumn( &adsColumn );
										}
										else
											pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery GetColumn() FAILED on %s with %x\r\n", pszLDAPQuery, result);

										// Get the other rows now
										if(SUCCEEDED(result))
										{
											while(SUCCEEDED(result = pDirectorySearchContainer->GetNextRow(hADSSearchOuter))&&
													result != S_ADS_NOMORE_ROWS)
											{

												// Store each of the LDAP class attributes 
												if(SUCCEEDED(pDirectorySearchContainer->GetColumn(hADSSearchOuter, (LPWSTR)ADS_PATH_ATTR, &adsColumn)))
												{
													try
													{
														if(adsColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
															result = E_FAIL;
														else
														{
															// Create the CADSIInstance
															// Now get the attributes on this object
															if(SUCCEEDED(result = GetADSIInstance(adsColumn.pADsValues->DNString, &pADSIInstance, pLogObject)))
															{
																(*pppADSIInstances)[i] = pADSIInstance;
																i++;
															}
															else
																pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery GetADSIInstance() FAILED on %s with %x\r\n", adsColumn.pADsValues->DNString, result);
														}
													}
													catch ( ... )
													{
														pDirectorySearchContainer->FreeColumn( &adsColumn );
														throw;
													}

													// Free resouces
													pDirectorySearchContainer->FreeColumn( &adsColumn );
												}
												else
													pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery GetColumn() FAILED on %s with %x\r\n", pszLDAPQuery, result);
											}
										}
									}
									else
										pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery GetFirstRow() FAILED on %s with %x\r\n", pszLDAPQuery, result);
								}
								catch ( ... )
								{
									// Delete the contents of the array
									for(DWORD j=0; j<i; j++)
										delete (*pppADSIInstances)[j];

									// Delete the array itself
									delete [] (*pppADSIInstances);

									// Set return values to empty
									*pppADSIInstances = NULL;
									*pdwNumRows = 0;

									throw;
								}
							}

							// Something went wrong? Release allocated resources
							if(i != *pdwNumRows)
							{
								pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery() Difference between Number of rows in 2 searches %d %d on %s Am invalidating the search as FAILED\r\n", i, *pdwNumRows, pszLDAPQuery);
								
								// Delete the contents of the array
								for(DWORD j=0; j<i; j++)
									delete (*pppADSIInstances)[j];

								// Delete the array itself
								delete [] (*pppADSIInstances);

								// Set return values to empty
								*pppADSIInstances = NULL;
								*pdwNumRows = 0;

								result = E_FAIL;
							}
						}
					}
					catch ( ... )
					{
						pDirectorySearchContainer->CloseSearchHandle(hADSSearchOuter);
						throw;
					}

					// Close the search. 
					pDirectorySearchContainer->CloseSearchHandle(hADSSearchOuter);
				} // ExecuteSearch() 
				else
					pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery ExecuteSearch() %s FAILED with %x\r\n", pszLDAPQuery, result);
			} // SetSearchPreference()
			else
				pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery SetSearchPreference() on %s FAILED with %x \r\n", pszPathToRoot, result);
		}
		catch ( ... )
		{
			pDirectorySearchContainer->Release();
			throw;
		}

		pDirectorySearchContainer->Release();
	} // ADsOpenObject
	else
		pLogObject->WriteW( L"CLDAPHelper :: ExecuteQuery ADsOpenObject() on %s FAILED with %x \r\n", pszPathToRoot, result);

	return result;
}