//
// $Id: MetabaseKeysProbe.cpp,v 1.1 2004/06/01 17:05:22 bakerj Exp $
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the OVAL Definition Interpreter.
//
// The OVAL Definition Interpreter is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License.
//
// The OVAL Definition Interpreter is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with the OVAL
// Definition Interpreter; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//
//****************************************************************************************//

#include "MetabaseKeysProbe.h"

//****************************************************************************************//
//								MetabaseKeysProbe Class									  //	
//****************************************************************************************//
MetabaseKeysProbe::MetabaseKeysProbe()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	//
	// -----------------------------------------------------------------------
}

MetabaseKeysProbe::~MetabaseKeysProbe()
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Do nothing for now
	//
	// -----------------------------------------------------------------------
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
pdVector MetabaseKeysProbe::Run(ProbeData *probeDataIn)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Run the Metabase probe. Return a vector of MetabaseKeysProbe. Supporting pattern 
	//	match on key, and id
	//
	//
	//	TO DO:
	//	Name is not currently supported. We don't know how to get it.
	// -----------------------------------------------------------------------

	MetabaseKeyData *mkIn = (MetabaseKeyData*)probeDataIn;
	
	pdVector returnVector;

	// Initialize the COM library for use by the calling thread.
	//
	// NOTE: Define _WIN32_DCOM (in your project settings pre-processor defines) inorder to
	// use CoInitializeEx() The API is specific to DCOM release of the OS is surrounded by
	// conditional compilation statements that require you to #define to access the
	// definitions.

	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

	// Create a single uninitialized object of the MSAdminBase class.
	
	HRESULT hRes;
	IMSAdminBase* pIABase;

	hRes = CoCreateInstance(CLSID_MSAdminBase,		// Class identifier (CLSID) of the object
						    NULL,					// Pointer to controlling IUnknown
							CLSCTX_ALL,				// Context for running executable code
							IID_IMSAdminBase,		// Reference to the identifier of the interface
							(void**)&pIABase);		// Address of output variable

	if (hRes != ERROR_SUCCESS)
	{
		if (hRes == REGDB_E_CLASSNOTREG)
		{
			// This error is thrown when the Metabase doesn't exist on the computer. (ie when
			// IIS isn't installed)

			string errorMessage = "";
			errorMessage.append("The metabase could not be found, most likely because IIS ");
			errorMessage.append("isn't installed.");

			MetabaseKeyData *tmp = new MetabaseKeyData();
			returnVector.push_back(tmp);
			tmp = NULL;

		}else
		{
			string errorMessage = "";
			errorMessage.append("(MetabaseKeysProbe) Failed to create instance of AdminBase ");
			errorMessage.append("object inorder to gather data from the metabase.");
			throw MetabaseKeysProbeException(errorMessage);
		}

	}
	else
	{
		/////////////////////////////////////////////////////////////////
		//	The metabase exists and the COM library was successfully
		//	initialized
		/////////////////////////////////////////////////////////////////

		sVector keys;

		//	Look for pattern match types
		if(mkIn->key->type != PATTERN_MATCH_TYPE && mkIn->identifier->type != PATTERN_MATCH_TYPE)
		{
			//	Only literal types
			returnVector.push_back(GetMetaData(mkIn->key->data, mkIn->identifier->data, pIABase));

		}else
		{
			try
			{
				//	Get the matching keys or use provided key
				if(mkIn->key->type == PATTERN_MATCH_TYPE)
				{
					try
					{
						myMatcher->Reset();

						GetMatchingKeys("", mkIn->key->data , &keys, pIABase);

					}catch(REGEXException ex)
					{
						if(ex.GetSeverity() == ERROR_WARN)
						{
							string pcreMsg = "";
							pcreMsg.append("Metabase Keys Probe Warning - while searching for matching keys:\n");
							pcreMsg.append("----------------------------------------------------------------\n");
							pcreMsg.append(ex.GetErrorMessage());
							if(Log::verboseMode) {
								cout << pcreMsg << "\n"<< endl;
								Log::WriteLog(pcreMsg + "\n\n");
							}
							
						}else
							throw;
					}

				}else
				{
					//	Ensure key exists
					if(ValidateKey(mkIn->key->data, pIABase))
						keys.push_back(mkIn->key->data);
					else
						throw MetabaseKeysProbeException("(MetabaseKeysProbe) the key'" + mkIn->key->data + "' was not found.");
					
				}

				//	For each key found
				sVector::iterator key;
				for (key=keys.begin(); key!=keys.end(); key++)
				{
					//	Get the matching identifiers or use provided identifier
					if(mkIn->identifier->type == PATTERN_MATCH_TYPE)
					{
						//	Reset the pattern matching utility
						myMatcher->Reset();

						GetMatchingIdentifiers((*key), mkIn->identifier->data, &returnVector, pIABase);
					
					}else
						returnVector.push_back(GetMetaData((*key), mkIn->identifier->data, pIABase));
				}
			}catch(Exception ex)
			{
				MetabaseKeyData *tmp = new MetabaseKeyData();
				returnVector.push_back(tmp);
				tmp = NULL;

			}catch(...)
			{
				MetabaseKeyData *tmp = new MetabaseKeyData();
				tmp->msg = "Error: Unknown exception has occured.";
				returnVector.push_back(tmp);
				tmp = NULL;
			}
		}
	}

	// Close the COM library on the current thread
	CoUninitialize();

	//	Before returning ensure that all tests have the testId set
	pdVector::iterator pdIterator;
	for (pdIterator=returnVector.begin(); pdIterator!=returnVector.end(); pdIterator++)
	{
		(*pdIterator)->SetTestId(mkIn->GetTestId());
	}

	return (returnVector);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
void MetabaseKeysProbe::GetMatchingIdentifiers(string strKeyIn, string pattern, pdVector *returnVectorIn, IMSAdminBase *pIABase)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Given a key in the Metabase, this function will get the data for each
	//  identifier associated with it that matches the specified pattern. The data
	//	is then stored in MetabaseKeyData object and pushed onto the return vector.
	//
	// -----------------------------------------------------------------------

	HRESULT hRes;
	DWORD numEntries;
	DWORD dataSetNumber;
	string workingKey				= strKeyIn;
	MetabaseKeyData *tmpWinMBKey	= NULL;

	/////////////////////////////////////////////////////////////////
	//	Prepare the working key
	//	Reverse the slashes so they are all facing ->
	//	Ensure that there is a leading slash
	/////////////////////////////////////////////////////////////////
	workingKey = Common::SwitchChar(workingKey, "\\", "/");
	if(workingKey.find("/") != 0)
		workingKey.insert(0, "/");


	LPWSTR pbKey;
	size_t size = mbstowcs(NULL, workingKey.c_str(), workingKey.length()) + 1;
	pbKey = new wchar_t[size];
	size = mbstowcs(pbKey, workingKey.c_str(), size) + 1;
	if (size == -1)
	{
		string errorMessage = "";
		errorMessage.append("There was an error converting to the key '");
		errorMessage.append(strKeyIn);
		errorMessage.append("to UNICODE.");
		throw MetabaseKeysProbeException(errorMessage);
	}

	/////////////////////////////////////////////////////////////////
	//	Get all the data for the key
	/////////////////////////////////////////////////////////////////

	// Initialize the buffer to store the list of identifiers from the metabase with the
	// given path.  We set up a zero length buffer to start with because we do not know
	// how big it has to be.  The first call to GetAllData will therefore fail, but will
	// return the necessary buffer size which we will use to redimension the buffer.
	DWORD dwReqIdentBufLen = 0;
	DWORD dwIdentBufLen = 0; 
	unsigned char* pbIdentBuffer = new unsigned char[0];


	// Try to retrieve the different identifiers associated with the specified key in the
	// metabase.  Check to see if GetAllData failed due to an insufficient buffer.
	// NOTE: This is going to fail the first time because we have dwIdentBufLen=0 the first
	// time through.  When it fails, it returns the required buffer size as dwReqIdentBufLen
	// which we will use to reset dwIdentBufLen.
	while (true)
	{
		hRes = pIABase->GetAllData(METADATA_MASTER_ROOT_HANDLE,		// metabase handle
	                               pbKey,							// path to the key, relative to handle
	                               METADATA_NO_ATTRIBUTES,			// flags used to get the data
	                               ALL_METADATA,					// user type of data
	                               ALL_METADATA,					// data type of data
	                               &numEntries,						// the number of entries copied to pbBuffer
	                               &dataSetNumber,					// points to a number associated with this data set
							       dwIdentBufLen,					// the size, in wchars, of buffer
							       pbIdentBuffer,					// the buffer that receives the data
							       &dwReqIdentBufLen);				// if the method fails, receives the required buffer size
		
		if(hRes == ERROR_SUCCESS)
		{
			DWORD i;

			try
			{
				// Walk through the list of identifiers and data.  pbIdentBuffer should contain
				// an array of METADATA_GETALL_RECORD

				METADATA_GETALL_RECORD mdGetAllRecord;
				unsigned int offset = 0;
				unsigned int dataOffset = 0;
				for (i=0; i<numEntries; i++)
				{
					offset = (i * sizeof(METADATA_GETALL_RECORD));
					//	Get the current Record
					memcpy(&mdGetAllRecord, (pbIdentBuffer + offset), sizeof(METADATA_GETALL_RECORD));

					//	Create a new MetabaseKeyData object to store the results in. 
					tmpWinMBKey = new MetabaseKeyData();

					//	Get the identifier
					tmpWinMBKey->identifier->data = GetIdentifier(&mdGetAllRecord);

					//	Id the identifier matches the pattern add it to the result vector
					if(myMatcher->IsMatch(pattern.c_str(), tmpWinMBKey->identifier->data.c_str()))
					{
						//	Get the remaining data
						tmpWinMBKey->key->data = strKeyIn;
						tmpWinMBKey->data_type = GetDataType(&mdGetAllRecord);
						tmpWinMBKey->user_type = GetUserType(&mdGetAllRecord);
						tmpWinMBKey->data = GetData(&mdGetAllRecord, pbIdentBuffer);

						// Add the name to the structure.
						// TODO - Can not figure out where/how to get the name that corresponds to the ID.
						tmpWinMBKey->name->data = "NOT SUPPORTED";
						tmpWinMBKey->msg.append("Currently unable to retrieve name information for metabase keys.");

						//	Add the data to the return vector
						returnVectorIn->push_back(tmpWinMBKey);
					}
				}

				//	Break out of the while true loop
				break;

			}catch(REGEXException ex) 
			{
				if(ex.GetSeverity() == ERROR_WARN)
				{
					string pcreMsg = "";
					pcreMsg.append("Metabase Keys Probe Warning - while searching for matching identifiers:\n");
					pcreMsg.append("-----------------------------------------------------------------------\n");
					pcreMsg.append(ex.GetErrorMessage());
					if(Log::verboseMode) {
						cout << pcreMsg << "\n"<< endl;
						Log::WriteLog(pcreMsg + "\n\n");
					}
				}else
				{
					tmpWinMBKey->key->data = strKeyIn;
					tmpWinMBKey->msg = ex.GetErrorMessage();
					returnVectorIn->push_back(tmpWinMBKey);				
				}

				//	If pattern fails once no need to continue
				break;
			}

		}else if (hRes == RETURNCODETOHRESULT(ERROR_INSUFFICIENT_BUFFER))
		{
			// Setup the buffer to handle the overflow.
			delete [] (pbIdentBuffer); 
			pbIdentBuffer = new unsigned char[dwReqIdentBufLen]; 
			dwIdentBufLen = dwReqIdentBufLen;

		//	An invalid parameter was specified.
		}else if(hRes == RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER))
		{
			string errorMessage = "";

			errorMessage.append("(MetabaseKeysProbe) Invalid parameter specified while attempting to get all data for key: '");
			errorMessage.append(strKeyIn);
			errorMessage.append("' pattern: '");
			errorMessage.append(pattern);
			errorMessage.append("'.");

			throw MetabaseKeysProbeException(errorMessage);

		//	Finished enumerating data for the key
		}else if(hRes == RETURNCODETOHRESULT(ERROR_ACCESS_DENIED))
		{
			string errorMessage = "";

			errorMessage.append("(MetabaseKeysProbe) Error while attmpting to get all data. Access is denied to  key: '");
			errorMessage.append(strKeyIn);
			errorMessage.append("' pattern: '");
			errorMessage.append(pattern);
			errorMessage.append("'.");

			throw MetabaseKeysProbeException(errorMessage);

		//	The path not found error should not occure.
		//	At this point only paths that have been found in the metabase
		//	are being processed. 
		}else if(hRes == RETURNCODETOHRESULT(ERROR_PATH_NOT_FOUND))
		{
			string errorMessage = "";

			errorMessage.append("(MetabaseKeysProbe) Unable to find the path: '");
			errorMessage.append(strKeyIn);
			errorMessage.append("' while attempting to get all its data. Pattern used: '");
			errorMessage.append(pattern);
			errorMessage.append("'.");

			throw MetabaseKeysProbeException(errorMessage);

		//	All expected return values have been handled. This case should never occure
		}else
		{
			char strErrorCode[33];
			_itoa(HRESULTTOWIN32(hRes), strErrorCode, 10);

			string errorMessage = "";

			errorMessage.append("(MetabaseKeysProbe) An unknown exception has occured while getting data for the key: '");
			errorMessage.append(strKeyIn);
			errorMessage.append("' pattern: '");
			errorMessage.append(pattern);
			errorMessage.append("' - Error code: ");
			errorMessage.append(strErrorCode);
			errorMessage.append("");

			throw MetabaseKeysProbeException(errorMessage);
		}
	}

	//	Delete the indentifier buffer
	delete [] (pbIdentBuffer);

	/////////////////////////////////////////////////////////////////
	//	Ensure that at least one Metabase test is created
	//	If the condition is met then at the least the key matched
	//	just the identifier didn't match
	/////////////////////////////////////////////////////////////////
	if(returnVectorIn->size() == 0)
	{
		tmpWinMBKey = new MetabaseKeyData();
		tmpWinMBKey->key->data = strKeyIn;
		tmpWinMBKey->msg = "(MetabaseKeysProbe) Unable to find a matching identifier.";
		returnVectorIn->push_back(tmpWinMBKey);
	}
}

void MetabaseKeysProbe::GetMatchingKeys(string keyIn, string pattern, sVector *keys, IMSAdminBase *pIABase)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Search the metabase for keys under the input key that match the pattern. 
	//	Add all matches to the keys vector. For every sub key found make recursive
	//	call. stop when all subkeys have been searched
	//
	// -----------------------------------------------------------------------

	LONG res;													//	result from enumerating the subkeys
	LPWSTR name			= new wchar_t[METADATA_MAX_NAME_LEN];//	buffer to store the subkey name
	LPWSTR pszMDPath	= new wchar_t[METADATA_MAX_NAME_LEN];//
	DWORD dwIndex		= 0;									//	index of subkey to enumerate
	string workingKey	= "";									//	The name of the keyIn and the subkey concatenated
	string tmpKey		= "";
	string errorMessage = "";									//	
	char *strName		= (char *)malloc(METADATA_MAX_NAME_LEN);//


	//for (dwIndex = 0, res = ERROR_SUCCESS; res == ERROR_SUCCESS; dwIndex++)
	do
	{
		//	Reset the buffers
		ZeroMemory(name, METADATA_MAX_NAME_LEN);
		ZeroMemory(pszMDPath, METADATA_MAX_NAME_LEN);
		ZeroMemory(strName, METADATA_MAX_NAME_LEN);

		//	Get the working key as a string
		workingKey = keyIn;

		//	Convert the key from a string to a wchar_t*.
		//	Must add 1 to size to include the NULL at the end of the string
		size_t size = mbstowcs(NULL, workingKey.c_str(), workingKey.length()) + 1;
		//pszMDPath = new wchar_t[size];
		size = mbstowcs(pszMDPath, workingKey.c_str(), size) + 1;
		if (size == -1)
		{
			string errorMessage = "";
			errorMessage.append("There was an error converting the key '");
			errorMessage.append(workingKey);
			errorMessage.append("to UNICODE.");
			throw MetabaseKeysProbeException(errorMessage);
		}

		res = pIABase->EnumKeys(METADATA_MASTER_ROOT_HANDLE,	//	metabase handle.
								pszMDPath,						//	path to the key.
								name,							//	receives the name of the subkey.
								dwIndex);						//	index of the subkey.


		//	Check results
		if(res == ERROR_SUCCESS)
		{
			//	Convert the new name to a string
			size = wcstombs(strName, name, METADATA_MAX_NAME_LEN);

			//	Add the subkey to the working key
			workingKey.append("/");
			workingKey.append(strName);

			//	If a match add the new key to the keys vector
			tmpKey = Common::SwitchChar(workingKey, "/", "\\");
			if(tmpKey.find("\\") == 0)
				tmpKey.erase(0, 1);
	
			if(myMatcher->IsMatch(pattern.c_str(), tmpKey.c_str()))
				keys->push_back(tmpKey);

			//	Make recursive call
			GetMatchingKeys(workingKey, pattern, keys, pIABase);
		}

		//	Increment index
		dwIndex++;

	}while(res == ERROR_SUCCESS);

	delete [] (pszMDPath);
	delete [] (name);
	delete [] (strName);

}


MetabaseKeyData* MetabaseKeysProbe::GetMetaData(string strKeyIn, string strIdentifierIn, IMSAdminBase *pIABase)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Given a key and an identifier in the Metabase, this function will first check that
	//	the key exists. Then it will get the data for the key and identifier it. 
	//	The data is then stored in MetabaseKeyData object and passed back to the
	//  calling function.
	//
	// -----------------------------------------------------------------------

	HRESULT hRes;
	string workingKey = strKeyIn;

	//	Initialize the MetabaseKeyData object used for output.
	MetabaseKeyData* tmp = new MetabaseKeyData();


	/////////////////////////////////////////////////////////////////
	//	Check that the key exists. Store it if it does
	//	Throw an exception if not
	/////////////////////////////////////////////////////////////////
	if(ValidateKey(strKeyIn, pIABase))	
		tmp->key->data = strKeyIn;
	else
		throw MetabaseKeysProbeException("(MetabaseKeysProbe) the key'" + strKeyIn + "' was not found.");


	/////////////////////////////////////////////////////////////////
	//	Prepare the working key
	//	Reverse the slashes so they are all facing ->
	//	Ensure that there is a leading slash
	/////////////////////////////////////////////////////////////////
	workingKey = Common::SwitchChar(workingKey, "\\", "/");
	if(workingKey.find("/") != 0)
		workingKey.insert(0, "/");

	//	Convert the key from a string to a wchar_t*.
	//	Must add 1 to size to include the NULL at the end of the string
	LPWSTR pbKey;
	size_t size = mbstowcs(NULL, workingKey.c_str(), workingKey.length()) + 1;
	pbKey = new wchar_t[size];
	size = mbstowcs(pbKey, workingKey.c_str(), size) + 1;
	if (size == -1)
	{
		string errorMessage = "";
		errorMessage.append("There was an error converting the key '");
		errorMessage.append(workingKey);
		errorMessage.append("to UNICODE.");
		throw MetabaseKeysProbeException(errorMessage);
	}

	/////////////////////////////////////////////////////////////////
	//	Check that the identifier exists and get the data
	/////////////////////////////////////////////////////////////////

	// Initialize the buffer to store the data from the metabase.  We set up a zero length
	// buffer to start with because we do not know how big it has to be.  The first call
	// to GetData will therefore fail, but will return the necessary buffer size which we
	// will use to redimension the buffer.

	DWORD dwReqDataBufLen = 0;

	// Initialize a METADATA_RECORD structure that will store the information about the
	// key we want to look at.

	METADATA_RECORD mdRec;

	mdRec.dwMDAttributes = METADATA_NO_ATTRIBUTES;
	mdRec.dwMDDataLen = 0;
	mdRec.dwMDDataType = ALL_METADATA;
	mdRec.dwMDIdentifier = atoi(strIdentifierIn.c_str());
	mdRec.dwMDUserType = ALL_METADATA;
	mdRec.pbMDData = new BYTE[0];

	// Use the METADATA_RECORD to retrieve the data for the specified key.  Remember that
	// this call to GetData() should fail due to an insufficient buffer.
	hRes = pIABase->GetData(METADATA_MASTER_ROOT_HANDLE,		// metabase handle
							pbKey,								// path to the key, relative to handle
							&mdRec,								// pointer to a structure that receives the data
							&dwReqDataBufLen);					// the required data length in the case of an overflow.

	//	Setup the buffer to handle the overflow and call GetData() again.
	if (hRes == RETURNCODETOHRESULT(ERROR_INSUFFICIENT_BUFFER))
	{
		delete [] (mdRec.pbMDData); 
		mdRec.pbMDData = new BYTE[dwReqDataBufLen]; 
		mdRec.dwMDDataLen = dwReqDataBufLen;

		hRes = pIABase->GetData(METADATA_MASTER_ROOT_HANDLE,	// metabase handle
								pbKey,							// path to the key, relative to handle
								&mdRec,							// pointer to a structure that receives the data
								&dwReqDataBufLen);				// the required data length in the case of an overflow.
	}

	if(hRes == ERROR_PATH_NOT_FOUND || hRes == MD_ERROR_DATA_NOT_FOUND)
	{
		tmp->msg = "(MetabaseKeysProbe) The identifier: '" + strIdentifierIn + "' could not be found under key: '" + strKeyIn + "'.";

	}else if (hRes != ERROR_SUCCESS)
	{
		tmp->msg = "(MetabaseKeysProbe) Failed to get data for identifier: '" + strIdentifierIn + "' under key: '" + strKeyIn + "'.";
	}else
	{
		//	We now have the data we want.  Fill out the MetabaseKeyData object
		RetrieveInfo(&tmp, strKeyIn, &mdRec);
	}

	// We are all done so delete the data buffer.
	delete [] (mdRec.pbMDData);

	return tmp;
}

void MetabaseKeysProbe::GetMetaKeys(pdVector *returnVectorIn, string strIdentifierIn, IMSAdminBase *pIABase)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Given an identifier in the Metabase, this function will get the data for each
	//  key associated with it.  The data is then stored in WinMetabaseKeyData object
	//  and pushed onto the return vector.
	//
	// -----------------------------------------------------------------------

	HRESULT hRes;

	// Initialize the buffer to store the list of paths from the metabase with the given
	// identifier.  We set up a zero length buffer to start with because we do not know
	// how big it has to be.  The first call to GetDataPaths will therefore fail, but will
	// return the necessary buffer size which we will use to redimension the buffer.
	//
	// NOTE: We assign pbTempPathBuffer to point to the beginning of pbPathBuffer.  That
	// way, when we are finished walking down the list of paths in pbPathBuffer, we can
	// still find the start of the buffer to delete it.

	DWORD dwReqPathBufLen = 0;
	DWORD dwPathBufLen = 0; 
	LPWSTR pbPathBuffer = new wchar_t[0];
	LPWSTR pbTempPathBuffer = pbPathBuffer;

	// Try to retrieve the different paths where the specified identifier is found in the
	// metabase.  Check to see if GetDataPaths failed due to an insufficient buffer.
	// NOTE: This is going to fail the first time because we have dwPathBufLen=0 the first
	// time through.  When it fails, it returns the required buffer size as dwReqPathBufLen
	// which we will use to reset dwPathBufLen.

	while (true)
	{
		// TODO - GetDataPaths() fails when not run as Administrator.
		// All the info I have found says the only the Administrator has permission to work
		// with the metabase.  I got the impression that this was just writing to the
		// metabase (metabase.bin) but since all I am doing is reading from it and it is
		// failing, I now assume that it is access in general.  I can not find a way around
		// this.

		hRes = pIABase->GetDataPaths(METADATA_MASTER_ROOT_HANDLE,		// metabase handle
									 L"",								// path to the key, relative to handle
									 atoi(strIdentifierIn.c_str()),		// identifier of the data
									 ALL_METADATA,						// type of data
									 dwPathBufLen,						// the size, in wchars, of buffer
									 pbPathBuffer,						// the buffer that receives the data
									 &dwReqPathBufLen);					// if the method fails, receives the required buffer size
		
		if(hRes == ERROR_SUCCESS)
		{
			// Walk through the list of paths.  pbPathBuffer should contain a list of paths
			// seperated by null characters.  The end of the list is siginfied by a two
			// null characters.

			wchar_t* tmpStr = pbPathBuffer;
			wint_t tmpChar = NULL;

			while (wcslen(pbPathBuffer) != 0)
			{
				// Convert the UNICODE path to a char path.

				char* szChar = NULL;
				size_t size = 0;
				string strPath = "";

				if((size = wcstombs(0, pbPathBuffer, 0)) != -1)
				{
					szChar = new char[size + 1];
					szChar[size] = NULL;

					wcstombs(szChar, pbPathBuffer, size);

					strPath = Common::SwitchChar(szChar+1, "/", "\\");
					strPath = strPath.erase(strPath.length()-1, 1);
				}

				if (size == -1)
				{
					MetabaseKeyData* tmp = new MetabaseKeyData();
					tmp->identifier->data.append(strIdentifierIn);
					tmp->msg.append("There was an error converting from UNICODE key.");
					(*returnVectorIn).push_back(tmp);
				}
				else
				{
					(*returnVectorIn).push_back(GetMetaData(strPath,
			                                                strIdentifierIn,
											                pIABase));
				}

				// Advance the path buffer to the next path in the list.

				tmpStr = wcschr(pbPathBuffer, tmpChar);
				pbPathBuffer = tmpStr + 1;
			}

			break;
		}
		else if (hRes == RETURNCODETOHRESULT(ERROR_INSUFFICIENT_BUFFER))
		{
			// Setup the buffer to handle the overflow.

			delete [] (pbPathBuffer); 
			pbPathBuffer = new wchar_t[dwReqPathBufLen]; 
			dwPathBufLen = dwReqPathBufLen;

			// Reset pbTempPathBuffer to point to the beginning of the new pbPathBuffer.

			pbTempPathBuffer = pbPathBuffer;

			continue;
		}
		//else if (hRes == RETURNCODETOHRESULT(ERROR_INVALID_PARAMETER))
		//{
		//	break;
		//}
		//else if (hRes == RETURNCODETOHRESULT(ERROR_NOT_ENOUGH_MEMORY))
		//{
		//	break;
		//}
		else if (hRes == RETURNCODETOHRESULT(ERROR_ACCESS_DENIED))
		{
			string errorMessage = "";

			errorMessage.append("(MetabaseKeysProbe) Invalid access permissions to get ");
			errorMessage.append("data for identifier '");
			errorMessage.append(strIdentifierIn);
			errorMessage.append("'.");

			throw MetabaseKeysProbeException(errorMessage);
		}
		else if (hRes == RETURNCODETOHRESULT(ERROR_PATH_NOT_FOUND))
		{
			// The specified identifier was not found in the metabase.  Break out of
			// this loop but do not throw an error.

			MetabaseKeyData* tmp = new MetabaseKeyData();
					
			tmp->identifier->data.append(strIdentifierIn);
			tmp->msg.append("The identifier could not be found.");

			(*returnVectorIn).push_back(tmp);
		}
		else
		{
			char strErrorCode[33];
			_itoa(HRESULTTOWIN32(hRes), strErrorCode, 10);

			string errorMessage = "";

			errorMessage.append("(MetabaseKeysProbe) Failed to get data for identifier '");
			errorMessage.append(strIdentifierIn);
			errorMessage.append("' - error # ");
			errorMessage.append(strErrorCode);
			errorMessage.append("");

			throw MetabaseKeysProbeException(errorMessage);
		}
	}

	// Delete the path buffer using our pointer to the beginning of the buffer held in
	// pbTempPathBuffer.

	delete [] (pbTempPathBuffer);
}

void MetabaseKeysProbe::RetrieveInfo(MetabaseKeyData** tmp, string pathIn, METADATA_RECORD* metaRecordIn)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Get the information from the METADATA_RECORD
	//
	//	TO DO
	//	Figure out how to get the name...
	// -----------------------------------------------------------------------

	DWORD i;

	// Add the path to the structure.
	(*tmp)->key->data = Common::SwitchChar(pathIn, "/", "\\");


	//	Add the identifier to the structure.
	char strIdentifier[10];
	ZeroMemory(strIdentifier, sizeof(strIdentifier));
	_snprintf(strIdentifier, sizeof(strIdentifier)-1, "%d", (*metaRecordIn).dwMDIdentifier);
	strIdentifier[sizeof(strIdentifier)-1] = '\0';

	(*tmp)->identifier->data = strIdentifier;

	// Add the name to the structure.
	//
	// TODO - Can not figure out where/how to get the name that corresponds to the ID.
	(*tmp)->name->data = "NOT SUPPORTED";
	(*tmp)->msg.append("Currently unable to retrieve name information for metabase keys.");


	//	Get the user type
	switch ((*metaRecordIn).dwMDUserType)
	{
		case ASP_MD_UT_APP:
			(*tmp)->user_type = "ASP_MD_UT_APP";
			break;
		case IIS_MD_UT_FILE:
			(*tmp)->user_type = "IIS_MD_UT_FILE";
			break;
		case IIS_MD_UT_SERVER:
			(*tmp)->user_type = "IIS_MD_UT_SERVER";
			break;
		case IIS_MD_UT_WAM:
			(*tmp)->user_type = "IIS_MD_UT_WAM";
			break;
		default:
			(*tmp)->user_type = "";
	}

	//	Get the data
	switch ((*metaRecordIn).dwMDDataType)
	{
		case BINARY_METADATA:
		{
			(*tmp)->data_type = "BINARY_METADATA";

			// The buffer must be three bytes long, two bytes for each hex charater in the
			// binary data, and one byte for the terminating NULL character.

			char binaryBuf[3];

			// Loop through each hex character.  By zero-ing out the binaryBuf before
			// _snprintf(), we are making sure the buffer is NULL terminated.

			for (i=0; i<(*metaRecordIn).dwMDDataLen; i++)
			{
				ZeroMemory(binaryBuf, 3);
				_snprintf(binaryBuf, 2, "%02x", (*metaRecordIn).pbMDData[i]);
				(*tmp)->data.append(binaryBuf);
				(*tmp)->data.append(" ");
			}

			break;
		}

		case DWORD_METADATA:
		{
			(*tmp)->data_type = "DWORD_METADATA";

			// The dwordBuf is 12 bytes since the max DWORD (2,147,483,647) is 10 characters
			// long.  Also add a byte for a possible negative sign and a byte for the
			// terminating NULL character.

			char dwordBuf[12];

			ZeroMemory(dwordBuf, sizeof(dwordBuf));
			_snprintf(dwordBuf, sizeof(dwordBuf)-1, "%d", *(DWORD *)(*metaRecordIn).pbMDData);
			dwordBuf[sizeof(dwordBuf)-1] = '\0';

			(*tmp)->data.append(dwordBuf);
			
			break;
		}

		case EXPANDSZ_METADATA:
		{
			(*tmp)->data_type = "EXPANDSZ_METADATA";

			// The buffer must be three bytes long, two bytes for each wide charater in the
			// string data, and one byte for the terminating NULL character.

			char expandszBuf[3];

			// Loop through each wide character.  Make sure the buffer is NULL terminated.
			// The loop goes to (dwMDDataLen - 2) since we can skip the last character.  It
			// is a terminating NULL charater and will be automatically replaced during the
			// append method.

			// NOTE: If dwMDDataLen < 2 then (dwMDDataLen-2) becomes a very high DWORD.
			// This is because there are no negative numbers for DWORDS.  Make sure we guard
			// against this by checking value of dwMDDataLen.

			if ((*metaRecordIn).dwMDDataLen >= 2)
			{
				for (i=0; i<((*metaRecordIn).dwMDDataLen-2); i=i+2)
				{
					ZeroMemory(expandszBuf, sizeof(expandszBuf));

					// NOTE: When %C is used with printf functions, it specifies a wide
					// character.  When %C is used with wprintf functions, it specifies a
					// single-byte character.

					_snprintf(expandszBuf, sizeof(expandszBuf)-1, "%C", (*metaRecordIn).pbMDData[i]);
					expandszBuf[sizeof(expandszBuf)-1] = '\0';
					(*tmp)->data.append(expandszBuf);
				}
			}

			break;
		}

		case MULTISZ_METADATA:
		{
			(*tmp)->data_type = "MULTISZ_METADATA";

			// The buffer must be three bytes long, two bytes for each wide charater in the
			// string data, and one byte for the terminating NULL character.

			char multiszBuf[3];

			// Loop through each wide character.  Make sure the buffer is NULL terminated.
			// MULTISZ data is an array of null-terminated strings, terminated by two null
			// characters.  Therefore, the loop goes to (dwMDDataLen - 4) since we can skip
			// the last two characters.  This keeps an extra bar from beeing added to the
			// end of the valueString.  A terminating NULL charater and will be
			// automatically replaced during the append method.

			// NOTE: If dwMDDataLen < 4 then (dwMDDataLen-4) becomes a very high DWORD.
			// This is because there are no negative numbers for DWORDS.  Make sure we guard
			// against this by checking value of dwMDDataLen.
			if ((*metaRecordIn).dwMDDataLen >= 4)
			{
				for (i=0; i<((*metaRecordIn).dwMDDataLen-4); i=i+2)
				{
					ZeroMemory(multiszBuf, sizeof(multiszBuf));
					_snprintf(multiszBuf, sizeof(multiszBuf)-1, "%C", (*metaRecordIn).pbMDData[i]);
					multiszBuf[sizeof(multiszBuf)-1] = '\0';
					if (multiszBuf[0] == '\0') (*tmp)->data.append("|");
					else (*tmp)->data.append(multiszBuf);
				}
			}

			break;
		}

		case STRING_METADATA:
		{
			(*tmp)->data_type = "STRING_METADATA";

			char strBuf[3];

			// NOTE: If dwMDDataLen < 2 then (dwMDDataLen-2) becomes a very high DWORD.
			// This is because there are no negative numbers for DWORDS.  Make sure we guard
			// against this by checking value of dwMDDataLen.

			if ((*metaRecordIn).dwMDDataLen >= 2)
			{
				for (i=0; i<((*metaRecordIn).dwMDDataLen-2); i=i+2)
				{
					ZeroMemory(strBuf, sizeof(strBuf));
					_snprintf(strBuf, sizeof(strBuf)-1, "%C", (*metaRecordIn).pbMDData[i]);
					strBuf[sizeof(strBuf)-1] = '\0';
					(*tmp)->data.append(strBuf);
				}
			}

			break;
		}

		default:
		{
			(*tmp)->data_type = "";
			(*tmp)->data = "";
		}
	}
}

string MetabaseKeysProbe::GetData(METADATA_GETALL_RECORD *metaRecordIn, unsigned char* pbIdentBuffer)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Return the data of the METADATA_GETALL_RECORD as a string 
	//	The data for a METADATA_GETALL_RECORD is stored after the array of records 
	//	that is returned by the call to GetAllData(). Each record in the array has an
	//	offset to its data in the buffer and a length for its data.
	//
	// -----------------------------------------------------------------------
	string data = "";
	DWORD i;

	//	Create a buffer the size of the data input
	char *dataBuff = (char*)malloc((*metaRecordIn).dwMDDataLen);
	ZeroMemory(dataBuff, sizeof(dataBuff));

	memcpy(dataBuff, (pbIdentBuffer + (*metaRecordIn).dwMDDataOffset), (*metaRecordIn).dwMDDataLen);

	//	Get the data
	switch ((*metaRecordIn).dwMDDataType)
	{
		case BINARY_METADATA:
		{
			// The buffer must be three bytes long, two bytes for each hex charater in the
			// binary data, and one byte for the terminating NULL character.

			char binaryBuf[3];

			// Loop through each hex character.  By zero-ing out the binaryBuf before
			// _snprintf(), we are making sure the buffer is NULL terminated.

			for (i=0; i<(*metaRecordIn).dwMDDataLen; i++)
			{
				ZeroMemory(binaryBuf, 3);
				_snprintf(binaryBuf, 2, "%02x", dataBuff[i]);
				data.append(binaryBuf);
				data.append(" ");
			}

			break;
		}

		case DWORD_METADATA:
		{
			// The dwordBuf is 12 bytes since the max DWORD (2,147,483,647) is 10 characters
			// long.  Also add a byte for a possible negative sign and a byte for the
			// terminating NULL character.

			char dwordBuf[12];

			ZeroMemory(dwordBuf, sizeof(dwordBuf));
			_snprintf(dwordBuf, sizeof(dwordBuf)-1, "%d", *(DWORD *)dataBuff);
			dwordBuf[sizeof(dwordBuf)-1] = '\0';

			data.append(dwordBuf);
			
			break;
		}

		case EXPANDSZ_METADATA:
		{
			// The buffer must be three bytes long, two bytes for each wide charater in the
			// string data, and one byte for the terminating NULL character.

			char expandszBuf[3];

			// Loop through each wide character.  Make sure the buffer is NULL terminated.
			// The loop goes to (dwMDDataLen - 2) since we can skip the last character.  It
			// is a terminating NULL charater and will be automatically replaced during the
			// append method.

			// NOTE: If dwMDDataLen < 2 then (dwMDDataLen-2) becomes a very high DWORD.
			// This is because there are no negative numbers for DWORDS.  Make sure we guard
			// against this by checking value of dwMDDataLen.

			if ((*metaRecordIn).dwMDDataLen >= 2)
			{
				for (i=0; i<((*metaRecordIn).dwMDDataLen-2); i=i+2)
				{
					ZeroMemory(expandszBuf, sizeof(expandszBuf));

					// NOTE: When %C is used with printf functions, it specifies a wide
					// character.  When %C is used with wprintf functions, it specifies a
					// single-byte character.

					_snprintf(expandszBuf, sizeof(expandszBuf)-1, "%C", dataBuff[i]);
					expandszBuf[sizeof(expandszBuf)-1] = '\0';
					data.append(expandszBuf);
				}
			}

			break;
		}

		case MULTISZ_METADATA:
		{
			// The buffer must be three bytes long, two bytes for each wide charater in the
			// string data, and one byte for the terminating NULL character.

			char multiszBuf[3];

			// Loop through each wide character.  Make sure the buffer is NULL terminated.
			// MULTISZ data is an array of null-terminated strings, terminated by two null
			// characters.  Therefore, the loop goes to (dwMDDataLen - 4) since we can skip
			// the last two characters.  This keeps an extra bar from beeing added to the
			// end of the valueString.  A terminating NULL charater and will be
			// automatically replaced during the append method.

			// NOTE: If dwMDDataLen < 4 then (dwMDDataLen-4) becomes a very high DWORD.
			// This is because there are no negative numbers for DWORDS.  Make sure we guard
			// against this by checking value of dwMDDataLen.
			if ((*metaRecordIn).dwMDDataLen >= 4)
			{
				for (i=0; i<((*metaRecordIn).dwMDDataLen-4); i=i+2)
				{
					ZeroMemory(multiszBuf, sizeof(multiszBuf));
					_snprintf(multiszBuf, sizeof(multiszBuf)-1, "%C", dataBuff[i]);
					multiszBuf[sizeof(multiszBuf)-1] = '\0';
					if (multiszBuf[0] == '\0') 
						data.append("|");
					else 
						data.append(multiszBuf);
				}
			}

			break;
		}

		case STRING_METADATA:
		{
			char strBuf[3];

			// NOTE: If dwMDDataLen < 2 then (dwMDDataLen-2) becomes a very high DWORD.
			// This is because there are no negative numbers for DWORDS.  Make sure we guard
			// against this by checking value of dwMDDataLen.

			if ((*metaRecordIn).dwMDDataLen >= 2)
			{
				for (i=0; i<((*metaRecordIn).dwMDDataLen-2); i=i+2)
				{
					ZeroMemory(strBuf, sizeof(strBuf));
					_snprintf(strBuf, sizeof(strBuf)-1, "%C", dataBuff[i]);
					strBuf[sizeof(strBuf)-1] = '\0';
					data.append(strBuf);
				}
			}

			break;
		}

		default:
		{
			data = "";
		}
	}

	//	Release the memory allocated to the data buffer
	ZeroMemory(dataBuff, sizeof(dataBuff));
//	delete[] dataBuff;


	return data;
}
string MetabaseKeysProbe::GetDataType(METADATA_GETALL_RECORD *metaRecordIn)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Return the data_type of the METADATA_GETALL_RECORD as a string 
	//
	// -----------------------------------------------------------------------
	string data_type = "";

	switch ((*metaRecordIn).dwMDDataType)
	{
		case BINARY_METADATA:
		{
			data_type = "BINARY_METADATA";
			break;
		}
		case DWORD_METADATA:
		{
			data_type = "DWORD_METADATA";
			break;
		}
		case EXPANDSZ_METADATA:
		{
			data_type = "EXPANDSZ_METADATA";
			break;
		}
		case MULTISZ_METADATA:
		{
			data_type = "MULTISZ_METADATA";
			break;
		}
		case STRING_METADATA:
		{
			data_type = "STRING_METADATA";
			break;
		}
		default:
		{
			data_type = "";
		}
	}

	return data_type;
}

string MetabaseKeysProbe::GetIdentifier(METADATA_GETALL_RECORD *metaRecordIn)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Return the identifier of the METADATA_GETALL_RECORD as a string 
	//
	// -----------------------------------------------------------------------

	//	Convert the DWORD identifier to a string
	char strIdentifier[10];
	ZeroMemory(strIdentifier, sizeof(strIdentifier));
	_snprintf(strIdentifier, sizeof(strIdentifier)-1, "%d", (*metaRecordIn).dwMDIdentifier);
	strIdentifier[sizeof(strIdentifier)-1] = '\0';

	return strIdentifier;
}

string MetabaseKeysProbe::GetUserType(METADATA_GETALL_RECORD *metaRecordIn)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Return the user_type of the METADATA_GETALL_RECORD as a string 
	//
	// -----------------------------------------------------------------------

	string user_type = "";
	switch ((*metaRecordIn).dwMDUserType)
	{
		case ASP_MD_UT_APP:
			user_type = "ASP_MD_UT_APP";
			break;
		case IIS_MD_UT_FILE:
			user_type = "IIS_MD_UT_FILE";
			break;
		case IIS_MD_UT_SERVER:
			user_type = "IIS_MD_UT_SERVER";
			break;
		case IIS_MD_UT_WAM:
			user_type = "IIS_MD_UT_WAM";
			break;
		default:
			user_type = "";
	}

	return user_type;
}

bool MetabaseKeysProbe::ValidateKey(string strKeyIn, IMSAdminBase *pIABase)
{
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Return the true if the key exists. The function attemps to open the key. 
	//	If the key can be opened it exists..Must ensure that the key is closed 
	//	before returning.
	//
	// -----------------------------------------------------------------------

	HRESULT hRes;
	string workingKey	= strKeyIn;
	bool keyExists		= false;

	/////////////////////////////////////////////////////////////////
	//	Prepare the working key
	//	Reverse the slashes so they are all facing ->
	//	Ensure that there is a leading slash
	/////////////////////////////////////////////////////////////////
	workingKey = Common::SwitchChar(workingKey, "\\", "/");
	if(workingKey.find("/") != 0)
		workingKey.insert(0, "/");

	//	Convert the key from a string to a wchar_t*.
	//	Must add 1 to size to include the NULL at the end of the string
	LPWSTR pbKey;
	size_t size = mbstowcs(NULL, workingKey.c_str(), workingKey.length()) + 1;
	pbKey = new wchar_t[size];
	size = mbstowcs(pbKey, workingKey.c_str(), size) + 1;
	if (size == -1)
	{
		string errorMessage = "";
		errorMessage.append("There was an error converting the key '");
		errorMessage.append(workingKey);
		errorMessage.append("to UNICODE.");
		throw MetabaseKeysProbeException(errorMessage);
	}

	/////////////////////////////////////////////////////////////////
	//	Check that the key exists. Store it if it does
	/////////////////////////////////////////////////////////////////
	METADATA_HANDLE tmpMetaDataHandle;
	hRes = pIABase->OpenKey(METADATA_MASTER_ROOT_HANDLE,	//	handle to the metabase
							pbKey,							//	key to open
							METADATA_PERMISSION_READ,		//
							2000,							//
							&tmpMetaDataHandle);			//

	if(hRes == ERROR_SUCCESS)
	{
		//	Add the key to the return structure
		keyExists = true;

	}else if(hRes == ERROR_PATH_NOT_FOUND)
	{
		//	Do nothing the key doesn't exist		

	}else if(hRes == ERROR_PATH_BUSY)
	{
		//	Write message and return
		string errMsg = "(MetabaseKeysProbe) The path to the key: '" + strKeyIn + "' is busy and con not be opened.";
	
		//	Close the key
		hRes = pIABase->CloseKey(tmpMetaDataHandle);

		throw MetabaseKeysProbeException(errMsg);

	}else if(hRes == ERROR_INVALID_PARAMETER)
	{
		//	Write message and return
		string errMsg = "(MetabaseKeysProbe) An invalid parameter was specified while opening the key: '" + strKeyIn + "'.";

		//	Close the key
		hRes = pIABase->CloseKey(tmpMetaDataHandle);

		throw MetabaseKeysProbeException(errMsg);
		

	}else
	{
		char errorCodeBuffer[20];
		_ltoa(hRes, errorCodeBuffer, 20);

		//	Write message and return
		string errMsg = "(MetabaseKeysProbe) An unknown error occured while opening the key: '" + strKeyIn + "'.";
		errMsg.append("\nError code - ");
		errMsg.append(errorCodeBuffer);

		//	Close the key
		hRes = pIABase->CloseKey(tmpMetaDataHandle);

		throw MetabaseKeysProbeException(errMsg);
	}

	//	Close the key
	hRes = pIABase->CloseKey(tmpMetaDataHandle);

	return keyExists;
}


//****************************************************************************************//
//						MetabaseKeysProbeException Class								  //	
//****************************************************************************************//
MetabaseKeysProbeException::MetabaseKeysProbeException() : Exception()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Default constructor simply set the severity to ERROR_FATAL. This is 
	//	done with the explicit call to the Exception class default constructor.
	//
	// -----------------------------------------------------------------------

}

MetabaseKeysProbeException::MetabaseKeysProbeException(string errMsgIn) : Exception(errMsgIn)
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Set the error message and then set the severity to ERROR_FATAL. This is 
	//	done with the explicit call to the Exception class constructor that 
	//	takes a single string param.
	//
	// -----------------------------------------------------------------------

}

MetabaseKeysProbeException::~MetabaseKeysProbeException()
{
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	//
	// -----------------------------------------------------------------------

}