//=======================================================================
//
//  Copyright (c) 1998-2000 Microsoft Corporation.  All Rights Reserved.
//
//  File:   iuxml.cpp
//
//  Description:
//
//      Implementation for the CIUXml class
//
//=======================================================================

#include "iuengine.h"
#include "iuxml.h"
#include <iucommon.h>
#include <fileutil.h>
#include <StringUtil.h>
#include <shlwapi.h>
#include <wininet.h>
#include "schemakeys.h"
#include "schemamisc.h"
#include "v3applog.h"

#define QuitIfNull(p) {if (NULL == p) {hr = E_INVALIDARG; LOG_ErrorMsg(hr);	return hr;}}
#define QuitIfFail(x) {hr = x; if (FAILED(hr)) goto CleanUp;}
#define ReturnIfFail(x) {hr = x; if (FAILED(hr)) {LOG_ErrorMsg(hr); return hr;}}
#define SkipIfFail(x) {hr = x; if (FAILED(hr)) {hr = S_FALSE; continue;}}

const TCHAR IDENT_IUSCHEMA[]			= _T("IUSchema");
const TCHAR IDENT_IUSCHEMA_SYSTEMSPEC[]	= _T("SystemSpecSchema");
const TCHAR IDENT_IUSCHEMA_ITEMS[]		= _T("ResultSchema");

const WCHAR CORP_PLATFORM_DIR_NT4[]     = L"x86WinNT4";
const WCHAR CORP_PLATFORM_DIR_NT5[]     = L"x86win2k";
const WCHAR CORP_PLATFORM_DIR_W98[]     = L"x86Win98";
const WCHAR CORP_PLATFORM_DIR_W95[]     = L"x86Win95";
const WCHAR CORP_PLATFORM_DIR_WINME[]   = L"x86WinME";
const WCHAR CORP_PLATFORM_DIR_X86WHI[]  = L"x86WinXP";
const WCHAR CORP_PLATFROM_DIR_IA64WHI[] = L"ia64WinXP";


// Initial length of the node array "m_ppNodeArray"
const DWORD MAX_NODES = 16;

// Initial length of the node array "m_ppNodeListArray"
const DWORD MAX_NODELISTS = 16;

// Bitmap of existence of all possible system info classes
const DWORD	COMPUTERSYSTEM	= 0x00000001;
const DWORD	REGKEYS			= 0x00000010;
const DWORD	PLATFORM		= 0x00000100;
const DWORD	LOCALE			= 0x00001000;
const DWORD	DEVICES			= 0x00010000;


// The following are constants used for V3 history migration:
//
// Log line types
#define LOG_V2				"V2"             // line format for items migrated from V2
#define LOG_V3CAT			"V3CAT"			 // V3 Beta format (version 1)
#define LOG_V3_2			"V3_2"			 // V3 log line format (version 2)
#define LOG_PSS				"PSS"			 // Entry for PSS

// LOG_V2 format
//    V2|DATE|TIME|LOGSTRING|
//
// LOG_V3CAT format
//    V3CAT|PUID|OPERATION|TITLE|VERSION|DATESTRING|TIMESTRING|RECTYPE|RESULT|ERRORCODE|ERRORSTRING|
//
// LOG_V3_2 format
//    V3_2|PUID|OPERATION|TITLE|VERSION|TIMESTAMP|RECTYPE|RESULT|ERRORCODE|ERRORSTRING|
//
// LOG_PSS format
//    PSS|PUID|OPERATION|TITLE|VERSION|TIMESTAMP|RECTYPE|RESULT|ERRORCODE|ERRORSTRING|
//

// operation type
#define LOG_INSTALL         "INSTALL"

// result
#define LOG_SUCCESS         "SUCCESS"
#define LOG_FAIL            "FAIL"
#define LOG_STARTED			"STARTED"      // started but reboot was required: exclusive items only

const WCHAR C_V3_CLIENTINFO[] = L"WU_V3";


/////////////////////////////////////////////////////////////////////////////
// CIUXml

/////////////////////////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////////////////////////
CIUXml::CIUXml()
 : m_dwSizeNodeArray(MAX_NODES),
   m_dwSizeNodeListArray(MAX_NODELISTS),
   m_ppNodeArray(NULL),
   m_ppNodeListArray(NULL)
{
	m_hHeap = GetProcessHeap();
}


/////////////////////////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////////////////////////
CIUXml::~CIUXml()
{
	DWORD	dwIndex;

	if (NULL != m_ppNodeArray)
	{
		for (dwIndex = 0; dwIndex < m_dwSizeNodeArray; dwIndex++)
		{
			SafeReleaseNULL(m_ppNodeArray[dwIndex]);
		}

		HeapFree(m_hHeap, 0, m_ppNodeArray);
		m_ppNodeArray = NULL;
	}

	if (NULL != m_ppNodeListArray)
	{
		for (dwIndex = 0; dwIndex < m_dwSizeNodeListArray; dwIndex++)
		{
			SafeReleaseNULL(m_ppNodeListArray[dwIndex]);
		}

		HeapFree(m_hHeap, 0, m_ppNodeListArray);
		m_ppNodeListArray = NULL;
	}
}


/////////////////////////////////////////////////////////////////////////////
// SafeCloseHandleNode()
//
// User can explicitly can this function to release a node for reuse when 
// writing a xml doc.
/////////////////////////////////////////////////////////////////////////////
void CIUXml::SafeCloseHandleNode(HANDLE_NODE& hNode)
{
	if (HANDLE_NODE_INVALID != hNode)
	{
		SafeReleaseNULL(m_ppNodeArray[hNode]);
		hNode = HANDLE_NODE_INVALID;
	}
}


/////////////////////////////////////////////////////////////////////////////
// SafeFindCloseHandle()
//
// User can explicitly can this function to release a nodelist for reuse when 
// reading a xml doc.
/////////////////////////////////////////////////////////////////////////////
void CIUXml::SafeFindCloseHandle(HANDLE_NODELIST& hNodeList)
{
	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		SafeReleaseNULL(m_ppNodeListArray[hNodeList]);
		hNodeList = HANDLE_NODELIST_INVALID;
	}
}


/////////////////////////////////////////////////////////////////////////////
// InitNodeArray()
//
// Allocate or re-allocate memory for the node array "m_ppNodeArray"
/////////////////////////////////////////////////////////////////////////////
HRESULT CIUXml::InitNodeArray(BOOL fRealloc /*= FALSE*/)
{
	if (fRealloc)	// re-allocation
	{
		IXMLDOMNode** ppNodeArrayTemp = (IXMLDOMNode**)HeapReAlloc(m_hHeap,
																HEAP_ZERO_MEMORY,
																m_ppNodeArray,
																m_dwSizeNodeArray * sizeof(IXMLDOMNode*));
		if (NULL == ppNodeArrayTemp)
		{
	        return E_OUTOFMEMORY;
		}
		else
		{
			m_ppNodeArray = ppNodeArrayTemp;
		}
	}
	else			// initial allocation
	{
		m_ppNodeArray = (IXMLDOMNode**)HeapAlloc(m_hHeap,
												 HEAP_ZERO_MEMORY,
												 m_dwSizeNodeArray * sizeof(IXMLDOMNode*));
		if (NULL == m_ppNodeArray)
		{
	        return E_OUTOFMEMORY;
		}
	}
	return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// InitNodeListArray()
//
// Allocate or re-allocate memory for the nodelist array "m_ppNodeListArray"
/////////////////////////////////////////////////////////////////////////////
HRESULT CIUXml::InitNodeListArray(BOOL fRealloc /*= FALSE*/)
{
	if (fRealloc)	// re-allocation
	{
		IXMLDOMNodeList** ppNodeListArrayTemp = (IXMLDOMNodeList**)HeapReAlloc(m_hHeap,
																	HEAP_ZERO_MEMORY,
																	m_ppNodeListArray,
																	m_dwSizeNodeListArray * sizeof(IXMLDOMNodeList*));
		if (NULL == ppNodeListArrayTemp)
		{
	        return E_OUTOFMEMORY;
		}
		else
		{
			m_ppNodeListArray = ppNodeListArrayTemp;
		}
	}
	else			// initial allocation
	{
		m_ppNodeListArray = (IXMLDOMNodeList**)HeapAlloc(m_hHeap,
													HEAP_ZERO_MEMORY,
													m_dwSizeNodeListArray * sizeof(IXMLDOMNodeList*));
		if (NULL == m_ppNodeListArray)
		{
	        return E_OUTOFMEMORY;
		}
	}
	return S_OK;
}


/////////////////////////////////////////////////////////////////////////////
// GetNodeHandle()
//
// Look for the first un-used node from the "m_ppNodeArray" array,
// including the memory allocation, if needed.
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODE CIUXml::GetNodeHandle()
{
	HRESULT	hr;
	DWORD	dwIndex;

	//
	// allocate memory for "m_ppNodeArray" array if this is the first time using it
	//
	if (NULL == m_ppNodeArray)
	{
		QuitIfFail(InitNodeArray());
		return 0;	// return the first element of the array
	}
		
	//
	// find the next node to use, or, if any node was used but closed, reuse it
	//
	for (dwIndex = 0; dwIndex < m_dwSizeNodeArray; dwIndex++)
	{
		if (NULL == m_ppNodeArray[dwIndex])
		{
			return dwIndex;
		}
	}

	//
	// all pre-allocated nodes are used up, so re-allocate longer array
	//
	m_dwSizeNodeArray += m_dwSizeNodeArray;	// double the size
	QuitIfFail(InitNodeArray(TRUE));		// re-allocation
	return dwIndex;

CleanUp:
    return HANDLE_NODE_INVALID;
}
	

/////////////////////////////////////////////////////////////////////////////
// GetNodeListHandle()
//
// Look for the first un-used nodelist from the "m_ppNodeListArray" array,
// including the memory allocation, if needed.
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CIUXml::GetNodeListHandle()
{
	HRESULT	hr;
	DWORD	dwIndex;

	//
	// allocate memory for "m_ppNodeListArray" array if this is the first time using it
	//
	if (NULL == m_ppNodeListArray)
	{
		QuitIfFail(InitNodeListArray());
		return 0;	// return the first element of the array
	}
		
	//
	// find the next nodelist to use, or, if any nodelist was used but closed, reuse it
	//
	for (dwIndex = 0; dwIndex < m_dwSizeNodeListArray; dwIndex++)
	{
		if (NULL == m_ppNodeListArray[dwIndex])
		{
			return dwIndex;
		}
	}

	//
	// all pre-allocated nodelists are used up, so re-allocate longer array
	//
	m_dwSizeNodeListArray += m_dwSizeNodeListArray;	// double the size
	QuitIfFail(InitNodeListArray(TRUE));			// re-allocation
	return dwIndex;

CleanUp:
	return HANDLE_NODELIST_INVALID;
}


/////////////////////////////////////////////////////////////////////////////
// GetDOMNodebyHandle()
//
// Retrieve the xml node with the given index of m_ppNodeArray
/////////////////////////////////////////////////////////////////////////////
IXMLDOMNode* CIUXml::GetDOMNodebyHandle(HANDLE_NODE hNode)
{
	if (NULL != m_ppNodeArray && HANDLE_NODE_INVALID != hNode)
	{
		return m_ppNodeArray[hNode];
	}
	return NULL;
}


/////////////////////////////////////////////////////////////////////////////
// FindFirstDOMNode()
//
// Retrieve the first xml node with the given tag name under the given parent node
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CIUXml::FindFirstDOMNode(IXMLDOMNode* pParentNode, BSTR bstrName, IXMLDOMNode** ppNode)
{
	USES_IU_CONVERSION;

	HRESULT		hr	= S_OK;
	if (NULL == pParentNode)
	{
		hr = E_INVALIDARG;
		return HANDLE_NODELIST_INVALID;
	}

	DWORD		dwIndex;
    dwIndex = GetNodeListHandle();
	if (HANDLE_NODELIST_INVALID != dwIndex)
	{
		LONG	lLength;
		QuitIfFail(pParentNode->selectNodes(bstrName, &m_ppNodeListArray[dwIndex]));
		if (NULL == m_ppNodeListArray[dwIndex])
		{
			goto CleanUp;
		}
		QuitIfFail(m_ppNodeListArray[dwIndex]->get_length(&lLength));
		if (lLength <= 0)
		{
			goto CleanUp;
		}
		QuitIfFail(m_ppNodeListArray[dwIndex]->nextNode(ppNode));
		if (NULL == *ppNode)
		{
			goto CleanUp;
		}
		return dwIndex;
	}

CleanUp:
	*ppNode = NULL;
    return HANDLE_NODELIST_INVALID;
}


/////////////////////////////////////////////////////////////////////////////
// FindFirstDOMNode()
//
// Retrieve the handle of first xml node with the given tag name under the given parent node
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CIUXml::FindFirstDOMNode(IXMLDOMNode* pParentNode, BSTR bstrName, HANDLE_NODE* phNode)
{
	USES_IU_CONVERSION;

	HRESULT		hr	= S_OK;
	if (NULL == pParentNode)
	{
		hr = E_INVALIDARG;
		return HANDLE_NODELIST_INVALID;
	}

	DWORD		dwIndex1, dwIndex2;
    dwIndex1 = GetNodeListHandle();
	if (HANDLE_NODELIST_INVALID != dwIndex1)
	{
		LONG	lLength;
		QuitIfFail(pParentNode->selectNodes(bstrName, &m_ppNodeListArray[dwIndex1]));
		if (NULL == m_ppNodeListArray[dwIndex1])
		{
			goto CleanUp;
		}
		QuitIfFail(m_ppNodeListArray[dwIndex1]->get_length(&lLength));
		if (lLength <= 0)
		{
			goto CleanUp;
		}
		dwIndex2 = GetNodeHandle();
		if (HANDLE_NODE_INVALID != dwIndex2)
		{
			QuitIfFail(m_ppNodeListArray[dwIndex1]->nextNode(&m_ppNodeArray[dwIndex2]));
			if (NULL == m_ppNodeArray[dwIndex2])
			{
				goto CleanUp;
			}
			*phNode = dwIndex2;
			return dwIndex1;
		}
	}

CleanUp:
	*phNode = HANDLE_NODE_INVALID;
    return HANDLE_NODELIST_INVALID;
}


/////////////////////////////////////////////////////////////////////////////
// FindFirstDOMNode()
//
// Retrieve the first xml node with the given tag name in the given xml doc
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CIUXml::FindFirstDOMNode(IXMLDOMDocument* pDoc, BSTR bstrName, IXMLDOMNode** ppNode)
{
	USES_IU_CONVERSION;

	HRESULT		hr	= S_OK;
	if (NULL == pDoc)
	{
		hr = E_INVALIDARG;
		return HANDLE_NODELIST_INVALID;
	}

	DWORD		dwIndex;
	IXMLDOMNode	*pParentNode = NULL;
    dwIndex = GetNodeListHandle();
	if (HANDLE_NODELIST_INVALID != dwIndex)
	{
		LONG		lLength;
		QuitIfFail(pDoc->QueryInterface(IID_IXMLDOMNode, (void**)&pParentNode));
		QuitIfFail(pParentNode->selectNodes(bstrName, &m_ppNodeListArray[dwIndex]));
		if (NULL == m_ppNodeListArray[dwIndex])
		{
			goto CleanUp;
		}
		QuitIfFail(m_ppNodeListArray[dwIndex]->get_length(&lLength));
		if (lLength <= 0)
		{
			goto CleanUp;
		}
		QuitIfFail(m_ppNodeListArray[dwIndex]->nextNode(ppNode));
		if (NULL == *ppNode)
		{
			goto CleanUp;
		}
		SafeReleaseNULL(pParentNode);
		return dwIndex;
	}

CleanUp:
	*ppNode = NULL;
	SafeReleaseNULL(pParentNode);
    return HANDLE_NODELIST_INVALID;
}


/////////////////////////////////////////////////////////////////////////////
// FindFirstDOMNode()
//
// Retrieve the handle of first xml node with the given tag name in the given xml doc
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CIUXml::FindFirstDOMNode(IXMLDOMDocument* pDoc, BSTR bstrName, HANDLE_NODE* phNode)
{
	USES_IU_CONVERSION;

	HRESULT		hr	= S_OK;
	if (NULL == pDoc)
	{
		hr = E_INVALIDARG;
		return HANDLE_NODELIST_INVALID;
	}

	DWORD		dwIndex1, dwIndex2;
	IXMLDOMNode	*pParentNode = NULL;
    dwIndex1 = GetNodeListHandle();
	if (HANDLE_NODELIST_INVALID != dwIndex1)
	{
		LONG	lLength;
		QuitIfFail(pDoc->QueryInterface(IID_IXMLDOMNode, (void**)&pParentNode));
		QuitIfFail(pParentNode->selectNodes(bstrName, &m_ppNodeListArray[dwIndex1]));
		if (NULL == m_ppNodeListArray[dwIndex1])
		{
			goto CleanUp;
		}
		QuitIfFail(m_ppNodeListArray[dwIndex1]->get_length(&lLength));
		if (lLength <= 0)
		{
			goto CleanUp;
		}
		dwIndex2 = GetNodeHandle();
		if (HANDLE_NODE_INVALID != dwIndex2)
		{
			QuitIfFail(m_ppNodeListArray[dwIndex1]->nextNode(&m_ppNodeArray[dwIndex2]));
			if (NULL == m_ppNodeArray[dwIndex2])
			{
				goto CleanUp;
			}
			*phNode = dwIndex2;
			SafeReleaseNULL(pParentNode);
			return dwIndex1;
		}
	}

CleanUp:
	*phNode = HANDLE_NODE_INVALID;
	SafeReleaseNULL(pParentNode);
    return HANDLE_NODELIST_INVALID;
}


/////////////////////////////////////////////////////////////////////////////
// FindNextDOMNode()
//
// Retrieve the next xml node with the given tag name under the given parent node
/////////////////////////////////////////////////////////////////////////////
HRESULT CIUXml::FindNextDOMNode(HANDLE_NODELIST hNodeList, IXMLDOMNode** ppNode)
{
	HRESULT		hr = E_FAIL;

	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		hr = m_ppNodeListArray[hNodeList]->nextNode(ppNode);
	}

    if (FAILED(hr) || NULL == *ppNode)
	{
		*ppNode = NULL;
		return E_FAIL;
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// FindNextDOMNode()
//
// Retrieve the handle of next xml node with the given tag name under the given parent node
/////////////////////////////////////////////////////////////////////////////
HRESULT CIUXml::FindNextDOMNode(HANDLE_NODELIST hNodeList, HANDLE_NODE* phNode)
{
	HRESULT		hr = E_FAIL;
	DWORD		dwIndex = HANDLE_NODE_INVALID;

	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		dwIndex = GetNodeHandle();
		if (HANDLE_NODE_INVALID != dwIndex)
		{
			hr = m_ppNodeListArray[hNodeList]->nextNode(&m_ppNodeArray[dwIndex]);
		}
	}

    if (FAILED(hr) || NULL == m_ppNodeArray[dwIndex])
	{
		*phNode = HANDLE_NODE_INVALID;
		return E_FAIL;
	}
	*phNode = dwIndex;
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// CreateDOMNodeWithHandle()
//
// Create an xml node of the given type
// Return: index of the node array "m_ppNodeArray"; or -1 if failure.
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODE CIUXml::CreateDOMNodeWithHandle(IXMLDOMDocument* pDoc, SHORT nType, BSTR bstrName, BSTR bstrNamespaceURI /*= NULL*/)
{
	USES_IU_CONVERSION;

	HRESULT		hr	= S_OK;
	if (NULL == pDoc)
	{
		hr = E_INVALIDARG;
		return HANDLE_NODE_INVALID;
	}

	DWORD		dwIndex;
    VARIANT		vType;
	VariantInit(&vType);

    vType.vt = VT_I2;
    vType.iVal = nType;

    dwIndex = GetNodeHandle();
	if (HANDLE_NODE_INVALID != dwIndex)
	{
		QuitIfFail(pDoc->createNode(vType, bstrName, bstrNamespaceURI, &m_ppNodeArray[dwIndex]));
		return dwIndex;
	}

CleanUp:
    return HANDLE_NODE_INVALID;
}


/////////////////////////////////////////////////////////////////////////////
// CXmlSystemSpec

/////////////////////////////////////////////////////////////////////////////
// Constructor
//
// Create IXMLDOMDocument* for SystemSpec
/////////////////////////////////////////////////////////////////////////////
CXmlSystemSpec::CXmlSystemSpec()
 : m_pDocSystemSpec(NULL),
   m_pNodeSystemInfo(NULL),
   m_pNodeComputerSystem(NULL),
   m_pNodeRegKeysSW(NULL),
   m_pNodePlatform(NULL),
   m_pNodeDevices(NULL)
{
    LOG_Block("CXmlSystemSpec()");

 	HRESULT hr = CoCreateInstance(CLSID_DOMDocument,
								  NULL,
								  CLSCTX_INPROC_SERVER,
								  IID_IXMLDOMDocument,
								  (void **) &m_pDocSystemSpec);
    if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	else
	{
		IXMLDOMNode*	pNodeXML = NULL;
		BSTR bstrNameSpaceSchema = NULL;

		//
		// create the <?xml version="1.0"?> node
		//
		pNodeXML = CreateDOMNode(m_pDocSystemSpec, NODE_PROCESSING_INSTRUCTION, KEY_XML);
		if (NULL == pNodeXML) goto CleanUp;

		CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocSystemSpec, pNodeXML));

		//
		// process the iuident.txt to find the SystemSpec schema path
		//
		TCHAR szIUDir[MAX_PATH];
		TCHAR szIdentFile[MAX_PATH];
		LPTSTR pszSystemSpecSchema = NULL;
		LPTSTR pszNameSpaceSchema = NULL;

		pszSystemSpecSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
		if (NULL == pszSystemSpecSchema)
		{
			LOG_ErrorMsg(E_OUTOFMEMORY);
			goto CleanUp;
		}
		pszNameSpaceSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
		if (NULL == pszNameSpaceSchema)
		{
			LOG_ErrorMsg(E_OUTOFMEMORY);
			goto CleanUp;
		}
	
		GetIndustryUpdateDirectory(szIUDir);
		hr = PathCchCombine(szIdentFile, ARRAYSIZE(szIdentFile), szIUDir,IDENTTXT);
		if (FAILED(hr))
		{
			LOG_ErrorMsg(hr);
			goto CleanUp;
		}

		if (GetPrivateProfileString(IDENT_IUSCHEMA,
								IDENT_IUSCHEMA_SYSTEMSPEC,
								_T(""),
								pszSystemSpecSchema,
								INTERNET_MAX_URL_LENGTH,
								szIdentFile) == INTERNET_MAX_URL_LENGTH - 1)
		{
			LOG_Error(_T("SystemSpec schema buffer overflow"));
			goto CleanUp;
		}

		if ('\0' == pszSystemSpecSchema[0])
		{
			// no SystemSpec schema path specified in iuident.txt
			LOG_Error(_T("No schema path specified in iuident.txt for SystemSpec"));
			goto CleanUp;
		}

		hr = StringCchPrintfEx(pszNameSpaceSchema, INTERNET_MAX_URL_LENGTH, NULL, NULL, MISTSAFE_STRING_FLAGS,
		                       _T("x-schema:%s"), pszSystemSpecSchema);
		if (FAILED(hr))
		{
			LOG_ErrorMsg(hr);
			goto CleanUp;
		}
		
		bstrNameSpaceSchema = T2BSTR(pszNameSpaceSchema);

		//
		// create the <systemInfo> node with the path of the schema
		//
		m_pNodeSystemInfo = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_SYSTEMINFO, bstrNameSpaceSchema);
		SafeSysFreeString(bstrNameSpaceSchema);
		if (NULL == m_pNodeSystemInfo) goto CleanUp;
		
		CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocSystemSpec, m_pNodeSystemInfo));

CleanUp:
		SafeReleaseNULL(pNodeXML);
		SafeHeapFree(pszSystemSpecSchema);
		SafeHeapFree(pszNameSpaceSchema);
	}
}


/////////////////////////////////////////////////////////////////////////////
// Destructor
//
// Release IXMLDOMDocument* for SystemSpec
/////////////////////////////////////////////////////////////////////////////
CXmlSystemSpec::~CXmlSystemSpec()
{
	SafeReleaseNULL(m_pNodeDevices);
	SafeReleaseNULL(m_pNodePlatform);
	SafeReleaseNULL(m_pNodeRegKeysSW);
	SafeReleaseNULL(m_pNodeComputerSystem);
	SafeReleaseNULL(m_pNodeSystemInfo);

	SafeReleaseNULL(m_pDocSystemSpec);
}


/////////////////////////////////////////////////////////////////////////////
// AddComputerSystem()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddComputerSystem(BSTR bstrManufacturer,
										  BSTR bstrModel,
										  BSTR bstrSupportSite /*= NULL*/,
										  INT  iAdmin /*= -1*/,
										  INT  iWUDisabled /*= -1*/,
										  INT  iAUEnabled /*= -1*/,
										  BSTR bstrPID)
{
	LOG_Block("AddComputerSystem()");

	HRESULT		hr = E_FAIL;

	m_pNodeComputerSystem = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_COMPUTERSYSTEM);
	if (NULL == m_pNodeComputerSystem) return hr;

	//
	// Manufacturer and Model are now optional (RAID#337879	IU: can't get latest IU controls to work with IU site)
	//
	if (NULL != bstrManufacturer)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_MANUFACTURER, bstrManufacturer));
	}
	if (NULL != bstrModel)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_MODEL, bstrModel));
	}
	if (NULL != bstrSupportSite)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_SUPPORTSITE, bstrSupportSite));
	}

	if (NULL != bstrPID)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_PID, bstrPID));
	}

	if (-1 != iAdmin)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_ADMINISTRATOR, iAdmin));
	}
	if (-1 != iWUDisabled)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_WU_DISABLED, iWUDisabled));
	}
	if (-1 != iAUEnabled)
	{
		ReturnIfFail(SetAttribute(m_pNodeComputerSystem, KEY_AU_ENABLED, iAUEnabled));
	}
	ReturnIfFail(InsertNode(m_pNodeSystemInfo, m_pNodeComputerSystem));
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddDriveSpace()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddDriveSpace(BSTR bstrDrive, INT iKBytes)
{
	LOG_Block("AddDriveSpace()");

	HRESULT			hr = E_FAIL;
	IXMLDOMNode*	pNodeDriveSpace = NULL;

	pNodeDriveSpace = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_DRIVESPACE);
	if (NULL == pNodeDriveSpace) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDriveSpace, KEY_DRIVE, bstrDrive));
	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDriveSpace, KEY_KBYTES, iKBytes));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeComputerSystem, pNodeDriveSpace));

CleanUp:
	SafeReleaseNULL(pNodeDriveSpace);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddReg()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddReg(BSTR bstrProvider)
{
	LOG_Block("AddReg()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeRegKeys = NULL;
	IXMLDOMNode*	pNodeRegKeysHKLM = NULL;
	IXMLDOMNode*	pNodeRegValue = NULL;
	IXMLDOMNode*	pNodeRegValueText = NULL;

	if (NULL == m_pNodeRegKeysSW)
	{
		//
		// create the <regKeys> node
		//
		pNodeRegKeys = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_REGKEYS);
		if (NULL == pNodeRegKeys) goto CleanUp;

		CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeSystemInfo, pNodeRegKeys));

		//
		// create the <HKEY_LOCAL_MACHINE> node
		//
		pNodeRegKeysHKLM = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_REG_HKLM);
		if (NULL == pNodeRegKeysHKLM) goto CleanUp;

		CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeRegKeys, pNodeRegKeysHKLM));

		//
		// create the <SOFTWARE> node
		//
		m_pNodeRegKeysSW = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_REG_SW);
		if (NULL == m_pNodeRegKeysSW) goto CleanUp;

		CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeRegKeysHKLM, m_pNodeRegKeysSW));
	}

	//
	// add the <value> node
	//
	pNodeRegValue = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_VALUE);
	if (NULL == pNodeRegValue) goto CleanUp;

	pNodeRegValueText = CreateDOMNode(m_pDocSystemSpec, NODE_TEXT, NULL);
	if (NULL == pNodeRegValueText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeRegValueText, bstrProvider));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeRegValue, pNodeRegValueText));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeRegKeysSW, pNodeRegValue));

CleanUp:
    if (FAILED(hr))
        SafeReleaseNULL(m_pNodeRegKeysSW);
	SafeReleaseNULL(pNodeRegKeys);
	SafeReleaseNULL(pNodeRegKeysHKLM);
	SafeReleaseNULL(pNodeRegValue);
	SafeReleaseNULL(pNodeRegValueText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddPlatform()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddPlatform(BSTR bstrName)
{
	LOG_Block("AddPlatform()");

	HRESULT		hr = E_FAIL;

	m_pNodePlatform = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_PLATFORM);
	if (NULL == m_pNodePlatform) return hr;

	ReturnIfFail(SetAttribute(m_pNodePlatform, KEY_NAME, bstrName));
	ReturnIfFail(InsertNode(m_pNodeSystemInfo, m_pNodePlatform));
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddProcessor()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddProcessor(BSTR bstrProcessor)
{
	LOG_Block("AddProcessor()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeProcessor = NULL;
	IXMLDOMNode*	pNodeProcessorText = NULL;

	pNodeProcessor = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_PROCESSORARCHITECTURE);
	if (NULL == pNodeProcessor) goto CleanUp;

	pNodeProcessorText = CreateDOMNode(m_pDocSystemSpec, NODE_TEXT, NULL);
	if (NULL == pNodeProcessorText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeProcessorText, bstrProcessor));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeProcessor, pNodeProcessorText));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodePlatform, pNodeProcessor));

CleanUp:
	SafeReleaseNULL(pNodeProcessor);
	SafeReleaseNULL(pNodeProcessorText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddVersion()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddVersion(INT  iMajor /*= -1*/,
								   INT  iMinor /*= -1*/,
								   INT  iBuild /*= -1*/,
								   INT  iSPMajor /*= -1*/,
								   INT  iSPMinor /*= -1*/,
								   BSTR bstrTimeStamp /*= NULL*/)
{
	LOG_Block("AddVersion()");

	HRESULT			hr = E_FAIL;
	IXMLDOMNode*	pNodeVersion = NULL;

	//
	// create the <version> node
	//
	pNodeVersion = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_VERSION);
	if (NULL == pNodeVersion) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodePlatform, pNodeVersion));

	//
	// set the "major" attribute
	//
	if (-1 != iMajor)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeVersion, KEY_MAJOR, iMajor));
	}

	//
	// set the "minor" attribute
	//
	if (-1 != iMinor)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeVersion, KEY_MINOR, iMinor));
	}

	//
	// set the "build" attribute
	//
	if (-1 != iBuild)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeVersion, KEY_BUILD, iBuild));
	}

	//
	// set the "servicePackMajor" attribute
	//
	if (-1 != iSPMajor)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeVersion, KEY_SERVICEPACKMAJOR, iSPMajor));
	}

	//
	// set the "servicePackMinor" attribute
	//
	if (-1 != iSPMinor)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeVersion, KEY_SERVICEPACKMINOR, iSPMinor));
	}

	//
	// set the "timestamp" attribute
	//
	if (NULL != bstrTimeStamp)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeVersion, KEY_TIMESTAMP, bstrTimeStamp));
	}

CleanUp:
	SafeReleaseNULL(pNodeVersion);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddSuite()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddSuite(BSTR bstrSuite)
{
	LOG_Block("AddSuite()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeSuite = NULL;
	IXMLDOMNode*	pNodeSuiteText = NULL;

	pNodeSuite = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_SUITE);
	if (NULL == pNodeSuite) goto CleanUp;

	pNodeSuiteText = CreateDOMNode(m_pDocSystemSpec, NODE_TEXT, NULL);
	if (NULL == pNodeSuiteText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeSuiteText, bstrSuite));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeSuite, pNodeSuiteText));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodePlatform, pNodeSuite));

CleanUp:
	SafeReleaseNULL(pNodeSuite);
	SafeReleaseNULL(pNodeSuiteText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddProductType()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddProductType(BSTR bstrProductType)
{
	LOG_Block("AddProductType()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeProductType = NULL;
	IXMLDOMNode*	pNodeProductTypeText = NULL;

	pNodeProductType = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_PRODUCTTYPE);
	if (NULL == pNodeProductType) goto CleanUp;

	pNodeProductTypeText = CreateDOMNode(m_pDocSystemSpec, NODE_TEXT, NULL);
	if (NULL == pNodeProductTypeText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeProductTypeText, bstrProductType));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeProductType, pNodeProductTypeText));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodePlatform, pNodeProductType));

CleanUp:
	SafeReleaseNULL(pNodeProductType);
	SafeReleaseNULL(pNodeProductTypeText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddLocale()
//
// We need to pass back a handle to differentiate different <locale> node
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddLocale(BSTR bstrContext, HANDLE_NODE* phNodeLocale)
{
	LOG_Block("AddLocale()");

	HRESULT		hr = E_FAIL;

	*phNodeLocale = CreateDOMNodeWithHandle(m_pDocSystemSpec, NODE_ELEMENT, KEY_LOCALE);
	if (HANDLE_NODE_INVALID == *phNodeLocale) return hr;

	hr = SetAttribute(m_ppNodeArray[*phNodeLocale], KEY_CONTEXT, bstrContext);
	if (FAILED(hr))
	{
	    SafeCloseHandleNode(*phNodeLocale);
	    LOG_ErrorMsg(hr);
	    return hr;
	}
	ReturnIfFail(InsertNode(m_pNodeSystemInfo, m_ppNodeArray[*phNodeLocale]));
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddLanguage()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddLanguage(HANDLE_NODE hNodeLocale, BSTR bstrLocale)
{
	LOG_Block("AddLanguage()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeLanguage = NULL;
	IXMLDOMNode*	pNodeLanguageText = NULL;

	pNodeLanguage = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_LANGUAGE);
	if (NULL == pNodeLanguage) goto CleanUp;

	pNodeLanguageText = CreateDOMNode(m_pDocSystemSpec, NODE_TEXT, NULL);
	if (NULL == pNodeLanguageText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeLanguageText, bstrLocale));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeLanguage, pNodeLanguageText));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeLocale], pNodeLanguage));

CleanUp:
	SafeReleaseNULL(pNodeLanguage);
	SafeReleaseNULL(pNodeLanguageText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddDevice()
//
// We need to pass back a handle to differentiate different <device> node
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddDevice(BSTR bstrDeviceInstance, /*= NULL - optional attribute*/
								  INT  iIsPrinter /*= -1*/, 
								  BSTR bstrProvider /*= NULL*/,
								  BSTR bstrMfgName /*= NULL*/,
								  BSTR bstrDriverName /*= NULL*/,
								  HANDLE_NODE* phNodeDevice)
{
	LOG_Block("AddDevice()");

	HRESULT			hr = E_FAIL;
	IXMLDOMNode*	pNodePrinterInfo = NULL;

	if (NULL == m_pNodeDevices)
	{
		//
		// create the <devices> node
		//
		m_pNodeDevices = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_DEVICES);
		if (NULL == m_pNodeDevices) goto CleanUp;

		CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeSystemInfo, m_pNodeDevices));
	}

	//
	// add the <device> node
	//
	*phNodeDevice = CreateDOMNodeWithHandle(m_pDocSystemSpec, NODE_ELEMENT, KEY_DEVICE);
	if (HANDLE_NODE_INVALID == *phNodeDevice) goto CleanUp;

	if (NULL != bstrDeviceInstance && 0 < lstrlenW(bstrDeviceInstance))
	{
		//
		// set optional deviceInstance attribute of <device>
		//
		CleanUpIfFailedAndSetHrMsg(SetAttribute(m_ppNodeArray[*phNodeDevice], KEY_DEVICEINSTANCE, bstrDeviceInstance));
	}

	if (-1 != iIsPrinter)
	{
		//
		// set isPrinter attribute of <device>
		//
		CleanUpIfFailedAndSetHrMsg(SetAttribute(m_ppNodeArray[*phNodeDevice], KEY_ISPRINTER, iIsPrinter));

		//
		// Add a <printerInfo> node within <device>
		//
		pNodePrinterInfo = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, KEY_PRINTERINFO);
		if (NULL != pNodePrinterInfo)
		{
			if (NULL != bstrProvider)
			{
				CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodePrinterInfo, KEY_DRIVERPROVIDER, bstrProvider));
			}
			if (NULL != bstrMfgName)
			{
				CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodePrinterInfo, KEY_MFGNAME, bstrMfgName));
			}
			if (NULL != bstrDriverName)
			{
				CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodePrinterInfo, KEY_DRIVERNAME, bstrDriverName));
			}
			CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[*phNodeDevice], pNodePrinterInfo));
		}
	}

	//
	// insert to <devices>
	//
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeDevices, m_ppNodeArray[*phNodeDevice]));

CleanUp:
	SafeReleaseNULL(pNodePrinterInfo);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddHWID()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::AddHWID(HANDLE_NODE hNodeDevice,
								BOOL fIsCompatible,
								UINT iRank,
								BSTR bstrHWID,
								BSTR bstrDriverVer /*= NULL*/)
{
	LOG_Block("AddHWID()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeHWID = NULL;
	IXMLDOMNode*	pNodeHWIDText = NULL;
	BSTR bstrNameHWID = NULL;

	if (fIsCompatible)
	{
		bstrNameHWID = SysAllocString(L"compid");
	}
	else
	{
		bstrNameHWID = SysAllocString(L"hwid");
	}

	pNodeHWID = CreateDOMNode(m_pDocSystemSpec, NODE_ELEMENT, bstrNameHWID);
	if (NULL == pNodeHWID) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeHWID, KEY_RANK, iRank));
	if (NULL != bstrDriverVer)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeHWID, KEY_DRIVERVER, bstrDriverVer));
	}
	pNodeHWIDText = CreateDOMNode(m_pDocSystemSpec, NODE_TEXT, NULL);
	if (NULL == pNodeHWIDText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeHWIDText, bstrHWID));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeHWID, pNodeHWIDText));
	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeDevice], pNodeHWID));

CleanUp:
	SafeReleaseNULL(pNodeHWID);
	SafeReleaseNULL(pNodeHWIDText);
	SysFreeString(bstrNameHWID);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetSystemSpecBSTR()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemSpec::GetSystemSpecBSTR(BSTR *pbstrXmlSystemSpec)
{
	LOG_Block("GetSystemSpecBSTR()");

	//
	// convert XML DOC into BSTR 
	//
	HRESULT hr = m_pDocSystemSpec->get_xml(pbstrXmlSystemSpec);
    if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}

	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// CXmlSystemClass

/////////////////////////////////////////////////////////////////////////////
// Constructor
//
// Create IXMLDOMDocument* for SystemInfoClasses
/////////////////////////////////////////////////////////////////////////////
CXmlSystemClass::CXmlSystemClass()
 : m_pDocSystemClass(NULL)
{
}


/////////////////////////////////////////////////////////////////////////////
// Destructor
//
// Release IXMLDOMDocument* for SystemInfoClasses
/////////////////////////////////////////////////////////////////////////////
CXmlSystemClass::~CXmlSystemClass()
{
	SafeReleaseNULL(m_pDocSystemClass);
}


/////////////////////////////////////////////////////////////////////////////
// LoadXMLDocument()
//
// Load an XML Document from string
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlSystemClass::LoadXMLDocument(BSTR bstrXml, BOOL fOfflineMode)
{
	LOG_Block("LoadXMLDocument()");
	SafeReleaseNULL(m_pDocSystemClass);
	HRESULT hr = LoadXMLDoc(bstrXml, &m_pDocSystemClass, fOfflineMode);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetClasses()
//
// Return the bitmap of existence of all possible system info classes
/////////////////////////////////////////////////////////////////////////////
DWORD CXmlSystemClass::GetClasses()
{
	LOG_Block("GetClasses()");

	DWORD				dwResult = 0;
    IXMLDOMNodeList*	pNodeList = NULL;

	BSTR bstrNameComputerSystem = SysAllocString(L"classes/computerSystem");
	BSTR bstrNameRegKeys = SysAllocString(L"classes/regKeys");
	BSTR bstrNamePlatform = SysAllocString(L"classes/platform");
	BSTR bstrNameLocale = SysAllocString(L"classes/locale");
	BSTR bstrNameDevices = SysAllocString(L"classes/devices");

	pNodeList = FindDOMNodeList(m_pDocSystemClass, bstrNameComputerSystem);
	if (NULL != pNodeList)
	{
		dwResult |= COMPUTERSYSTEM;
		SafeReleaseNULL(pNodeList);
	}

	pNodeList = FindDOMNodeList(m_pDocSystemClass, bstrNameRegKeys);
	if (NULL != pNodeList)
	{
		dwResult |= REGKEYS;
		SafeReleaseNULL(pNodeList);
	}
	
	pNodeList = FindDOMNodeList(m_pDocSystemClass, bstrNamePlatform);
	if (NULL != pNodeList)
	{
		dwResult |= PLATFORM;
		SafeReleaseNULL(pNodeList);
	}
	
	pNodeList = FindDOMNodeList(m_pDocSystemClass, bstrNameLocale);
	if (NULL != pNodeList)
	{
		dwResult |= LOCALE;
		SafeReleaseNULL(pNodeList);
	}

	pNodeList = FindDOMNodeList(m_pDocSystemClass, bstrNameDevices);
	if (NULL != pNodeList)
	{
		dwResult |= DEVICES;
		SafeReleaseNULL(pNodeList);
	}
	
	SysFreeString(bstrNameComputerSystem);
	SysFreeString(bstrNameRegKeys);
	SysFreeString(bstrNamePlatform);
	SysFreeString(bstrNameLocale);
	SysFreeString(bstrNameDevices);
	return dwResult;
}


/////////////////////////////////////////////////////////////////////////////
// CXmlCatalog

/////////////////////////////////////////////////////////////////////////////
// Constructor
//
// Create IXMLDOMDocument* for Catalog
/////////////////////////////////////////////////////////////////////////////
CXmlCatalog::CXmlCatalog()
 : m_pDocCatalog(NULL)
{
}


/////////////////////////////////////////////////////////////////////////////
// Destructor
//
// Release IXMLDOMDocument* for Catalog
/////////////////////////////////////////////////////////////////////////////
CXmlCatalog::~CXmlCatalog()
{
	SafeReleaseNULL(m_pDocCatalog);
}


/////////////////////////////////////////////////////////////////////////////
// LoadXMLDocument()
//
// Load an XML Document from string
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::LoadXMLDocument(BSTR bstrXml, BOOL fOfflineMode)
{
	LOG_Block("LoadXMLDocument()");
	SafeReleaseNULL(m_pDocCatalog);
	HRESULT hr = LoadXMLDoc(bstrXml, &m_pDocCatalog, fOfflineMode);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetItemCount()
//
// Gets a Count of How Many Items are in this Catalog
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemCount(LONG *plItemCount)
{
    LOG_Block("GetItemCount()");
    HRESULT hr = E_FAIL;
    IXMLDOMNodeList *pItemList = NULL;

    QuitIfNull(plItemCount);
    QuitIfNull(m_pDocCatalog);


	IXMLDOMNode	*pParentNode = NULL;
	CleanUpIfFailedAndSetHrMsg(m_pDocCatalog->QueryInterface(IID_IXMLDOMNode, (void**)&pParentNode));	
	CleanUpIfFailedAndSetHrMsg(pParentNode->selectNodes(KEY_ITEM_SEARCH, &pItemList));
    if (NULL == pItemList) goto CleanUp;

    CleanUpIfFailedAndSetHrMsg(pItemList->get_length(plItemCount));

CleanUp:
	SafeReleaseNULL(pParentNode);
    SafeReleaseNULL(pItemList);
    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetProviders()
// 
// Find a list of <provider> node in catalog xml
/////////////////////////////////////////////////////////////////////////////
IXMLDOMNodeList* CXmlCatalog::GetProviders()
{
    LOG_Block("GetProviders()");

	IXMLDOMNodeList*	pNodeList = NULL;
	
	pNodeList = FindDOMNodeList(m_pDocCatalog, KEY_CATALOG_PROVIDER);
	
	return pNodeList;
}


/////////////////////////////////////////////////////////////////////////////
// GetFirstProvider()
//
// Find the first provider in catalog xml doc
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CXmlCatalog::GetFirstProvider(HANDLE_NODE* phNodeProvider)
{
    LOG_Block("GetFirstProvider()");

	HANDLE_NODELIST hNodeListProvider = FindFirstDOMNode(m_pDocCatalog, KEY_CATALOG_PROVIDER, phNodeProvider);
	
	return hNodeListProvider;
}
	
	
/////////////////////////////////////////////////////////////////////////////
// GetNextProvider()
//
// Find the next provider in catalog xml doc
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetNextProvider(HANDLE_NODELIST hNodeListProvider, HANDLE_NODE* phNodeProvider)
{
    LOG_Block("GetNextProvider()");

	return FindNextDOMNode(hNodeListProvider, phNodeProvider);
}


/////////////////////////////////////////////////////////////////////////////
// GetFirstItem()
//
// Find the first item in provider (parent) node
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CXmlCatalog::GetFirstItem(HANDLE_NODE hNodeProvider, HANDLE_NODE* phNodeItem)
{
    LOG_Block("GetFirstItem()");

	HANDLE_NODELIST hNodeListItem = FindFirstDOMNode(m_ppNodeArray[hNodeProvider], KEY_ITEM, phNodeItem);

    return hNodeListItem;
}
	
	
/////////////////////////////////////////////////////////////////////////////
// GetNextItem()
//
// Find the next item in provider (parent) node
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetNextItem(HANDLE_NODELIST hNodeListItem, HANDLE_NODE* phNodeItem)
{
    LOG_Block("GetNextItem()");

	return FindNextDOMNode(hNodeListItem, phNodeItem);
}
	
/////////////////////////////////////////////////////////////////////////////
// GetFirstItemDependency()
//
// Find the first dependency item in Item Dependencies node
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CXmlCatalog::GetFirstItemDependency(HANDLE_NODE hNodeItem, HANDLE_NODE* phNodeItem)
{
    LOG_Block("GetFirstItemDependency");
    HRESULT hr;

    QuitIfNull(phNodeItem);

    IXMLDOMNode* pNodeDependencies = NULL;
    IXMLDOMNode* pNodeIdentity = NULL;
    HANDLE_NODELIST hNodeListItem = HANDLE_NODELIST_INVALID;
        
    hr = FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_DEPENDENCIES, &pNodeDependencies);
    QuitIfFail(hr);

    hNodeListItem = FindFirstDOMNode(pNodeDependencies, KEY_IDENTITY, &pNodeIdentity);
    if (HANDLE_NODELIST_INVALID != hNodeListItem)
    {
        // We found at least one Identity in this Dependencies key, Try to find the matching
        // Item in the Catalog and if found return as the phNodeItem
        FindItemByIdentity(pNodeIdentity, phNodeItem);
    }

CleanUp:
    SafeReleaseNULL(pNodeIdentity);
    SafeReleaseNULL(pNodeDependencies);

    return hNodeListItem;
}


/////////////////////////////////////////////////////////////////////////////
// GetNextItemDependency()
//
// Find the next dependency item in the Item Dependencies node
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetNextItemDependency(HANDLE_NODELIST hNodeListItem, HANDLE_NODE* phNodeItem)
{
    LOG_Block("GetNextItemDependency");
    HRESULT hr;

    QuitIfNull(phNodeItem);
    
    IXMLDOMNode* pNodeIdentity = NULL;

    hr = FindNextDOMNode(hNodeListItem, &pNodeIdentity);
    // This function is supposed to return S_FALSE when no more items are available
    // but FindNextDOMNode returns E_FAIL when it can't find the next node. So we won't
    // look at the return.
    // CleanUpIfFailedAndMsg(hr); 

    if (NULL != pNodeIdentity)
    {
        // We found another Identity in this Dependencies Key
        hr = FindItemByIdentity(pNodeIdentity, phNodeItem);
    }
    else 
    {
        hr = S_FALSE; // indicate to caller there are no more identities.
    }

    SafeReleaseNULL(pNodeIdentity);
    return hr;
}

	
/////////////////////////////////////////////////////////////////////////////
// CloseItemList()
//
// Release the item nodelist
/////////////////////////////////////////////////////////////////////////////
void CXmlCatalog::CloseItemList(HANDLE_NODELIST hNodeListItem)
{
	SafeFindCloseHandle(hNodeListItem);
}


/////////////////////////////////////////////////////////////////////////////
// GetIdentity()
//
// Retrieve the unique name (identity) of the given provider or item
/////////////////////////////////////////////////////////////////////////////
//
// public version
//
HRESULT CXmlCatalog::GetIdentity(HANDLE_NODE hNode,
								 BSTR* pbstrName,
								 BSTR* pbstrPublisherName,
								 BSTR* pbstrGUID)
{
    LOG_Block("GetIdentity()");

	HRESULT		hr = E_FAIL;
	IXMLDOMNode*	pNodeIdentity = NULL;

	CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNode], KEY_IDENTITY, &pNodeIdentity));
	hr = Get3IdentiStrFromIdentNode(pNodeIdentity, pbstrName, pbstrPublisherName, pbstrGUID);

CleanUp:
	SafeReleaseNULL(pNodeIdentity);
	return hr;
}




/////////////////////////////////////////////////////////////////////////////
// GetIdentityStr()
//
// Retrieve the string that can be used to uniquely identify an object
// based on its <identity> node.
//
// This function defines the logic about what components can be used
// to define the uniqueness of an item based on the 3 parts of data from
// GetIdentity().
//
// The created string will be language neutral. That is, it can not
// ensure the uniqueness for two items having the same <identity> node
// except different only on <langauge> part inside <identity>
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetIdentityStr(HANDLE_NODE hNode,
					BSTR* pbstrUniqIdentifierString)
{
	IXMLDOMNode* pIdentityNode = NULL;
	HRESULT hr = E_INVALIDARG;
	if (FindNode(m_ppNodeArray[hNode], KEY_IDENTITY, &pIdentityNode) && NULL != pIdentityNode)
	{
		hr = UtilGetUniqIdentityStr(pIdentityNode, pbstrUniqIdentifierString, 0x0);
		SafeReleaseNULL(pIdentityNode);
	}
	return hr;
}

HRESULT CXmlCatalog::GetIdentityStrForPing(HANDLE_NODE hNode,
                    BSTR* pbstrUniqIdentifierString)
{
	IXMLDOMNode* pIdentityNode = NULL;
	HRESULT hr = E_INVALIDARG;
	if (FindNode(m_ppNodeArray[hNode], KEY_IDENTITY, &pIdentityNode) && NULL != pIdentityNode)
	{
		//
		// first, based on OP team's requirement, we try to look for <itemId> in the identity tag.
		// if it's there, use use that. If not, we use the publisherName.itemname thing.
		//
		if (FAILED(hr = GetAttribute(pIdentityNode, KEY_ITEMID, pbstrUniqIdentifierString)) || NULL == *pbstrUniqIdentifierString)
		{
			// hr = UtilGetUniqIdentityStr(pIdentityNode, pbstrUniqIdentifierString, SKIP_SERVICEPACK_VER);
			hr = E_INVALIDARG;
		}
		
		SafeReleaseNULL(pIdentityNode);
	}
	return hr;
}



/////////////////////////////////////////////////////////////////////////////
// GetBSTRItemForCallback()
//
// Create an item node as the passed-in node, have child nodes identity and
// platform (anything uniquely idenitify this item) then output this 
// item node data as string, then delete the crated node
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetBSTRItemForCallback(HANDLE_NODE hItem, BSTR* pbstrXmlItemForCallback)
{
	HRESULT hr = E_INVALIDARG;
	IXMLDOMNode* pNewItemNode = NULL;
	IXMLDOMNode* pNewIdentityNode = NULL;
	IXMLDOMNode* pNewPlatformNode = NULL;
	IXMLDOMNode* pOldIdentityNode = NULL;
	IXMLDOMNode* pOldPlatformNode = NULL;
	IXMLDOMNode* p1 = NULL, *p2 = NULL;

	LOG_Block("CXmlCatalog::GetBSTRItemForCallback()");

	if (FAILED(hr = m_ppNodeArray[hItem]->cloneNode(VARIANT_FALSE, &pNewItemNode)) ||
		FAILED(hr = FindSingleDOMNode(m_ppNodeArray[hItem], KEY_IDENTITY, &pOldIdentityNode)) || 
		NULL == pOldIdentityNode ||
		FAILED(hr = pOldIdentityNode->cloneNode(VARIANT_TRUE, &pNewIdentityNode)) ||
		NULL == pNewIdentityNode ||
		FAILED(hr = pNewItemNode->appendChild(pNewIdentityNode, &p1)) || NULL == p1)	
	{
		if (S_FALSE == hr)
			hr = E_INVALIDARG;
		LOG_ErrorMsg(hr);
		goto CleanUp;
	}

	//
	// platform is optional
	//
	FindSingleDOMNode(m_ppNodeArray[hItem], KEY_PLATFORM, &pOldPlatformNode);
	if (pOldPlatformNode)
	{
		if (FAILED(hr = pOldPlatformNode->cloneNode(VARIANT_TRUE, &pNewPlatformNode)) ||
			NULL == pNewPlatformNode ||
			FAILED(hr = pNewItemNode->appendChild(pNewPlatformNode, &p2)) || NULL == p2)

		{
			LOG_ErrorMsg(hr);
			goto CleanUp;
		}
	}
		
	pNewItemNode->get_xml(pbstrXmlItemForCallback);
	

CleanUp:
	SafeReleaseNULL(pOldIdentityNode);
	SafeReleaseNULL(pOldPlatformNode);
	SafeReleaseNULL(pNewIdentityNode);
	SafeReleaseNULL(pNewPlatformNode);
	SafeReleaseNULL(p1);
	SafeReleaseNULL(p2);
	SafeReleaseNULL(pNewItemNode);

	return hr;
}



/////////////////////////////////////////////////////////////////////////////
// IsPrinterDriver()
//
// Retrieves from the Catalog whether this Item is a Printer Driver
//
/////////////////////////////////////////////////////////////////////////////
BOOL CXmlCatalog::IsPrinterDriver(HANDLE_NODE hNode)
{
    LOG_Block("IsPrinterDriver()");
    BOOL fRet = FALSE;
    HRESULT hr;

    IXMLDOMNode* pNodeDetection = NULL;
    IXMLDOMNode* pNodeCompatibleHardware = NULL;
    IXMLDOMNode* pNodeDevice = NULL;

    hr = FindSingleDOMNode(m_ppNodeArray[hNode], KEY_DETECTION, &pNodeDetection);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDetection, KEY_COMPATIBLEHARDWARE, &pNodeCompatibleHardware);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeCompatibleHardware, KEY_DEVICE, &pNodeDevice);
    CleanUpIfFailedAndMsg(hr);

    int intIsPrinter = 0;
    GetAttribute(pNodeDevice, KEY_ISPRINTER, &intIsPrinter);

    if (1 == intIsPrinter)
    {
        fRet = TRUE;
    }
    else
    {
        fRet = FALSE;
    }

CleanUp:
    SafeReleaseNULL(pNodeDevice);
    SafeReleaseNULL(pNodeCompatibleHardware);
    SafeReleaseNULL(pNodeDetection);

    return fRet;
}

/////////////////////////////////////////////////////////////////////////////
// GetDriverInfo()
//
// Retrieves the Driver Information from the Catalog for this Item. Returns
// the Display Name and HWID for this driver - This is passed to the CDM 
// installer
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetDriverInfo(HANDLE_NODE hNode, 
                                   BSTR* pbstrHWID, 
                                   BSTR* pbstrDisplayName)
{
    HRESULT hr;
    LOG_Block("GetDriverInfo()");

    QuitIfNull(pbstrHWID);
    QuitIfNull(pbstrDisplayName);

    *pbstrHWID = NULL;
    *pbstrDisplayName = NULL;
    
    IXMLDOMNode* pNodeDetection = NULL;
    IXMLDOMNode* pNodeCompatibleHardware = NULL;
    IXMLDOMNode* pNodeDevice = NULL;
    IXMLDOMNode* pNodeHWID = NULL;
    IXMLDOMNode* pNodeDescription = NULL;
    IXMLDOMNode* pNodeDescriptionText = NULL;
    IXMLDOMNode* pNodeTitle = NULL;

    hr = FindSingleDOMNode(m_ppNodeArray[hNode], KEY_DETECTION, &pNodeDetection);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDetection, KEY_COMPATIBLEHARDWARE, &pNodeCompatibleHardware);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeCompatibleHardware, KEY_DEVICE, &pNodeDevice);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDevice, KEY_HWID, &pNodeHWID);
    CleanUpIfFailedAndMsg(hr);

    GetText(pNodeHWID, pbstrHWID);

    hr = FindSingleDOMNode(pNodeDetection, KEY_DESCRIPTION, &pNodeDescription);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDescription, KEY_DESCRIPTIONTEXT, &pNodeDescriptionText);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDescriptionText, KEY_TITLE, &pNodeTitle);
    CleanUpIfFailedAndMsg(hr);

    GetText(pNodeTitle, pbstrDisplayName);

CleanUp:
    if (FAILED(hr))
    {
        SafeSysFreeString(*pbstrHWID);
        SafeSysFreeString(*pbstrDisplayName);
    }
    SafeReleaseNULL(pNodeTitle);
    SafeReleaseNULL(pNodeDescriptionText);
    SafeReleaseNULL(pNodeDescription);
    SafeReleaseNULL(pNodeHWID);
    SafeReleaseNULL(pNodeDevice);
    SafeReleaseNULL(pNodeCompatibleHardware);
    SafeReleaseNULL(pNodeDetection);

    return hr;
}

/////////////////////////////////////////////////////////////////////////////
// GetPrinterDriverInfo()
//
// Retrieves the Printer Driver Information from the Catalog for this Item. 
// Returns the DriverName and the Architecture - This is passed to the CDM
// installer
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetPrinterDriverInfo(HANDLE_NODE hNode,
                                          BSTR* pbstrDriverName,
                                          BSTR* pbstrArchitecture)
{
    HRESULT hr;
    LOG_Block("GetPrinterDriverInfo()");

    QuitIfNull(pbstrDriverName);
    QuitIfNull(pbstrArchitecture);

    IXMLDOMNode* pNodeDetection = NULL;
    IXMLDOMNode* pNodeCompatibleHardware = NULL;
    IXMLDOMNode* pNodeDevice = NULL;
    IXMLDOMNode* pNodePrinterInfo = NULL;

    hr = FindSingleDOMNode(m_ppNodeArray[hNode], KEY_DETECTION, &pNodeDetection);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDetection, KEY_COMPATIBLEHARDWARE, &pNodeCompatibleHardware);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeCompatibleHardware, KEY_DEVICE, &pNodeDevice);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDevice, KEY_PRINTERINFO, &pNodePrinterInfo);
    CleanUpIfFailedAndMsg(hr);

    GetAttribute(pNodePrinterInfo, KEY_DRIVERNAME, pbstrDriverName);

    // NOTE: Currently the CatalogSchema site is not returning 
    // architecture for printers, and the schema doesn't require it
    // CDM is using a default string for now based on compile architecture
    // so we'll leave pbstrArchitecture NULL..

CleanUp:
    SafeReleaseNULL(pNodePrinterInfo);
    SafeReleaseNULL(pNodeDevice);
    SafeReleaseNULL(pNodeCompatibleHardware);
    SafeReleaseNULL(pNodeDetection);

    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetDriverInfoEx()
//
// Combines functionality of IsPrinterDriver, GetDriverInfo, and
// GetPrinterDriverInfo plus retreives MfgName and DriverProvider.
// Used by FindMatchingDriver()
//
// If SUCCEEDES pbstrHWID, pbstrDriverVer, and pbstrDisplayName
//    are always returned.
// If SUCCEEDES && *pFIsPrinter == TRUE then pbstrDriverName,
//    pbstrDriverProvider, and pbstrMfgName are returned.
//
// Currently pbstrArchitecture is never returned.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetDriverInfoEx(	HANDLE_NODE hNode,
										BOOL* pfIsPrinter,
										BSTR* pbstrHWID,
										BSTR* pbstrDriverVer,
										BSTR* pbstrDisplayName,
                                        BSTR* pbstrDriverName,
										BSTR* pbstrDriverProvider,
										BSTR* pbstrMfgName,
                                        BSTR* pbstrArchitecture)
{
    HRESULT hr;
    LOG_Block("GetDriverInfoEx()");

    QuitIfNull(pbstrHWID);
	QuitIfNull(pbstrDriverVer);
	QuitIfNull(pbstrDisplayName);
	QuitIfNull(pbstrDriverName);
	QuitIfNull(pbstrDriverProvider);
	QuitIfNull(pbstrMfgName);
	QuitIfNull(pbstrArchitecture);
    
	*pbstrHWID = NULL;
	*pbstrDriverVer = NULL;
	*pbstrDisplayName = NULL;
	*pbstrDriverName = NULL;
	*pbstrDriverProvider = NULL;
	*pbstrMfgName = NULL;
	*pbstrArchitecture = NULL;
    
    IXMLDOMNode* pNodeDetection = NULL;
    IXMLDOMNode* pNodeCompatibleHardware = NULL;
    IXMLDOMNode* pNodeDevice = NULL;
    IXMLDOMNode* pNodeHWID = NULL;
    IXMLDOMNode* pNodeDescription = NULL;
    IXMLDOMNode* pNodeDescriptionText = NULL;
    IXMLDOMNode* pNodeTitle = NULL;
    IXMLDOMNode* pNodePrinterInfo = NULL;

    hr = FindSingleDOMNode(m_ppNodeArray[hNode], KEY_DETECTION, &pNodeDetection);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDetection, KEY_COMPATIBLEHARDWARE, &pNodeCompatibleHardware);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeCompatibleHardware, KEY_DEVICE, &pNodeDevice);
    CleanUpIfFailedAndMsg(hr);
	//
	// Is it a printer?
	//
	int intIsPrinter = 0;
    GetAttribute(pNodeDevice, KEY_ISPRINTER, &intIsPrinter);

    if (1 == intIsPrinter)
    {
        *pfIsPrinter = TRUE;
    }
    else
    {
        *pfIsPrinter = FALSE;
    }
	//
	// HWID and Driver Description
	//
    hr = FindSingleDOMNode(pNodeDevice, KEY_HWID, &pNodeHWID);
    CleanUpIfFailedAndMsg(hr);

    hr = GetText(pNodeHWID, pbstrHWID);
    CleanUpIfFailedAndMsg(hr);

	hr = GetAttribute(pNodeHWID, KEY_DRIVERVER, pbstrDriverVer);
    CleanUpIfFailedAndMsg(hr);

    hr = FindSingleDOMNode(m_ppNodeArray[hNode], KEY_DESCRIPTION, &pNodeDescription);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDescription, KEY_DESCRIPTIONTEXT, &pNodeDescriptionText);
    CleanUpIfFailedAndMsg(hr);
    hr = FindSingleDOMNode(pNodeDescriptionText, KEY_TITLE, &pNodeTitle);
    CleanUpIfFailedAndMsg(hr);

    hr = GetText(pNodeTitle, pbstrDisplayName);
    CleanUpIfFailedAndMsg(hr);

	if (*pfIsPrinter)
	{
		//
		// Printer Attributes
		//
		hr = FindSingleDOMNode(pNodeDevice, KEY_PRINTERINFO, &pNodePrinterInfo);
		CleanUpIfFailedAndMsg(hr);

		hr = GetAttribute(pNodePrinterInfo, KEY_DRIVERNAME, pbstrDriverName);
		CleanUpIfFailedAndMsg(hr);

		hr = GetAttribute(pNodePrinterInfo, KEY_DRIVERPROVIDER, pbstrDriverProvider);
		CleanUpIfFailedAndMsg(hr);

		hr = GetAttribute(pNodePrinterInfo, KEY_MFGNAME, pbstrMfgName);
		CleanUpIfFailedAndMsg(hr);

		// NOTE: Currently the CatalogSchema site is not returning 
		// architecture for printers, and the schema doesn't require it
		// CDM is using a default string for now based on compile architecture
		// so we'll leave pbstrArchitecture NULL..
	}

CleanUp:
    if (FAILED(hr))
	{
		SafeSysFreeString(*pbstrHWID);
		SafeSysFreeString(*pbstrDriverVer);
		SafeSysFreeString(*pbstrDisplayName);
		SafeSysFreeString(*pbstrDriverName);
		SafeSysFreeString(*pbstrDriverProvider);
		SafeSysFreeString(*pbstrMfgName);
		SafeSysFreeString(*pbstrArchitecture);
	}
    SafeReleaseNULL(pNodeTitle);
    SafeReleaseNULL(pNodeDescriptionText);
    SafeReleaseNULL(pNodeDescription);
    SafeReleaseNULL(pNodeHWID);
    SafeReleaseNULL(pNodeDevice);
    SafeReleaseNULL(pNodeCompatibleHardware);
    SafeReleaseNULL(pNodeDetection);
	SafeReleaseNULL(pNodePrinterInfo);

    return hr;
}

/////////////////////////////////////////////////////////////////////////////
// GetItemPlatformStr()
//
// The input node pointer points to a node has <identity> as its child.
// This function will retrieve the <platform> node from <identity> and
// convert the data inside <platform> into a string that can be used to
// uniquely identify a platform.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemFirstPlatformStr(HANDLE_NODE hNodeItem,
					BSTR* pbstrPlatform)
{
	HRESULT hr;

	IXMLDOMNode* pNodePlatform = NULL;

	LOG_Block("GetItemFirstPlatformStr");

	USES_IU_CONVERSION;

	//
	// get the first platform node from this item node
	//
	hr = FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_PLATFORM, &pNodePlatform);
	CleanUpIfFailedAndMsg(hr);

	//
	// get platform data from this ndoe and convert it into string
	//
	hr = GetPlatformStrForPing(pNodePlatform, pbstrPlatform);

CleanUp:

	SafeReleaseNULL(pNodePlatform);

	//
	// since platform is not a required element in <item>, so we should not
	// return error if not found
	//
	if (HRESULT_FROM_WIN32(ERROR_NOT_FOUND) == hr)
	{
		*pbstrPlatform = NULL;
		hr = S_FALSE;
	}

	return hr;
}



/////////////////////////////////////////////////////////////////////////////
// GetItemAllPlatformStr()
//
// The input node pointer points to an item node that has <platform> node(s).
// This function will retrieve every <platform> node from this item node and
// convert the data inside <platform> into a string that can be used to
// uniquely identify a platform.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemAllPlatformStr(HANDLE_NODE hNodeItem,
					BSTR** ppbPlatforms, UINT* pnPlatformCount)
{
	LOG_Block("GetItemAllPlatformStr");

	HRESULT hr;
	IXMLDOMNodeList*	pPlatformList = NULL;
	IXMLDOMElement*		pElement = NULL;
	IXMLDOMNode*		pNodePlatform = NULL;

	long				lCount = 0;
	int					i;
	BSTR*				pbstrPlatformList = NULL;

	//
	// get list of platform nodes
	//
	hr = m_ppNodeArray[hNodeItem]->QueryInterface(IID_IXMLDOMElement, (void**)&pElement);
	CleanUpIfFailedAndMsg(hr);
	hr = pElement->getElementsByTagName(KEY_PLATFORM, &pPlatformList);
	CleanUpIfFailedAndMsg(hr);

	hr = pPlatformList->get_length(&lCount);
	CleanUpIfFailedAndMsg(hr);

	if (0 == lCount)
	{
		goto CleanUp;
	}


	pbstrPlatformList = (BSTR*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lCount * sizeof(BSTR*));
	CleanUpFailedAllocSetHrMsg(pbstrPlatformList);

	//
	// loop through each suite, if any
	//
	pPlatformList->reset();
	for (i = 0; i < lCount; i++)
	{
		hr = pPlatformList->get_item(i, &pNodePlatform);
		CleanUpIfFailedAndMsg(hr);
		if (pNodePlatform)
		{
			hr = pNodePlatform->get_text(&(pbstrPlatformList[i]));
			CleanUpIfFailedAndMsg(hr);
			pNodePlatform->Release();
			pNodePlatform = NULL;
		}
	}

	hr = S_OK;


CleanUp:
	SafeReleaseNULL(pNodePlatform);
	SafeReleaseNULL(pPlatformList);
	SafeReleaseNULL(pElement);

	if (SUCCEEDED(hr))
	{
		*pnPlatformCount = lCount;
		*ppbPlatforms = pbstrPlatformList;
	}
	else
	{
		if (NULL != pbstrPlatformList)
		{
			*pnPlatformCount = 0;
			//
			// release all possibly allocated memory
			//
			for (i = 0; i < lCount; i++)
			{
				SafeSysFreeString(pbstrPlatformList[i]);
			}
			HeapFree(GetProcessHeap(), 0, pbstrPlatformList);
		}
	}

	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetItemPlatformStr()
//
// The input node pointer points to a node has <identity> as its child.
// This function will retrieve the <platform> node from <identity> and
// convert the data inside <platform> into a string that can be used to
// uniquely identify a platform.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetPlatformStr(IXMLDOMNode* pNodePlatform,
					BSTR* pbstrPlatform)
{
	return UtilGetPlatformStr(pNodePlatform, pbstrPlatform, 0x0);
}

HRESULT CXmlCatalog::GetPlatformStrForPing(IXMLDOMNode* pNodePlatform,
					BSTR* pbstrPlatform)
{
	return UtilGetPlatformStr(pNodePlatform, pbstrPlatform, SKIP_SUITES | SKIP_SERVICEPACK_VER);
}


/////////////////////////////////////////////////////////////////////////////
//
// get data from a version node and convert them into a string with
// format: 
//			VersionStr   = <Version>[,<SvcPackVer>[,<timeStamp>]]
//			<Version>    = <Major>[.<Minor>[.<Build>]]
//			<SvcPackVer> = <Major>[.<minor>]
//
// Assumption:
//			pszVersion points to a buffer LARGE ENOUGH to store
//			any legal version number.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::getVersionStr(IXMLDOMNode* pNodeVersion, LPTSTR pszVersion)
{
	return UtilGetVersionStr(pNodeVersion, pszVersion, 0x0);
}

HRESULT CXmlCatalog::getVersionStrWithoutSvcPack(IXMLDOMNode* pNodeVersion, LPTSTR pszVersion)
{
	return UtilGetVersionStr(pNodeVersion, pszVersion, SKIP_SERVICEPACK_VER);
}



/////////////////////////////////////////////////////////////////////////////
// GetItemFirstLanguageStr()
//
// The input node pointer points to a node has <identity> as its child.
// This function will retrieve the first <language> node from <identity> 
// node 
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemFirstLanguageStr(HANDLE_NODE hNodeItem,
					BSTR* pbstrLanguage)
{
	LOG_Block("GetItemFirstLanguageStr");

	IXMLDOMNode* pNodeIdentity = NULL;
	IXMLDOMNode* pNodeLanguage = NULL;

	HRESULT hr = m_ppNodeArray[hNodeItem]->selectSingleNode(KEY_IDENTITY, &pNodeIdentity);
	CleanUpIfFailedAndMsg(hr);

	if (pNodeIdentity)
	{
		if (HANDLE_NODELIST_INVALID == FindFirstDOMNode(pNodeIdentity, KEY_LANGUAGE, &pNodeLanguage))
		{
			hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
			goto CleanUp;
		}
		else
		{
			hr = pNodeLanguage->get_text(pbstrLanguage);
			CleanUpIfFailedAndMsg(hr);
		}
	}

CleanUp:

	SafeReleaseNULL(pNodeLanguage);
	SafeReleaseNULL(pNodeIdentity);

	//
	// since language is not a required element in <identity>, so we should not
	// return error if not found
	//
	if (HRESULT_FROM_WIN32(ERROR_NOT_FOUND) == hr)
	{
		*pbstrLanguage = NULL;
		hr = S_FALSE;
	}

	return hr;

}


/////////////////////////////////////////////////////////////////////////////
// GetItemAllLanguageStr()
//
// The input node pointer points to a node has <identity> as its child.
// This function will retrieve every <language> node from <identity> node and
// convert the data into an BSTR array to return.
//
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemAllLanguageStr(HANDLE_NODE hNodeItem,
					BSTR** ppbstrLanguage, UINT* pnLangCount)
{
	LOG_Block("GetItemAllLanguageStr");

	HRESULT				hr;
	IXMLDOMNode*		pNodeIdentity = NULL;
	IXMLDOMNodeList*	pLanguageList = NULL;
	IXMLDOMElement*		pElement = NULL;
	IXMLDOMNode*		pNodeLanguage = NULL;

	long				lCount = 0;
	int					i;
	BSTR*				pbstrLanguageList = NULL;

	hr = m_ppNodeArray[hNodeItem]->selectSingleNode(KEY_IDENTITY, &pNodeIdentity);
	CleanUpIfFailedAndMsg(hr);

	//
	// get list of  nodes
	//
	hr = pNodeIdentity->QueryInterface(IID_IXMLDOMElement, (void**)&pElement);
	CleanUpIfFailedAndMsg(hr);
	hr = pElement->getElementsByTagName(KEY_LANGUAGE, &pLanguageList);
	CleanUpIfFailedAndMsg(hr);

	hr = pLanguageList->get_length(&lCount);
	CleanUpIfFailedAndMsg(hr);

	if (0 == lCount)
	{
		goto CleanUp;
	}


	pbstrLanguageList = (BSTR*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lCount * sizeof(BSTR*));
	CleanUpFailedAllocSetHrMsg(pbstrLanguageList);

	//
	// loop through each suite, if any
	//
	pLanguageList->reset();
	for (i = 0; i < lCount; i++)
	{
		hr = pLanguageList->get_item(i, &pNodeLanguage);
		CleanUpIfFailedAndMsg(hr);
		if (pNodeLanguage)
		{
			hr = pNodeLanguage->get_text(&(pbstrLanguageList[i]));
			CleanUpIfFailedAndMsg(hr);
			pNodeLanguage->Release();
			pNodeLanguage = NULL;
		}
	}

	hr = S_OK;


CleanUp:
	SafeReleaseNULL(pNodeLanguage);
	SafeReleaseNULL(pLanguageList);
	SafeReleaseNULL(pElement);
	SafeReleaseNULL(pNodeIdentity);

	if (SUCCEEDED(hr))
	{
		*pnLangCount = lCount;
		*ppbstrLanguage = pbstrLanguageList;
	}
	else
	{

		if (NULL != pbstrLanguageList)
		{
			*pnLangCount = 0;
			//
			// release all possibly allocated memory
			//
			for (i = 0; i < lCount; i++)
			{
				SafeSysFreeString(pbstrLanguageList[i]);
			}
			HeapFree(GetProcessHeap(), 0, pbstrLanguageList);
		}
	}

	return hr;



}


/////////////////////////////////////////////////////////////////////////////
// GetItemFirstCodeBase()
//
// Find the first codebase (path) of the given item
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CXmlCatalog::GetItemFirstCodeBase(HANDLE_NODE hNodeItem,
												  BSTR* pbstrCodeBase,
												  BSTR* pbstrName,
												  BSTR* pbstrCRC,
												  BOOL* pfPatchAvailable,
												  LONG* plSize)
{
	LOG_Block("GetItemFirstCodeBase()");

	HRESULT		hr = E_FAIL;

	QuitIfNull(pbstrCodeBase);
	QuitIfNull(pbstrName);
	QuitIfNull(pbstrCRC);
	QuitIfNull(pfPatchAvailable);
	QuitIfNull(plSize);

	IXMLDOMNode*	pNodeInstall = NULL;
	IXMLDOMNode*	pNodeCodeBaseSize = NULL;

	HANDLE_NODE		hNodeCodeBase = HANDLE_NODE_INVALID;
	HANDLE_NODELIST hNodeListCodeBase = HANDLE_NODELIST_INVALID;

	*pbstrCodeBase = NULL;
	*pbstrName = NULL;
	*pbstrCRC = NULL;
	*pfPatchAvailable = FALSE;
	*plSize = -1;
	BSTR bstrSize = NULL;

	CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_INSTALLATION, &pNodeInstall));
	hNodeListCodeBase = FindFirstDOMNode(pNodeInstall, KEY_CODEBASE, &hNodeCodeBase);
	if (HANDLE_NODELIST_INVALID == hNodeListCodeBase) goto CleanUp;
	CleanUpIfFailedAndSetHrMsg(GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_HREF, pbstrCodeBase));
	CleanUpIfFailedAndSetHrMsg(GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_PATCHAVAILABLE, pfPatchAvailable));
	GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_CRC, pbstrCRC); // optional attribute, don't fail if its not there.
	GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_NAME, pbstrName);
	FindSingleDOMNode(m_ppNodeArray[hNodeCodeBase], KEY_SIZE, &pNodeCodeBaseSize);
	GetText(pNodeCodeBaseSize, &bstrSize);
	if (NULL != bstrSize)
	{
		*plSize = MyBSTR2L(bstrSize);
		SysFreeString(bstrSize);
	}

CleanUp:
	SafeReleaseNULL(pNodeInstall);
	SafeReleaseNULL(pNodeCodeBaseSize);
	if (FAILED(hr))
	{
		SafeSysFreeString(*pbstrCodeBase);
		SafeSysFreeString(*pbstrName);
		SafeSysFreeString(*pbstrCRC);
		*pfPatchAvailable = FALSE;
		*plSize = -1;
	}
	return hNodeListCodeBase;
}


/////////////////////////////////////////////////////////////////////////////
// GetItemNextCodeBase()
//
// Find the next codebase (path) of the given item
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemNextCodeBase(HANDLE_NODELIST hNodeListCodeBase,
										 BSTR* pbstrCodeBase,
										 BSTR* pbstrName,
										 BSTR* pbstrCRC,
										 BOOL* pfPatchAvailable,
										 LONG* plSize)
{
    LOG_Block("GetItemNextCodeBase()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeCodeBaseSize = NULL;

	HANDLE_NODE		hNodeCodeBase = HANDLE_NODE_INVALID;

	if (NULL == pbstrCodeBase || NULL == pbstrName || NULL == pbstrCRC || NULL == pfPatchAvailable || NULL == plSize)
	{
		return E_INVALIDARG;
	}

	*pbstrCodeBase = NULL;
	*pbstrName = NULL;
	*pbstrCRC = NULL;
	*pfPatchAvailable = FALSE;
	*plSize = -1;
	BSTR bstrSize = NULL;
	
	if (SUCCEEDED(hr = FindNextDOMNode(hNodeListCodeBase, &hNodeCodeBase)))
	{
		CleanUpIfFailedAndSetHrMsg(GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_HREF, pbstrCodeBase));
		CleanUpIfFailedAndSetHrMsg(GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_PATCHAVAILABLE, pfPatchAvailable));
		GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_NAME, pbstrName);
		GetAttribute(m_ppNodeArray[hNodeCodeBase], KEY_CRC, pbstrCRC);
		FindSingleDOMNode(m_ppNodeArray[hNodeCodeBase], KEY_SIZE, &pNodeCodeBaseSize);
		GetText(pNodeCodeBaseSize, &bstrSize);
		if (NULL != bstrSize)
		{
			*plSize = MyBSTR2L(bstrSize);
		}
	}

CleanUp:
	SafeReleaseNULL(pNodeCodeBaseSize);
	SafeSysFreeString(bstrSize);
	if (FAILED(hr))
	{
		SafeSysFreeString(*pbstrCodeBase);
		SafeSysFreeString(*pbstrCRC);
		SafeSysFreeString(*pbstrName);
		*pfPatchAvailable = FALSE;
		*plSize = -1;
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetItemInstallInfo()
//
// Retrieve the installation information of the given item
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemInstallInfo(HANDLE_NODE hNodeItem,
                                        BSTR* pbstrInstallerType,
										BOOL* pfExclusive,
                                        BOOL* pfReboot,
                                        LONG* plNumCommands)
{
    LOG_Block("GetItemInstallInfo()");

    HRESULT     hr = E_FAIL;

    QuitIfNull(pbstrInstallerType);
	QuitIfNull(pfExclusive);
    QuitIfNull(pfReboot);
    QuitIfNull(plNumCommands);
    *pbstrInstallerType = NULL;
    *plNumCommands = 0; // may not be any, so initialize to 0

    IXMLDOMNode*        pNodeInstall = NULL;
    IXMLDOMNodeList*    pNodeListCommands = NULL;

    CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_INSTALLATION, &pNodeInstall));
    CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstall, KEY_INSTALLERTYPE, pbstrInstallerType));
	CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstall, KEY_EXCLUSIVE, pfExclusive));
    CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstall, KEY_NEEDSREBOOT, pfReboot));
    pNodeListCommands = FindDOMNodeList(pNodeInstall, KEY_COMMAND);
    if (NULL != pNodeListCommands)
    {
        CleanUpIfFailedAndSetHrMsg(pNodeListCommands->get_length(plNumCommands));
    }

CleanUp:
    if (FAILED(hr))
    {
        SafeSysFreeString(*pbstrInstallerType);
    }
    SafeReleaseNULL(pNodeInstall);
    SafeReleaseNULL(pNodeListCommands);
    return hr;
}

	
/////////////////////////////////////////////////////////////////////////////
// GetItemInstallCommand()
//
// Find the installation command and switches of the given item
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemInstallCommand(HANDLE_NODE hNodeItem,
                                           INT   iOrder,
                                           BSTR* pbstrCommandType,
                                           BSTR* pbstrCommand,
                                           BSTR* pbstrSwitches,
                                           BSTR* pbstrInfSection)
{
    LOG_Block("GetItemInstallCommand()");

    USES_IU_CONVERSION;

    HRESULT     hr = E_FAIL;

	QuitIfNull(pbstrCommandType);
	QuitIfNull(pbstrCommand);
	QuitIfNull(pbstrSwitches);
	QuitIfNull(pbstrInfSection);

	*pbstrCommandType = NULL;
	*pbstrCommand = NULL;
	*pbstrSwitches = NULL;
	*pbstrInfSection = NULL;

    IXMLDOMNode*    pNodeInstall = NULL;
    IXMLDOMNode*    pNodeCommand = NULL;
    IXMLDOMNode*    pNodeSwitches = NULL;
    BSTR bstrNameCommandTemp = SysAllocString(L"command[@order = ");

    TCHAR szCommand[64];

    hr = StringCchPrintfEx(szCommand, ARRAYSIZE(szCommand), NULL, NULL, MISTSAFE_STRING_FLAGS,
                           _T("%ls\"%d\"]"), bstrNameCommandTemp, iOrder);
    CleanUpIfFailedAndSetHrMsg(hr);

    BSTR bstrNameCommand = SysAllocString(T2OLE(szCommand));

    CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_INSTALLATION, &pNodeInstall));
    CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeInstall, bstrNameCommand, &pNodeCommand));
    CleanUpIfFailedAndSetHrMsg(GetText(pNodeCommand, pbstrCommand));
    CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeCommand, KEY_COMMANDTYPE, pbstrCommandType));
    CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeCommand, KEY_INFINSTALL, pbstrInfSection));
    if (SUCCEEDED(FindSingleDOMNode(pNodeCommand, KEY_SWITCHES, &pNodeSwitches)))
	{
		CleanUpIfFailedAndSetHrMsg(GetText(pNodeSwitches, pbstrSwitches));
	}

CleanUp:
	if (FAILED(hr))
	{
		SafeSysFreeString(*pbstrCommandType);
		SafeSysFreeString(*pbstrCommand);
		SafeSysFreeString(*pbstrSwitches);
		SafeSysFreeString(*pbstrInfSection);
	}
    SafeReleaseNULL(pNodeInstall);
    SafeReleaseNULL(pNodeCommand);
    SafeReleaseNULL(pNodeSwitches);
    SysFreeString(bstrNameCommand);
    SysFreeString(bstrNameCommandTemp);
    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// CloseItem()
//
// Release the item node
/////////////////////////////////////////////////////////////////////////////
void CXmlCatalog::CloseItem(HANDLE_NODE hNodeItem)
{
	SafeCloseHandleNode(hNodeItem);
}


/////////////////////////////////////////////////////////////////////////////
// GetTotalEstimatedSize()
//
// Get the Total Estimated Download Size of all Items based on Codebase Size
HRESULT CXmlCatalog::GetTotalEstimatedSize(LONG *plTotalSize)
{
    LOG_Block("GetTotalEstimatedSize()");

    HRESULT hr = E_FAIL;
    BSTR bstrCodebaseSize = NULL;
    IXMLDOMNodeList *pItemList = NULL;
    IXMLDOMNodeList *pCodebaseList = NULL;
    IXMLDOMNode *pNodeItem = NULL;
    IXMLDOMNode *pNodeInstall = NULL;
    IXMLDOMNode *pNodeCodebase = NULL;
    IXMLDOMNode *pNodeSize = NULL;
    LONG lItemCount = 0;
    LONG lCodebaseCount = 0;
    LONG lTotalSize = 0;

    QuitIfNull(plTotalSize);
    *plTotalSize = 0;

	IXMLDOMNode	*pParentNode = NULL;
	CleanUpIfFailedAndSetHrMsg(m_pDocCatalog->QueryInterface(IID_IXMLDOMNode, (void**)&pParentNode));
	CleanUpIfFailedAndSetHrMsg(pParentNode->selectNodes(KEY_ITEM_SEARCH, &pItemList));
    if (NULL == pItemList) goto CleanUp;

    CleanUpIfFailedAndSetHrMsg(pItemList->get_length(&lItemCount));
    if (0 == lItemCount) goto CleanUp; // no items

    // Loop through each Item
    CleanUpIfFailedAndSetHrMsg(pItemList->nextNode(&pNodeItem));
    while (NULL != pNodeItem)
    {
        // Get Installation Element
        CleanUpIfFailedAndSetHrMsg(pNodeItem->selectSingleNode(KEY_INSTALLATION, &pNodeInstall));
        if (NULL == pNodeInstall) goto CleanUp;

        CleanUpIfFailedAndSetHrMsg(pNodeInstall->selectNodes(KEY_CODEBASE, &pCodebaseList));
        if (NULL == pCodebaseList) goto CleanUp;

        CleanUpIfFailedAndSetHrMsg(pCodebaseList->get_length(&lCodebaseCount));
        if (0 == lCodebaseCount) goto CleanUp; // must be at least 1 cab

        // Loop through each Codebase Getting the Size for Each one
        CleanUpIfFailedAndSetHrMsg(pCodebaseList->nextNode(&pNodeCodebase));
        while (NULL != pNodeCodebase)
        {
            // Get the Size Element
            CleanUpIfFailedAndSetHrMsg(pNodeCodebase->selectSingleNode(KEY_SIZE, &pNodeSize));
            if (NULL != pNodeSize)
            {
                pNodeSize->get_text(&bstrCodebaseSize);
                if (NULL != bstrCodebaseSize)
                {
                    // Add this Codebase's size to the total download size
                    lTotalSize += (DWORD) MyBSTR2L(bstrCodebaseSize);
                    SafeSysFreeString(bstrCodebaseSize);
                }
            }
            SafeReleaseNULL(pNodeSize);
            SafeReleaseNULL(pNodeCodebase);
            CleanUpIfFailedAndSetHrMsg(pCodebaseList->nextNode(&pNodeCodebase));
        }
        SafeReleaseNULL(pCodebaseList);
        SafeReleaseNULL(pNodeInstall);
        SafeReleaseNULL(pNodeItem);
        CleanUpIfFailedAndSetHrMsg(pItemList->nextNode(&pNodeItem)); // get the next Item Node
    }

    // Update the Total Size after completing.. if we fail anywhere through here
    // we'll return 0.
    *plTotalSize = lTotalSize;

CleanUp:
	SafeReleaseNULL(pParentNode);
    SafeReleaseNULL(pItemList);
    SafeReleaseNULL(pCodebaseList);
    SafeReleaseNULL(pNodeItem);
    SafeReleaseNULL(pNodeInstall);
    SafeReleaseNULL(pNodeCodebase);
    SafeReleaseNULL(pNodeSize);
    return hr;
}


/////////////////////////////////////////////////////////////////////////////
// FindItemByIdentity()
//
// Input:
// pNodeIdentity - a pointer to an Identity Node to match against an Items
//                 identity in the Catalog. We will search through each item
//                 till we find a match with the supplied identity                 
//                 
// Output:
// phNodeItem    - Handle of the found Item
//                 
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::FindItemByIdentity(IXMLDOMNode* pNodeIdentity, HANDLE_NODE* phNodeItem)
{
    LOG_Block("FindItemByIdentity()");

    HRESULT hr = E_FAIL;

    IXMLDOMNode*    pNodeIdentityDes = NULL;
    
    *phNodeItem = HANDLE_NODE_INVALID;
    HANDLE_NODE hNodeItem = HANDLE_NODE_INVALID;
    HANDLE_NODELIST hNodeList = HANDLE_NODELIST_INVALID;

    hNodeList = FindFirstDOMNode(m_pDocCatalog, KEY_ITEM_SEARCH, &hNodeItem);
    if (HANDLE_NODELIST_INVALID != hNodeList)
    {
        CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_IDENTITY, &pNodeIdentityDes));
        if (AreNodesEqual(pNodeIdentityDes, pNodeIdentity))
        {
            *phNodeItem = hNodeItem;
            goto CleanUp;
        }

        SafeReleaseNULL(pNodeIdentityDes);
        SafeReleaseNULL(m_ppNodeArray[hNodeItem]);

        while (SUCCEEDED(FindNextDOMNode(hNodeList, &hNodeItem)))
        {
            CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_IDENTITY, &pNodeIdentityDes));
            if (AreNodesEqual(pNodeIdentityDes, pNodeIdentity))
            {
                *phNodeItem = hNodeItem;
                goto CleanUp;
            }
            SafeReleaseNULL(pNodeIdentityDes);
            SafeReleaseNULL(m_ppNodeArray[hNodeItem]);
        }
    }

CleanUp:
    CloseItemList(hNodeList);
    SafeReleaseNULL(pNodeIdentityDes);

    if (HANDLE_NODE_INVALID == *phNodeItem)
    {
        LOG_Error(_T("Can't find the matching Item Node in Catalog"));
        hr = E_FAIL;
    }
    return hr;
}


/*
/////////////////////////////////////////////////////////////////////////////
// IfSameIdentity()
//
// Return TRUE if the two <identity> nodes are identical. Return FALSE otherwise.
/////////////////////////////////////////////////////////////////////////////
BOOL CXmlCatalog::IfSameIdentity(IXMLDOMNode* pNodeIdentity1, IXMLDOMNode* pNodeIdentity2)
{
    LOG_Block("IfSameIdentity()");

    BOOL fResult = FALSE;
    BSTR bstrNameGUID = SysAllocString(L"guid");
    BSTR bstrNameIDName = SysAllocString(L"name");
    BSTR bstrNameIDPublisherName = SysAllocString(L"publisherName");
    BSTR bstrNameType = SysAllocString(L"type");
    BSTR bstrNameVersion = SysAllocString(L"version");
    BSTR bstrNameLanguage = SysAllocString(L"language");

    //
    // compare <guid> node
    //
    if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameGUID))
    {
        goto CleanUp;
    }

    //
    // compare <publisherName> node
    //
    if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameIDPublisherName))
    {
        goto CleanUp;
    }

    //
    // compare "name" attribute, this is a required attribute
    //
    if (!IfHasSameAttribute(pNodeIdentity1, pNodeIdentity2, bstrNameIDName, FALSE))
    {
        goto CleanUp;
    }

    //
    // compare <type> node
    //
    if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameType))
    {
        goto CleanUp;
    }

    //
    // compare <version> node, which really means "file version" here
    //
    if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameVersion))
    {
        goto CleanUp;
    }

    //
    // compare <language> node
    //
    if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameLanguage))
    {
        goto CleanUp;
    }

    fResult = TRUE;

CleanUp:
    SysFreeString(bstrNameGUID);
    SysFreeString(bstrNameIDName);
    SysFreeString(bstrNameIDPublisherName);
    SysFreeString(bstrNameType);
    SysFreeString(bstrNameVersion);
    SysFreeString(bstrNameLanguage);
    if (!fResult)
    {
        LOG_XML(_T("Different <identity>\'s found."));
    }
    else
    {
        LOG_XML(_T("Same <identity>\'s found."));
    }
    return fResult;
}
*/

/////////////////////////////////////////////////////////////////////////////
// GetItemLanguage()
//
// Get the Language Entity from the Item Identity
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetItemLanguage(HANDLE_NODE hNodeItem, BSTR* pbstrLanguage)
{
    HRESULT hr = S_OK;
    if (HANDLE_NODE_INVALID == hNodeItem || NULL == pbstrLanguage)
    {
        return E_INVALIDARG;
    }

    IXMLDOMNode *pNodeIdentity = NULL;
    IXMLDOMNode *pNodeLanguage = NULL;
    
    hr = FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_IDENTITY, &pNodeIdentity);
    if (FAILED(hr))
        goto CleanUp;

    hr = FindSingleDOMNode(pNodeIdentity, KEY_LANGUAGE, &pNodeLanguage);
    if (FAILED(hr))
        goto CleanUp;

    hr = GetText(pNodeLanguage, pbstrLanguage);
    if (FAILED(hr))
        goto CleanUp;

CleanUp:
    SafeReleaseNULL(pNodeLanguage);
    SafeReleaseNULL(pNodeIdentity);
    return hr;
}

/////////////////////////////////////////////////////////////////////////////
// GetCorpItemPlatformStr()
//
// Get the Simplified Platform String for an Item (uses the first available platform element)
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlCatalog::GetCorpItemPlatformStr(HANDLE_NODE hNodeItem, BSTR* pbstrPlatformStr)
{
    HRESULT hr = S_OK;
    if (HANDLE_NODE_INVALID == hNodeItem || NULL == pbstrPlatformStr)
    {
        return E_INVALIDARG;
    }

    IXMLDOMNode *pNodePlatform = NULL;
    IXMLDOMNode *pNodePlatformArchitecture = NULL;
    IXMLDOMNode *pNodePlatformVersion = NULL;
    BSTR bstrPlatformName = NULL;
    BSTR bstrArchitecture = NULL;
    int iMajor = 0;
    int iMinor = 0;


    hr = FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_PLATFORM, &pNodePlatform);
    if (FAILED(hr))
        goto CleanUp;

    hr = GetAttribute(pNodePlatform, KEY_NAME, &bstrPlatformName);
    if (FAILED(hr))
        goto CleanUp;

    hr = FindSingleDOMNode(pNodePlatform, KEY_PROCESSORARCHITECTURE, &pNodePlatformArchitecture);
    if (FAILED(hr))
        goto CleanUp;

    hr = FindSingleDOMNode(pNodePlatform, KEY_VERSION, &pNodePlatformVersion);
    if (FAILED(hr))
        goto CleanUp;

	if (NULL != bstrPlatformName && 0 != SysStringLen(bstrPlatformName))
    {
		if (CSTR_EQUAL == CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE,
			bstrPlatformName, -1, L"VER_PLATFORM_WIN32_NT", -1))
		{
			// this is an NT platform
			hr = GetAttribute(pNodePlatformVersion, KEY_MAJOR, &iMajor);
			if (FAILED(hr))
				goto CleanUp;

			if (4 == iMajor)
			{
				// WinNT4
				*pbstrPlatformStr = SysAllocString(CORP_PLATFORM_DIR_NT4);
			}
			else // 5 == iMajor
			{
				hr = GetAttribute(pNodePlatformVersion, KEY_MINOR, &iMinor);
				if (FAILED(hr))
					goto CleanUp;

				if (iMinor > 0)
				{
					hr = GetText(pNodePlatformArchitecture, &bstrArchitecture);
					if (FAILED(hr))
						goto CleanUp;
					// whistler
					if (CSTR_EQUAL == CompareStringW(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE,
						bstrArchitecture, -1, L"x86", -1))
					{
						// x86WinXP
						*pbstrPlatformStr = SysAllocString(CORP_PLATFORM_DIR_X86WHI);
					}
					else
					{
						// ia64WinXP
						*pbstrPlatformStr = SysAllocString(CORP_PLATFROM_DIR_IA64WHI);
					}
				}
				else
				{
					// x86Win2k
					*pbstrPlatformStr = SysAllocString(CORP_PLATFORM_DIR_NT5);
				}
			}
		}
		else // VER_PLATFORM_WIN32_WINDOWS
		{
			// this is a Win9x platform
			hr = GetAttribute(pNodePlatformVersion, KEY_MINOR, &iMinor);
			if (FAILED(hr))
				goto CleanUp;

			if (iMinor >= 90)
			{
				// x86WinME
				*pbstrPlatformStr = SysAllocString(CORP_PLATFORM_DIR_WINME);
			}
			else if (iMinor > 0 && iMinor < 90)
			{
				// x86Win98
				*pbstrPlatformStr = SysAllocString(CORP_PLATFORM_DIR_W98);
			}
			else
			{
				// x86Win95
				*pbstrPlatformStr = SysAllocString(CORP_PLATFORM_DIR_W95);
			}
		}
	}

CleanUp:
    SysFreeString(bstrPlatformName);
    SysFreeString(bstrArchitecture);
    SafeReleaseNULL(pNodePlatformVersion);
    SafeReleaseNULL(pNodePlatformArchitecture);
    SafeReleaseNULL(pNodePlatform);
    return hr;
}




/////////////////////////////////////////////////////////////////////////////
// CXmlItems

/////////////////////////////////////////////////////////////////////////////
// Constructor
//
// Create IXMLDOMDocument* for Items; this is for write only
/////////////////////////////////////////////////////////////////////////////
CXmlItems::CXmlItems()
 : m_pDocItems(NULL),
   m_pNodeItems(NULL)
{
    LOG_Block("CXmlItems()");

	Init();
}


/////////////////////////////////////////////////////////////////////////////
// Constructor
//
// Create IXMLDOMDocument* for Items; take TRUE for read, FALSE for write
/////////////////////////////////////////////////////////////////////////////
CXmlItems::CXmlItems(BOOL fRead)
 : m_pDocItems(NULL),
   m_pNodeItems(NULL)
{
    LOG_Block("CXmlItems(BOOL fRead)");

	//
	// for writing Items only
	//
	if (!fRead)
	{
		Init();
	}
}


/////////////////////////////////////////////////////////////////////////////
//
// Initialize XML DOC node pointers before writing
/////////////////////////////////////////////////////////////////////////////
void CXmlItems::Init()
{
	LOG_Block("Init()");

 	HRESULT hr = CoCreateInstance(CLSID_DOMDocument,
								  NULL,
								  CLSCTX_INPROC_SERVER,
								  IID_IXMLDOMDocument,
								  (void **) &m_pDocItems);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	else
	{
		IXMLDOMNode*	pNodeXML = NULL;
		BSTR bstrNameSpaceSchema = NULL;

		//
		// create the <?xml version="1.0"?> node
		//
		pNodeXML = CreateDOMNode(m_pDocItems, NODE_PROCESSING_INSTRUCTION, KEY_XML);
		if (NULL == pNodeXML) goto CleanUp;

		CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, pNodeXML));

		//
		// process the iuident.txt to find the Items schema path
		//
		TCHAR szIUDir[MAX_PATH];
		TCHAR szIdentFile[MAX_PATH];
		LPTSTR pszItemsSchema = NULL;
		LPTSTR pszNameSpaceSchema = NULL;

		pszItemsSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
		if (NULL == pszItemsSchema)
		{
			LOG_ErrorMsg(E_OUTOFMEMORY);
			goto CleanUp;
		}
		pszNameSpaceSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
		if (NULL == pszNameSpaceSchema)
		{
			LOG_ErrorMsg(E_OUTOFMEMORY);
			goto CleanUp;
		}
	
		GetIndustryUpdateDirectory(szIUDir);
		hr = PathCchCombine(szIdentFile, ARRAYSIZE(szIdentFile), szIUDir, IDENTTXT);
		if (FAILED(hr))
		{
			LOG_ErrorMsg(hr);
			goto CleanUp;
		}

		GetPrivateProfileString(IDENT_IUSCHEMA,
								IDENT_IUSCHEMA_ITEMS,
								_T(""),
								pszItemsSchema,
								INTERNET_MAX_URL_LENGTH,
								szIdentFile);

		if ('\0' == pszItemsSchema[0])
		{
			// no Items schema path specified in iuident.txt
			LOG_Error(_T("No schema path specified in iuident.txt for Items"));
			goto CleanUp;
		}
		
		hr = StringCchPrintfEx(pszNameSpaceSchema, INTERNET_MAX_URL_LENGTH, NULL, NULL, MISTSAFE_STRING_FLAGS,
		                       _T("x-schema:%s"), pszItemsSchema);
		if (FAILED(hr))
		{
			LOG_ErrorMsg(hr);
			goto CleanUp;
		}

		bstrNameSpaceSchema = T2BSTR(pszNameSpaceSchema);

		//
		// create the <items> node with the path of the schema
		//
		m_pNodeItems = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_ITEMS, bstrNameSpaceSchema);
		if (NULL == m_pNodeItems) goto CleanUp;
		
		CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, m_pNodeItems));

CleanUp:
		SafeReleaseNULL(pNodeXML);
		SysFreeString(bstrNameSpaceSchema);
		SafeHeapFree(pszItemsSchema);
		SafeHeapFree(pszNameSpaceSchema);
	}
}


/////////////////////////////////////////////////////////////////////////////
// Destructor
//
// Release IXMLDOMDocument* for Items
/////////////////////////////////////////////////////////////////////////////
CXmlItems::~CXmlItems()
{
	SafeReleaseNULL(m_pNodeItems);
	SafeReleaseNULL(m_pDocItems);
}


/////////////////////////////////////////////////////////////////////////////
// Clear()
//
// Reset IXMLDOMDocument* for Items
/////////////////////////////////////////////////////////////////////////////
void CXmlItems::Clear()
{
	SafeReleaseNULL(m_pNodeItems);
	SafeReleaseNULL(m_pDocItems);
}


/////////////////////////////////////////////////////////////////////////////
// LoadXMLDocument()
//
// Load an XML Document from string
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::LoadXMLDocument(BSTR bstrXml)
{
	LOG_Block("LoadXMLDocument()");
	SafeReleaseNULL(m_pDocItems);
	HRESULT hr = LoadXMLDoc(bstrXml, &m_pDocItems);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	return hr;
}

/////////////////////////////////////////////////////////////////////////////
// LoadXMLDocumentFile()
//
// Load an XML Document from the specified file
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::LoadXMLDocumentFile(BSTR bstrFilePath)
{
	LOG_Block("LoadXMLDocumentFile()");
	SafeReleaseNULL(m_pDocItems);
	HRESULT hr = LoadDocument(bstrFilePath, &m_pDocItems);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// SaveXMLDocument()
//
// Save an XML Document to the specified location
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::SaveXMLDocument(BSTR bstrFilePath)
{
	LOG_Block("SaveXMLDocument()");
	HRESULT hr = SaveDocument(m_pDocItems, bstrFilePath);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddGlobalErrorCodeIfNoItems()
//
// Add the errorCode attribute for <items> if there's no <itemStatus> child node
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CXmlItems::AddGlobalErrorCodeIfNoItems(DWORD dwErrorCode)
{
	LOG_Block("AddGlobalErrorCodeIfNoItems()");

	HRESULT			hr = S_OK;
	IXMLDOMNode*	pNodeItem = NULL;
	HANDLE_NODE		hNodeItemStatus = HANDLE_NODE_INVALID;

	FindFirstDOMNode(m_pDocItems, KEY_ITEM_ITEMSTATUS, &hNodeItemStatus);
	if (HANDLE_NODE_INVALID == hNodeItemStatus)
	{
		//
		// set the "errorCode" attribute
		//
		FindFirstDOMNode(m_pDocItems, KEY_ITEMS, &pNodeItem);
		if (NULL != pNodeItem)
		{
			hr = SetAttribute(pNodeItem, KEY_ERRORCODE, dwErrorCode);
		}
	}
	
	SafeReleaseNULL(pNodeItem);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetFirstItem()
//
// Find the first item in Items xml doc
/////////////////////////////////////////////////////////////////////////////
HANDLE_NODELIST CXmlItems::GetFirstItem(HANDLE_NODE* phNodeItem)
{
	LOG_Block("GetFirstItem()");

	HANDLE_NODELIST hNodeListItem = FindFirstDOMNode(m_pDocItems, KEY_ITEM_ITEMSTATUS, phNodeItem);
	
	return hNodeListItem;
}

	
/////////////////////////////////////////////////////////////////////////////
// GetNextItem()
//
// Find the next item in Items xml doc
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::GetNextItem(HANDLE_NODELIST hNodeListItem, HANDLE_NODE* phNodeItem)
{
    LOG_Block("GetNextItem()");

	return FindNextDOMNode(hNodeListItem, phNodeItem);
}

	
/////////////////////////////////////////////////////////////////////////////
// CloseItemList()
//
// Release the item nodelist
/////////////////////////////////////////////////////////////////////////////
void CXmlItems::CloseItemList(HANDLE_NODELIST hNodeListItem)
{
	SafeFindCloseHandle(hNodeListItem);
}


/////////////////////////////////////////////////////////////////////////////
// GetItemDownloadPath()
//
// Retrieve the download path of the given item
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::GetItemDownloadPath(HANDLE_NODE hNodeItem, BSTR* pbstrDownloadPath)
{
    LOG_Block("GetItemDownloadPath()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeDownloadPath = NULL;

	CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_DOWNLOADPATH, &pNodeDownloadPath));
	CleanUpIfFailedAndSetHrMsg(GetText(pNodeDownloadPath, pbstrDownloadPath));

CleanUp:
	SafeReleaseNULL(pNodeDownloadPath);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetItemDownloadPath()
//
// Retrieve the download path of the given item in catalog
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::GetItemDownloadPath(CXmlCatalog* pCatalog, HANDLE_NODE hNodeItem, BSTR* pbstrDownloadPath)
{
    LOG_Block("GetItemDownloadPath() for an item in catalog");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeItem = NULL;
	IXMLDOMNode*	pNodeDownloadPath = NULL;

	HANDLE_NODE	hNodeItemsItem = HANDLE_NODE_INVALID;

	if (NULL != (pNodeItem = pCatalog->GetDOMNodebyHandle(hNodeItem)))
	{
		hr = FindItem(pNodeItem, &hNodeItemsItem, TRUE);
	}
	else
	{
		LOG_Error(_T("Can't retrieve valid item node from catalog xml"));
		hr = E_FAIL;
		goto CleanUp;
	}
	
	if (FAILED(hr) || HANDLE_NODE_INVALID == hNodeItemsItem)
	{
		LOG_Error(_T("Can't find item from Items xml"));
		goto CleanUp;
	}
	
	CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItemsItem], KEY_DOWNLOADPATH, &pNodeDownloadPath));
	CleanUpIfFailedAndSetHrMsg(GetText(pNodeDownloadPath, pbstrDownloadPath));

CleanUp:
	SafeReleaseNULL(pNodeDownloadPath);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// CloseItem()
//
// Release the item node
/////////////////////////////////////////////////////////////////////////////
void CXmlItems::CloseItem(HANDLE_NODE hNodeItem)
{
	SafeCloseHandleNode(hNodeItem);
}


/////////////////////////////////////////////////////////////////////////////
// FindItem()
//
// Input:
// pNodeItem	- the <itemStatus> node of the install items xml; we need
//                to find the corresponding <itemStatus> node in the existing 
//                items xml with the identical <identity>, <platform> and 
//                <client> nodes.
// Output:
// phNodeItem	- the handle we pass back to differentiate different
//				  <itemStatus> node in the existing items xml
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::FindItem(IXMLDOMNode* pNodeItem, HANDLE_NODE* phNodeItem, BOOL fIdentityOnly /*= FALSE*/)
{
	LOG_Block("FindItem()");

	HRESULT		hr1, hr2, hr = E_FAIL;

	IXMLDOMNode*	pNodeIdentitySrc = NULL;
	IXMLDOMNode*	pNodeIdentityDes = NULL;
	IXMLDOMNode*	pNodePlatformSrc = NULL;
	IXMLDOMNode*	pNodePlatformDes = NULL;
	IXMLDOMNode*	pNodeClientSrc = NULL;
	IXMLDOMNode*	pNodeClientDes = NULL;

	*phNodeItem = HANDLE_NODE_INVALID;
	HANDLE_NODE	hNodeItem = HANDLE_NODE_INVALID;
	HANDLE_NODELIST	hNodeList = HANDLE_NODELIST_INVALID;

	hNodeList = FindFirstDOMNode(m_pDocItems, KEY_ITEM_ITEMSTATUS, &hNodeItem);
	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_IDENTITY, &pNodeIdentityDes));
		CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_IDENTITY, &pNodeIdentitySrc));
		if (AreNodesEqual(pNodeIdentityDes, pNodeIdentitySrc))
		{
			if (fIdentityOnly)
			{
				//
				// we now found the match
				//
				*phNodeItem = hNodeItem;
				goto CleanUp;
			}
			else
			{
				hr1 = FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_PLATFORM, &pNodePlatformDes);
				hr2 = FindSingleDOMNode(pNodeItem, KEY_PLATFORM, &pNodePlatformSrc);
				if ((FAILED(hr1) && FAILED(hr2)) ||
					(SUCCEEDED(hr1) && SUCCEEDED(hr2) && AreNodesEqual(pNodePlatformDes, pNodePlatformSrc)))
				{
					CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_CLIENT, &pNodeClientDes));
					CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_CLIENT, &pNodeClientSrc));
					if (AreNodesEqual(pNodeClientDes, pNodeClientSrc))
					{
						//
						// we now found the match
						//
						*phNodeItem = hNodeItem;
						goto CleanUp;
					}
				}
			}
		}
		SafeReleaseNULL(pNodeClientDes);
		SafeReleaseNULL(pNodeClientSrc);
		SafeReleaseNULL(pNodePlatformDes);
		SafeReleaseNULL(pNodePlatformSrc);
		SafeReleaseNULL(pNodeIdentityDes);
		SafeReleaseNULL(pNodeIdentitySrc);
		SafeReleaseNULL(m_ppNodeArray[hNodeItem]);
		while (SUCCEEDED(FindNextDOMNode(hNodeList, &hNodeItem)))
		{
			CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_IDENTITY, &pNodeIdentityDes));
			CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_IDENTITY, &pNodeIdentitySrc));
			if (AreNodesEqual(pNodeIdentityDes, pNodeIdentitySrc))
			{
				if (fIdentityOnly)
				{
					//
					// we now found the match
					//
					*phNodeItem = hNodeItem;
					goto CleanUp;
				}
				else
				{
					hr1 = FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_PLATFORM, &pNodePlatformDes);
					hr2 = FindSingleDOMNode(pNodeItem, KEY_PLATFORM, &pNodePlatformSrc);
					if ((FAILED(hr1) && FAILED(hr2)) ||
						(SUCCEEDED(hr1) && SUCCEEDED(hr2) && AreNodesEqual(pNodePlatformDes, pNodePlatformSrc)))
					{
						CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_CLIENT, &pNodeClientDes));
						CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_CLIENT, &pNodeClientSrc));
						if (AreNodesEqual(pNodeClientDes, pNodeClientSrc))
						{
							//
							// we now found the match
							//
							*phNodeItem = hNodeItem;
							break;
						}
					}
				}
			}
			SafeReleaseNULL(pNodeClientDes);
			SafeReleaseNULL(pNodeClientSrc);
			SafeReleaseNULL(pNodePlatformDes);
			SafeReleaseNULL(pNodePlatformSrc);
			SafeReleaseNULL(pNodeIdentityDes);
			SafeReleaseNULL(pNodeIdentitySrc);
			SafeReleaseNULL(m_ppNodeArray[hNodeItem]);
		}
	}

CleanUp:
	CloseItemList(hNodeList);
	SafeReleaseNULL(pNodeClientDes);
	SafeReleaseNULL(pNodeClientSrc);
	SafeReleaseNULL(pNodePlatformDes);
	SafeReleaseNULL(pNodePlatformSrc);
	SafeReleaseNULL(pNodeIdentityDes);
	SafeReleaseNULL(pNodeIdentitySrc);
	if (HANDLE_NODE_INVALID == *phNodeItem)
	{
		LOG_Error(_T("Can't find the identical item node in existing Items xml"));
		hr = E_FAIL;
	}
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// FindItem()
//
// Input:
// pCatalog		- the pointer to the CXmlCatalog object
// hNodeItem	- the handle of the <item> node of the catalog xml; we need
//                to find the corresponding <itemStatus> node in the existing 
//                items xml with the identical <identity>, <platform> and 
//                <client> nodes.
// Output:
// phNodeItem	- the handle we pass back to differentiate different
//				  <itemStatus> node in items xml
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::FindItem(CXmlCatalog* pCatalog,
							HANDLE_NODE hNodeItem,
							HANDLE_NODE* phNodeItem)
{
	LOG_Block("FindItem() by handle");

	IXMLDOMNode*	pNode = NULL;

	if (NULL != (pNode = pCatalog->GetDOMNodebyHandle(hNodeItem)))
	{
		return FindItem(pNode, phNodeItem, TRUE);
	}
	LOG_Error(_T("Can't retrieve valid item node from catalog xml"));
	return E_FAIL;
}

	
/*
/////////////////////////////////////////////////////////////////////////////
// IfSameClientInfo()
//
// Return TRUE if the two <client> nodes are identical. Return FALSE otherwise.
/////////////////////////////////////////////////////////////////////////////
BOOL CXmlItems::IfSameClientInfo(IXMLDOMNode* pNodeClient1, IXMLDOMNode* pNodeClient2)
{
    LOG_Block("IfSameClientInfo()");

	BSTR bstrText1 = NULL, bstrText2 = NULL;
	BOOL fResult = FALSE;

	GetText(pNodeClient1, &bstrText1);
	GetText(pNodeClient2, &bstrText2);

	if (NULL != bstrText1 && NULL != bstrText2)
	{
		fResult = CompareBSTRsEqual(bstrText1, bstrText2);
	}

	SysFreeString(bstrText1);
	SysFreeString(bstrText2);
	if (!fResult)
	{
		LOG_XML(_T("Different <client>\'s found."));
	}
	else
	{
		LOG_XML(_T("Same <client>\'s found."));
	}
	return fResult;
}


/////////////////////////////////////////////////////////////////////////////
// IfSameIdentity()
//
// Return TRUE if the two <identity> nodes are identical. Return FALSE otherwise.
/////////////////////////////////////////////////////////////////////////////
BOOL CXmlItems::IfSameIdentity(IXMLDOMNode* pNodeIdentity1, IXMLDOMNode* pNodeIdentity2)
{
    LOG_Block("IfSameIdentity()");

	BOOL fResult = FALSE;
	BSTR bstrNameGUID = SysAllocString(L"guid");
	BSTR bstrNameIDName = SysAllocString(L"name");
	BSTR bstrNameIDPublisherName = SysAllocString(L"publisherName");
	BSTR bstrNameType = SysAllocString(L"type");
	BSTR bstrNameVersion = SysAllocString(L"version");
	BSTR bstrNameLanguage = SysAllocString(L"language");

	//
	// compare <guid> node
	//
	if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameGUID))
	{
		goto CleanUp;
	}

	//
	// compare <publisherName> node
	//
	if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameIDPublisherName))
	{
		goto CleanUp;
	}

	//
	// compare "name" attribute, this is a required attribute
	//
	if (!IfHasSameAttribute(pNodeIdentity1, pNodeIdentity2, bstrNameIDName, FALSE))
	{
		goto CleanUp;
	}

	//
	// compare <type> node
	//
	if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameType))
	{
		goto CleanUp;
	}

	//
	// compare <version> node, which really means "file version" here
	//
	if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameVersion))
	{
		goto CleanUp;
	}

	//
	// compare <language> node
	//
	if (!IfHasSameElement(pNodeIdentity1, pNodeIdentity2, bstrNameLanguage))
	{
		goto CleanUp;
	}

	fResult = TRUE;

CleanUp:
	SysFreeString(bstrNameGUID);
	SysFreeString(bstrNameIDName);
	SysFreeString(bstrNameIDPublisherName);
	SysFreeString(bstrNameType);
	SysFreeString(bstrNameVersion);
	SysFreeString(bstrNameLanguage);
	if (!fResult)
	{
		LOG_XML(_T("Different <identity>\'s found."));
	}
	else
	{
		LOG_XML(_T("Same <identity>\'s found."));
	}
	return fResult;
}


/////////////////////////////////////////////////////////////////////////////
// IfSamePlatform()
//
// Return TRUE if the two <platform> nodes are identical. Return FALSE otherwise.
/////////////////////////////////////////////////////////////////////////////
BOOL CXmlItems::IfSamePlatform(IXMLDOMNode* pNodePlatform1, IXMLDOMNode* pNodePlatform2)
{
    LOG_Block("IfSamePlatform()");

	HRESULT		hr1 = S_OK, hr2 = S_OK;
	BSTR		bstrPlatform1 = NULL, bstrPlatform2 = NULL;
	BOOL		fResult = FALSE;

	hr1 = pNodePlatform1->get_xml(&bstrPlatform1);
	hr2 = pNodePlatform2->get_xml(&bstrPlatform2);

	if (FAILED(hr1) || FAILED(hr2) || !CompareBSTRsEqual(bstrPlatform1, bstrPlatform2))
		goto CleanUp;

	fResult = TRUE;

CleanUp:
	SysFreeString(bstrPlatform1);
	SysFreeString(bstrPlatform2);
	return fResult;
}
*/


/////////////////////////////////////////////////////////////////////////////
// MergeItemDownloaded()
//
// Insert items with download history into existing history (insert in front)
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::MergeItemDownloaded(CXmlItems *pHistoryDownload)
{
	LOG_Block("MergeItemDownloaded()");

	HRESULT		hr = S_OK;

	IXMLDOMNode*		pNodeItem = NULL;
	IXMLDOMNode*		pNodeItemNew = NULL;
	IXMLDOMNode*		pNodeItemRef = NULL;
	IXMLDOMNode*		pNodeXML = NULL;
	BSTR bstrNameSpaceSchema = NULL;
	LPTSTR pszItemsSchema = NULL;
	LPTSTR pszNameSpaceSchema = NULL;

	HANDLE_NODE	hNodeItem = HANDLE_NODE_INVALID;
	HANDLE_NODELIST	hNodeListItem = HANDLE_NODELIST_INVALID;

	hNodeListItem = pHistoryDownload->GetFirstItem(&hNodeItem);
	if (HANDLE_NODELIST_INVALID != hNodeListItem)
	{
		//
		// if this is the first time writing history
		// (e.g. the log file does not exist yet), do
		// initialization for m_pDocItems here...
		//
		if (NULL == m_pDocItems)
		{

 			hr = CoCreateInstance(CLSID_DOMDocument,
										  NULL,
										  CLSCTX_INPROC_SERVER,
										  IID_IXMLDOMDocument,
										  (void **) &m_pDocItems);
			if (FAILED(hr))
			{
				LOG_ErrorMsg(hr);
			}
			else
			{
				//
				// create the <?xml version="1.0"?> node
				//
				pNodeXML = CreateDOMNode(m_pDocItems, NODE_PROCESSING_INSTRUCTION, KEY_XML);
				if (NULL == pNodeXML) goto CleanUp;

				CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, pNodeXML));

				//
				// process the iuident.txt to find the Items schema path
				//
				TCHAR szIUDir[MAX_PATH];
				TCHAR szIdentFile[MAX_PATH];

				pszItemsSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
				if (NULL == pszItemsSchema)
				{
					hr = E_OUTOFMEMORY;
					LOG_ErrorMsg(hr);
					goto CleanUp;
				}
				pszNameSpaceSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
				if (NULL == pszNameSpaceSchema)
				{
					hr = E_OUTOFMEMORY;
					LOG_ErrorMsg(hr);
					goto CleanUp;
				}
	
				GetIndustryUpdateDirectory(szIUDir);
        		hr = PathCchCombine(szIdentFile, ARRAYSIZE(szIdentFile), szIUDir, IDENTTXT);
        		if (FAILED(hr))
        		{
        			LOG_ErrorMsg(hr);
        			goto CleanUp;
        		}

				GetPrivateProfileString(IDENT_IUSCHEMA,
										IDENT_IUSCHEMA_ITEMS,
										_T(""),
										pszItemsSchema,
										INTERNET_MAX_URL_LENGTH,
										szIdentFile);

				if ('\0' == pszItemsSchema[0])
				{
					// no Items schema path specified in iuident.txt
					LOG_Error(_T("No schema path specified in iuident.txt for Items"));
					goto CleanUp;
				}
				
        		hr = StringCchPrintfEx(pszNameSpaceSchema, INTERNET_MAX_URL_LENGTH, NULL, NULL, MISTSAFE_STRING_FLAGS,
        		                       _T("x-schema:%s"), pszItemsSchema);
        		if (FAILED(hr))
        		{
        			LOG_ErrorMsg(hr);
        			goto CleanUp;
        		}
				
				bstrNameSpaceSchema = T2BSTR(pszNameSpaceSchema);

				//
				// create the <items> node with the path of the schema
				//
				m_pNodeItems = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_ITEMS, bstrNameSpaceSchema);
				if (NULL == m_pNodeItems) goto CleanUp;
				
				CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, m_pNodeItems));
			}
		}
		else
		{
		    SafeReleaseNULL(m_pNodeItems);
			FindSingleDOMNode(m_pDocItems, KEY_ITEMS, &m_pNodeItems);
		}

		if (NULL != m_pNodeItems)
		{
			if (NULL != (pNodeItem = pHistoryDownload->GetDOMNodebyHandle(hNodeItem)))
			{
				CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeItem, m_pDocItems, &pNodeItemNew));
				CleanUpIfFailedAndSetHrMsg(m_pNodeItems->get_firstChild(&pNodeItemRef));
				CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, pNodeItemNew, pNodeItemRef));
//				SafeReleaseNULL(pNodeItem);
				SafeReleaseNULL(pNodeItemNew);
				SafeReleaseNULL(pNodeItemRef);
			}
			while (SUCCEEDED(pHistoryDownload->GetNextItem(hNodeListItem, &hNodeItem)))
			{
				if (NULL != (pNodeItem = pHistoryDownload->GetDOMNodebyHandle(hNodeItem)))
				{
					CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeItem, m_pDocItems, &pNodeItemNew));
					CleanUpIfFailedAndSetHrMsg(m_pNodeItems->get_firstChild(&pNodeItemRef));
					CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, pNodeItemNew, pNodeItemRef));
//					SafeReleaseNULL(pNodeItem);
					SafeReleaseNULL(pNodeItemNew);
					SafeReleaseNULL(pNodeItemRef);
				}
			}
		}
	}

CleanUp:
	pHistoryDownload->CloseItemList(hNodeListItem);
//	SafeReleaseNULL(pNodeItem);
	SafeReleaseNULL(pNodeItemNew);
	SafeReleaseNULL(pNodeItemRef);
	SafeReleaseNULL(pNodeXML);
	SysFreeString(bstrNameSpaceSchema);
	SafeHeapFree(pszItemsSchema);
	SafeHeapFree(pszNameSpaceSchema);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// UpdateItemInstalled()
//
// Update items with installation history in existing history
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::UpdateItemInstalled(CXmlItems *pHistoryInstall)
{
	LOG_Block("UpdateItemInstalled()");

	USES_IU_CONVERSION;

	HRESULT		hr = S_OK;

	IXMLDOMNode*		pNodeItem = NULL;
	IXMLDOMNode*		pNodeItemNew = NULL;
	IXMLDOMNode*		pNodeItemRef = NULL;
	IXMLDOMNode*		pNodeItemExist = NULL;
	IXMLDOMNode*		pNodeInstall = NULL;
	IXMLDOMNode*		pNodeInstallExist = NULL;
	IXMLDOMNode*		pNodeInstallNew = NULL;
	IXMLDOMNode*		pNodeInstallOut = NULL;
	IXMLDOMNode*		pNodeXML = NULL;
	BSTR bstrInstallStatusExist = NULL;
	BSTR bstrInstallStatusNew = NULL;
	BSTR bstrTimeStamp = NULL;
	BSTR bstrNameSpaceSchema = NULL;
	LPTSTR pszItemsSchema = NULL;
	LPTSTR pszNameSpaceSchema = NULL;

	HANDLE_NODE	hNodeItem = HANDLE_NODE_INVALID;
	HANDLE_NODE	hNodeItemExist = HANDLE_NODE_INVALID;
	HANDLE_NODELIST	hNodeListItem = HANDLE_NODELIST_INVALID;

	hNodeListItem = pHistoryInstall->GetFirstItem(&hNodeItem);
	if (HANDLE_NODELIST_INVALID != hNodeListItem)
	{
		//
		// if this is the first time writing history
		// (e.g. the log file does not exist yet), do
		// initialization for m_pDocItems here...
		//
		if (NULL == m_pDocItems)
		{

 			hr = CoCreateInstance(CLSID_DOMDocument,
										  NULL,
										  CLSCTX_INPROC_SERVER,
										  IID_IXMLDOMDocument,
										  (void **) &m_pDocItems);
			if (FAILED(hr))
			{
				LOG_ErrorMsg(hr);
			}
			else
			{
				//
				// create the <?xml version="1.0"?> node
				//
				pNodeXML = CreateDOMNode(m_pDocItems, NODE_PROCESSING_INSTRUCTION, KEY_XML);
				if (NULL == pNodeXML) goto CleanUp;

				CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, pNodeXML));

				//
				// process the iuident.txt to find the Items schema path
				//
				TCHAR szIUDir[MAX_PATH];
				TCHAR szIdentFile[MAX_PATH];

				pszItemsSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
				if (NULL == pszItemsSchema)
				{
					hr = E_OUTOFMEMORY;
					LOG_ErrorMsg(hr);
					goto CleanUp;
				}
				pszNameSpaceSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
				if (NULL == pszNameSpaceSchema)
				{
					hr = E_OUTOFMEMORY;
					LOG_ErrorMsg(hr);
					goto CleanUp;
				}
	
				GetIndustryUpdateDirectory(szIUDir);
        		hr = PathCchCombine(szIdentFile, ARRAYSIZE(szIdentFile), szIUDir, IDENTTXT);
        		if (FAILED(hr))
        		{
        			LOG_ErrorMsg(hr);
        			goto CleanUp;
        		}

				GetPrivateProfileString(IDENT_IUSCHEMA,
										IDENT_IUSCHEMA_ITEMS,
										_T(""),
										pszItemsSchema,
										INTERNET_MAX_URL_LENGTH,
										szIdentFile);

				if ('\0' == pszItemsSchema[0])
				{
					// no Items schema path specified in iuident.txt
					LOG_Error(_T("No schema path specified in iuident.txt for Items"));
					goto CleanUp;
				}

        		hr = StringCchPrintfEx(pszNameSpaceSchema, INTERNET_MAX_URL_LENGTH, NULL, NULL, MISTSAFE_STRING_FLAGS,
        		                       _T("x-schema:%s"), pszItemsSchema);
        		if (FAILED(hr))
        		{
        			LOG_ErrorMsg(hr);
        			goto CleanUp;
        		}
				
				bstrNameSpaceSchema = T2BSTR(pszNameSpaceSchema);

				//
				// create the <items> node with the path of the schema
				//
				m_pNodeItems = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_ITEMS, bstrNameSpaceSchema);
				if (NULL == m_pNodeItems) goto CleanUp;
				
				CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, m_pNodeItems));
			}
		}
		else
		{
		    SafeReleaseNULL(m_pNodeItems);
			CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_pDocItems, KEY_ITEMS, &m_pNodeItems));
		}

		if (NULL != (pNodeItem = pHistoryInstall->GetDOMNodebyHandle(hNodeItem)))
		{
			if (SUCCEEDED(FindItem(pNodeItem, &hNodeItemExist)))
			{
				//
				// successfully found the match
				//
				if (NULL != (pNodeItemExist = GetDOMNodebyHandle(hNodeItemExist)))
				{
					CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_INSTALLSTATUS, &pNodeInstall));			
					FindSingleDOMNode(pNodeItemExist, KEY_INSTALLSTATUS, &pNodeInstallExist);
					if (NULL != pNodeInstallExist)
					{
						//
						// found the item already with installStatus; now find out if we want to update
						// or append the installStatus
						//
						CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstallExist, KEY_VALUE, &bstrInstallStatusExist));
						CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstall, KEY_VALUE, &bstrInstallStatusNew));		
						if (CSTR_EQUAL == WUCompareStringI(OLE2T(bstrInstallStatusExist), _T("IN_PROGRESS")) &&
							CSTR_EQUAL != WUCompareStringI(OLE2T(bstrInstallStatusNew), _T("IN_PROGRESS")))
						{
							//
							// this entry is an exclusive item with "IN_PROGRESS" installStatus and we found its 
							// updated installStatus, we need to update its installStatus
							//
							LOG_Out(_T("Update the exclusive item's installStatus"));
							CleanUpIfFailedAndSetHrMsg(pNodeItemExist->removeChild(pNodeInstallExist, &pNodeInstallOut));
							CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeInstall, m_pDocItems, &pNodeInstallNew));
							CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeItemExist, pNodeInstallNew));

							CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeItem, KEY_TIMESTAMP, &bstrTimeStamp));
							CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeItemExist, KEY_TIMESTAMP, bstrTimeStamp));
							SafeSysFreeString(bstrTimeStamp);
						}
						else
						{							
							//
							// in this case we append a copy of this item with the new installStatus, since
							// this comes from a separate install operation
							//
							LOG_Out(_T("This item was installed again, add an entry of this item into history \
										for the new installation status only."));
							CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeItem, m_pDocItems, &pNodeItemNew));
							CleanUpIfFailedAndSetHrMsg(m_pNodeItems->get_firstChild(&pNodeItemRef));
							CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, pNodeItemNew, pNodeItemRef));
						}
						SafeSysFreeString(bstrInstallStatusExist);
						SafeSysFreeString(bstrInstallStatusNew);
					}
					else
					{
						//
						// found the item without installStatus, update the entry with the installStatus
						// and update the timeStamp with its installation timeStamp
						//
						CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeInstall, m_pDocItems, &pNodeInstallNew));
						CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeItemExist, pNodeInstallNew));

						CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeItem, KEY_TIMESTAMP, &bstrTimeStamp));
						CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeItemExist, KEY_TIMESTAMP, bstrTimeStamp));
						SafeSysFreeString(bstrTimeStamp);
					}
				}
			}
			else
			{
				//
				// no match found, this item was not downloaded through IU,
				// append this item with the install status
				//
				LOG_Out(_T("Can't find the downloaded item in existing history. This item was not downloaded \
							through IU. Add the item into history for installation status only."));
				CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeItem, m_pDocItems, &pNodeItemNew));
				CleanUpIfFailedAndSetHrMsg(m_pNodeItems->get_firstChild(&pNodeItemRef));
				CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, pNodeItemNew, pNodeItemRef));
			}
//			SafeReleaseNULL(pNodeItem);
//			SafeReleaseNULL(pNodeItemExist);
			SafeReleaseNULL(pNodeItemNew);
			SafeReleaseNULL(pNodeItemRef);
			SafeReleaseNULL(pNodeInstall);
			SafeReleaseNULL(pNodeInstallExist);
			SafeReleaseNULL(pNodeInstallNew);
			SafeReleaseNULL(pNodeInstallOut);
		}
		while (SUCCEEDED(pHistoryInstall->GetNextItem(hNodeListItem, &hNodeItem)))
		{
			if (NULL != (pNodeItem = pHistoryInstall->GetDOMNodebyHandle(hNodeItem)))
			{
				if (SUCCEEDED(FindItem(pNodeItem, &hNodeItemExist)))
				{
					//
					// successfully found the match
					//
					if (NULL != (pNodeItemExist = GetDOMNodebyHandle(hNodeItemExist)))
					{
						CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_INSTALLSTATUS, &pNodeInstall));			
						FindSingleDOMNode(pNodeItemExist, KEY_INSTALLSTATUS, &pNodeInstallExist);
						if (NULL != pNodeInstallExist)
						{
							//
							// found the item already with installStatus; now find out if we want to update
							// or append the installStatus
							//
							CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstallExist, KEY_VALUE, &bstrInstallStatusExist));
							CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeInstall, KEY_VALUE, &bstrInstallStatusNew));
							if (CSTR_EQUAL == WUCompareStringI(OLE2T(bstrInstallStatusExist), _T("IN_PROGRESS")) &&
								CSTR_EQUAL != WUCompareStringI(OLE2T(bstrInstallStatusNew), _T("IN_PROGRESS")))
							{
								//
								// this entry is an exclusive item with "IN_PROGRESS" installStatus and we found its 
								// updated installStatus, we need to update its installStatus
								//
								LOG_Out(_T("Update the exclusive item's installStatus"));
								CleanUpIfFailedAndSetHrMsg(pNodeItemExist->removeChild(pNodeInstallExist, &pNodeInstallOut));
								CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeInstall, m_pDocItems, &pNodeInstallNew));
								CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeItemExist, pNodeInstallNew));

								CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeItem, KEY_TIMESTAMP, &bstrTimeStamp));
								CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeItemExist, KEY_TIMESTAMP, bstrTimeStamp));
								SafeSysFreeString(bstrTimeStamp);
							}
							else
							{							
								//
								// in this case we append a copy of this item with the new installStatus, since
								// this comes from a separate install operation
								//
								LOG_Out(_T("This item was installed again, add an entry of this item into history \
											for the new installation status only."));
								CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeItem, m_pDocItems, &pNodeItemNew));
								CleanUpIfFailedAndSetHrMsg(m_pNodeItems->get_firstChild(&pNodeItemRef));
								CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, pNodeItemNew, pNodeItemRef));
							}
							SafeSysFreeString(bstrInstallStatusExist);
							SafeSysFreeString(bstrInstallStatusNew);
						}
						else
						{
							//
							// found the item without installStatus, update the entry with the installStatus
							// and update the timeStamp with its installation timeStamp
							//
							CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeInstall, m_pDocItems, &pNodeInstallNew));
							CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeItemExist, pNodeInstallNew));

							CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeItem, KEY_TIMESTAMP, &bstrTimeStamp));
							CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeItemExist, KEY_TIMESTAMP, bstrTimeStamp));
							SafeSysFreeString(bstrTimeStamp);
						}
					}
				}
				else
				{
					//
					// no match found, this item was not downloaded through IU,
					// append this item with the install status
					//
					LOG_Out(_T("Can't find the downloaded item in existing history. This item was not downloaded \
								through IU. Add the item into history for installation status only."));
					CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeItem, m_pDocItems, &pNodeItemNew));
					CleanUpIfFailedAndSetHrMsg(m_pNodeItems->get_firstChild(&pNodeItemRef));
					CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, pNodeItemNew, pNodeItemRef));
				}
//				SafeReleaseNULL(pNodeItem);
//				SafeReleaseNULL(pNodeItemExist);
				SafeReleaseNULL(pNodeItemNew);
				SafeReleaseNULL(pNodeItemRef);
				SafeReleaseNULL(pNodeInstall);
				SafeReleaseNULL(pNodeInstallExist);
				SafeReleaseNULL(pNodeInstallNew);
				SafeReleaseNULL(pNodeInstallOut);
			}
		}
	}

CleanUp:
	pHistoryInstall->CloseItemList(hNodeListItem);
//	SafeReleaseNULL(pNodeItem);
//	SafeReleaseNULL(pNodeItemExist);
	SafeReleaseNULL(pNodeItemNew);
	SafeReleaseNULL(pNodeItemRef);
	SafeReleaseNULL(pNodeInstall);
	SafeReleaseNULL(pNodeInstallExist);
	SafeReleaseNULL(pNodeInstallNew);
	SafeReleaseNULL(pNodeInstallOut);
	SafeReleaseNULL(pNodeXML);
	SysFreeString(bstrInstallStatusExist);
	SysFreeString(bstrInstallStatusNew);
	SysFreeString(bstrTimeStamp);
	SysFreeString(bstrNameSpaceSchema);
	SafeHeapFree(pszItemsSchema);
	SafeHeapFree(pszNameSpaceSchema);
	return hr;
}

	
/////////////////////////////////////////////////////////////////////////////
// UpdateItemInstallStatus()
//
// Update the install status of the given item
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::UpdateItemInstallStatus(HANDLE_NODE hNodeItem,
											 BSTR bstrValue,
											 INT iNeedsReboot /*= -1*/,
											 DWORD dwErrorCode /*= 0*/)
{
	LOG_Block("UpdateItemInstallStatus()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeInstallStatus = NULL;

	//
	// get the <installStatus> node
	//
	CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(m_ppNodeArray[hNodeItem], KEY_INSTALLSTATUS, &pNodeInstallStatus));

	//
	// set the "value" attribute
	//
	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeInstallStatus, KEY_VALUE, bstrValue));

	//
	// set the "needsReboot" attribute
	//
	if (-1 != iNeedsReboot)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeInstallStatus, KEY_NEEDSREBOOT, iNeedsReboot));
	}

	//
	// set the "errorCode" attribute
	//
	if (0 != dwErrorCode)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeInstallStatus, KEY_ERRORCODE, dwErrorCode));
	}

CleanUp:
	SafeReleaseNULL(pNodeInstallStatus);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddItem()
//
// Input:
// pNodeItem	- the <item> node of the catalog xml; we need to read
//				  <identity> node, <description> node and <platform> nodes
//                from it and write to the items xml.
// Output:
// phNodeItem	- the handle we pass back to differentiate different
//				  <itemStatus> node in items xml
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddItem(IXMLDOMNode* pNodeItem, HANDLE_NODE* phNodeItem)
{
	LOG_Block("AddItem()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeIndentity = NULL;
	IXMLDOMNode*	pNodeIndentityNew = NULL;
	IXMLDOMNode*	pNodeDescription = NULL;
	IXMLDOMNode*	pNodeDescriptionNew = NULL;
	IXMLDOMNode*	pNodePlatform = NULL;
	IXMLDOMNode*	pNodePlatformNew = NULL;

	HANDLE_NODELIST	hNodeList = HANDLE_NODELIST_INVALID;

	*phNodeItem = CreateDOMNodeWithHandle(m_pDocItems, NODE_ELEMENT, KEY_ITEMSTATUS);
	if (HANDLE_NODE_INVALID == *phNodeItem) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_pNodeItems, m_ppNodeArray[*phNodeItem]));

	hNodeList = FindFirstDOMNode(pNodeItem, KEY_IDENTITY, &pNodeIndentity);
	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		SafeFindCloseHandle(hNodeList);
		CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeIndentity, m_pDocItems, &pNodeIndentityNew));
		CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[*phNodeItem], pNodeIndentityNew));
	}

	hNodeList = FindFirstDOMNode(pNodeItem, KEY_DESCRIPTION, &pNodeDescription);
	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		SafeFindCloseHandle(hNodeList);
		CleanUpIfFailedAndSetHrMsg(CopyNode(pNodeDescription, m_pDocItems, &pNodeDescriptionNew));
		CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[*phNodeItem], pNodeDescriptionNew));
	}

	hNodeList = FindFirstDOMNode(pNodeItem, KEY_PLATFORM, &pNodePlatform);
	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		CleanUpIfFailedAndSetHrMsg(CopyNode(pNodePlatform, m_pDocItems, &pNodePlatformNew));
		CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[*phNodeItem], pNodePlatformNew));
		SafeReleaseNULL(pNodePlatform);
		SafeReleaseNULL(pNodePlatformNew);
		while (SUCCEEDED(FindNextDOMNode(hNodeList, &pNodePlatform)))
		{
			CleanUpIfFailedAndSetHrMsg(CopyNode(pNodePlatform, m_pDocItems, &pNodePlatformNew));
			CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[*phNodeItem], pNodePlatformNew));
			SafeReleaseNULL(pNodePlatform);
			SafeReleaseNULL(pNodePlatformNew);
		}
	}

CleanUp:
	if (HANDLE_NODELIST_INVALID != hNodeList)
	{
		SafeFindCloseHandle(hNodeList);
	}
	SafeReleaseNULL(pNodeIndentity);
	SafeReleaseNULL(pNodeIndentityNew);
	SafeReleaseNULL(pNodeDescription);
	SafeReleaseNULL(pNodeDescriptionNew);
	SafeReleaseNULL(pNodePlatform);
	SafeReleaseNULL(pNodePlatformNew);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddItem()
//
// Input:
// pCatalog		- the pointer to the CXmlCatalog object
// hNodeItem	- the handle of the <item> node of the catalog xml; we need
//				  to read <identity> node, <description> node and <platform>
//                nodes from it and write to the items xml.
// Output:
// phNodeItem	- the handle we pass back to differentiate different
//				  <itemStatus> node in items xml
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddItem(CXmlCatalog* pCatalog, HANDLE_NODE hNodeItem, HANDLE_NODE* phNodeItem)
{
	LOG_Block("AddItem() by handle");

	IXMLDOMNode*	pNode = NULL;

	if (NULL != (pNode = pCatalog->GetDOMNodebyHandle(hNodeItem)))
	{
		return AddItem(pNode, phNodeItem);
	}
	LOG_Error(_T("Can't retrieve valid item node from catalog xml"));
	return E_FAIL;
}


/////////////////////////////////////////////////////////////////////////////
// AddTimeStamp()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddTimeStamp(HANDLE_NODE hNodeItem)
{
	LOG_Block("AddTimeStamp()");

	USES_IU_CONVERSION;

	HRESULT		hr = E_FAIL;

	TCHAR szTimestamp[32];
	SYSTEMTIME stTimestamp;
	BSTR bstrTimeStamp = NULL;
	GetLocalTime(&stTimestamp);

	hr = StringCchPrintfEx(szTimestamp, ARRAYSIZE(szTimestamp), NULL, NULL, MISTSAFE_STRING_FLAGS,
                           _T("%4d-%02d-%02dT%02d:%02d:%02d"), // "ISO 8601" format for "datatime" datatype in xml
                           stTimestamp.wYear,
                           stTimestamp.wMonth,
                           stTimestamp.wDay,
                           stTimestamp.wHour,
                           stTimestamp.wMinute,
                           stTimestamp.wSecond);
	CleanUpIfFailedAndSetHrMsg(hr);
	
	bstrTimeStamp = SysAllocString(T2OLE(szTimestamp));

	//
	// set the "timestamp" attribute
	//
	CleanUpIfFailedAndSetHrMsg(SetAttribute(m_ppNodeArray[hNodeItem], KEY_TIMESTAMP, bstrTimeStamp));

CleanUp:
	SysFreeString(bstrTimeStamp);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddDetectResult()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddDetectResult(HANDLE_NODE hNodeItem,
								   INT iInstalled    /*= -1*/,
								   INT iUpToDate     /*= -1*/,
								   INT iNewerVersion /*= -1*/,
								   INT iExcluded     /*= -1*/,
								   INT iForce        /*= -1*/,
								   INT iComputerSystem /* = -1 */)
{
	LOG_Block("AddDetectResult()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeDetectResult = NULL;

	//
	// create the <detectResult> node
	//
	pNodeDetectResult = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_DETECTRESULT);
	if (NULL == pNodeDetectResult) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeItem], pNodeDetectResult));

	//
	// set the "installed" attribute
	//
	if (-1 != iInstalled)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDetectResult, KEY_INSTALLED, iInstalled));
	}

	//
	// set the "upToDate" attribute
	//
	if (-1 != iUpToDate)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDetectResult, KEY_UPTODATE, iUpToDate));
	}

	//
	// set the "newerVersion" attribute
	//
	if (-1 != iNewerVersion)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDetectResult, KEY_NEWERVERSION, iNewerVersion));
	}

	//
	// set the "excluded" attribute
	//
	if (-1 != iExcluded)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDetectResult, KEY_EXCLUDED, iExcluded));
	}

	//
	// set the "force" attribute
	//
	if (-1 != iForce)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDetectResult, KEY_FORCE, iForce));
	}

	//
	// set computerSystem attribute
	//
	if (-1 != iComputerSystem)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDetectResult, KEY_COMPUTERSYSTEM, iComputerSystem));
	}


CleanUp:
	SafeReleaseNULL(pNodeDetectResult);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddDownloadStatus()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddDownloadStatus(HANDLE_NODE hNodeItem, BSTR bstrValue, DWORD dwErrorCode /*= 0*/)
{
	LOG_Block("AddDownloadStatus()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeDownloadStatus = NULL;

	//
	// create the <downloadStatus> node
	//
	pNodeDownloadStatus = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_DOWNLOADSTATUS);
	if (NULL == pNodeDownloadStatus) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeItem], pNodeDownloadStatus));

	//
	// set the "value" attribute
	//
	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDownloadStatus, KEY_VALUE, bstrValue));

	//
	// set the "errorCode" attribute
	//
	if (0 != dwErrorCode)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeDownloadStatus, KEY_ERRORCODE, dwErrorCode));
	}

CleanUp:
	SafeReleaseNULL(pNodeDownloadStatus);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddDownloadPath()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddDownloadPath(HANDLE_NODE hNodeItem, BSTR bstrDownloadPath)
{
	LOG_Block("AddDownloadPath()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeDownloadPath = NULL;
	IXMLDOMNode*	pNodeDownloadPathText = NULL;

	pNodeDownloadPath = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_DOWNLOADPATH);
	if (NULL == pNodeDownloadPath) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeItem], pNodeDownloadPath));

	pNodeDownloadPathText = CreateDOMNode(m_pDocItems, NODE_TEXT, NULL);
	if (NULL == pNodeDownloadPathText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeDownloadPathText, bstrDownloadPath));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeDownloadPath, pNodeDownloadPathText));

CleanUp:
	SafeReleaseNULL(pNodeDownloadPath);
	SafeReleaseNULL(pNodeDownloadPathText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddInstallStatus()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddInstallStatus(HANDLE_NODE hNodeItem,
									  BSTR bstrValue,
									  BOOL fNeedsReboot,
									  DWORD dwErrorCode /*= 0*/)
{
	LOG_Block("AddInstallStatus()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeInstallStatus = NULL;

	//
	// create the <installStatus> node
	//
	pNodeInstallStatus = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_INSTALLSTATUS);
	if (NULL == pNodeInstallStatus) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeItem], pNodeInstallStatus));

	//
	// set the "value" attribute
	//
	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeInstallStatus, KEY_VALUE, bstrValue));

	//
	// set the "needsReboot" attribute
	//
	CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeInstallStatus, KEY_NEEDSREBOOT, fNeedsReboot));

	//
	// set the "errorCode" attribute
	//
	if (0 != dwErrorCode)
	{
		CleanUpIfFailedAndSetHrMsg(SetAttribute(pNodeInstallStatus, KEY_ERRORCODE, dwErrorCode));
	}

CleanUp:
	SafeReleaseNULL(pNodeInstallStatus);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// AddClientInfo()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::AddClientInfo(HANDLE_NODE hNodeItem, BSTR bstrClient)
{
	LOG_Block("AddClientInfo()");

	HRESULT		hr = E_FAIL;

	IXMLDOMNode*	pNodeClient = NULL;
	IXMLDOMNode*	pNodeClientText = NULL;

	pNodeClient = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_CLIENT);
	if (NULL == pNodeClient) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(InsertNode(m_ppNodeArray[hNodeItem], pNodeClient));

	pNodeClientText = CreateDOMNode(m_pDocItems, NODE_TEXT, NULL);
	if (NULL == pNodeClientText) goto CleanUp;

	CleanUpIfFailedAndSetHrMsg(SetValue(pNodeClientText, bstrClient));
	CleanUpIfFailedAndSetHrMsg(InsertNode(pNodeClient, pNodeClientText));

CleanUp:
	SafeReleaseNULL(pNodeClient);
	SafeReleaseNULL(pNodeClientText);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// MigrateV3History()
//
// Migrate V3 History: Consumer history only.
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::MigrateV3History(LPCTSTR pszHistoryFilePath)
{
	LOG_Block("MigrateV3History()");

	HRESULT		hr = S_OK;

	IXMLDOMNode*	pNodeXML = NULL;
	IXMLDOMNode*	pNodeItemStatus = NULL;
	IXMLDOMNode*	pNodeIdentity = NULL;
	IXMLDOMNode*	pNodeDescription = NULL;
	IXMLDOMNode*	pNodeDescriptionText = NULL;
	IXMLDOMNode*	pNodeTitle = NULL;
	IXMLDOMNode*	pNodeTitleText = NULL;
	IXMLDOMNode*	pNodeVersion = NULL;
	IXMLDOMNode*	pNodeVersionText = NULL;
	IXMLDOMNode*	pNodeInstallStatus = NULL;
	IXMLDOMNode*	pNodeClient = NULL;
	IXMLDOMNode*	pNodeClientText = NULL;
	BSTR bstrNameSpaceSchema = NULL, bstrStatus = NULL, bstrString = NULL;
	LPTSTR pszItemsSchema = NULL;
	LPTSTR pszNameSpaceSchema = NULL;

	CV3AppLog V3History(pszHistoryFilePath);
	char szLineType[32];
	char szTemp[32];
	char szDate[32];
	char szTime[32];
	char szPUID[32];		// puid - migrate to <identity "name">
	char szTitle[256];		// title - migrate to <description>-><descriptionText>-><title>
	char szVersion[40];		// version - migrate to <identity>-><version>
	char szTimeStamp[32];	// timestamp - migrate to <itemStatus "timestamp">
	char szResult[16];		// result - migrate to <installStatus "value">
	char szErrCode[16];		// errorcode - migrate to <installStatus "errorCode">

	USES_IU_CONVERSION;

	V3History.StartReading();
	while (V3History.ReadLine())
	{
	    SafeSysFreeString(bstrString);
		// get line type (first field)
		V3History.CopyNextField(szLineType, ARRAYSIZE(szLineType));
		if ((_stricmp(szLineType, LOG_V3CAT) == 0) || (_stricmp(szLineType, LOG_V3_2) == 0)) 
		{
			// get "puid" field
			V3History.CopyNextField(szPUID, ARRAYSIZE(szPUID));

			// get "operation" field: installed/uninstalled
			// we only migrate installed item
			V3History.CopyNextField(szTemp, ARRAYSIZE(szTemp));
			if (0 != _stricmp(szTemp, LOG_INSTALL))
				continue;

			//
			// now we start to create <itemStatus> node for this item
			//
			if (NULL == m_pDocItems)
			{
				//
				// we don't have IU history file yet
				//
 				hr = CoCreateInstance(CLSID_DOMDocument,
											  NULL,
											  CLSCTX_INPROC_SERVER,
											  IID_IXMLDOMDocument,
											  (void **) &m_pDocItems);
				if (FAILED(hr))
				{
					LOG_ErrorMsg(hr);
				}
				else
				{
					//
					// create the <?xml version="1.0"?> node
					//
					pNodeXML = CreateDOMNode(m_pDocItems, NODE_PROCESSING_INSTRUCTION, KEY_XML);
					if (NULL == pNodeXML) goto CleanUp;

					CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, pNodeXML));

					//
					// process the iuident.txt to find the Items schema path
					//
					TCHAR szIUDir[MAX_PATH];
					TCHAR szIdentFile[MAX_PATH];

					pszItemsSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
					if (NULL == pszItemsSchema)
					{
						hr = E_OUTOFMEMORY;
						LOG_ErrorMsg(hr);
						goto CleanUp;
					}
					pszNameSpaceSchema = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INTERNET_MAX_URL_LENGTH * sizeof(TCHAR));
					if (NULL == pszNameSpaceSchema)
					{
						hr = E_OUTOFMEMORY;
						LOG_ErrorMsg(hr);
						goto CleanUp;
					}
		
					GetIndustryUpdateDirectory(szIUDir);
            		hr = PathCchCombine(szIdentFile, ARRAYSIZE(szIdentFile), szIUDir, IDENTTXT);
            		if (FAILED(hr))
            		{
            			LOG_ErrorMsg(hr);
            			goto CleanUp;
            		}

					GetPrivateProfileString(IDENT_IUSCHEMA,
											IDENT_IUSCHEMA_ITEMS,
											_T(""),
											pszItemsSchema,
											INTERNET_MAX_URL_LENGTH,
											szIdentFile);

					if ('\0' == pszItemsSchema[0])
					{
						// no Items schema path specified in iuident.txt
						LOG_Error(_T("No schema path specified in iuident.txt for Items"));
						goto CleanUp;
					}

            		hr = StringCchPrintfEx(pszNameSpaceSchema, INTERNET_MAX_URL_LENGTH, NULL, NULL, MISTSAFE_STRING_FLAGS,
            		                       _T("x-schema:%s"), pszItemsSchema);
            		if (FAILED(hr))
            		{
            			LOG_ErrorMsg(hr);
            			goto CleanUp;
            		}

					bstrNameSpaceSchema = T2BSTR(pszNameSpaceSchema);

					//
					// create the <items> node with the path of the schema
					//
					m_pNodeItems = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_ITEMS, bstrNameSpaceSchema);
					if (NULL == m_pNodeItems) goto CleanUp;
					
					CleanUpIfFailedAndSetHrMsg(InsertNode(m_pDocItems, m_pNodeItems));
				}
			}
			else
			{
			    SafeReleaseNULL(m_pNodeItems);
				FindSingleDOMNode(m_pDocItems, KEY_ITEMS, &m_pNodeItems);
			}
			
			// create <itemStatus> node
			pNodeItemStatus = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_ITEMSTATUS);
			if (NULL == pNodeItemStatus) continue;
			SkipIfFail(InsertNode(m_pNodeItems, pNodeItemStatus));
		
			// create <client> node
			pNodeClient = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_CLIENT);
			if (NULL == pNodeClient) continue;
			pNodeClientText = CreateDOMNode(m_pDocItems, NODE_TEXT, NULL);
			if (NULL == pNodeClientText) continue;
			BSTR bstrV3Client = SysAllocString(C_V3_CLIENTINFO);
			SkipIfFail(SetValue(pNodeClientText, bstrV3Client));
			SkipIfFail(InsertNode(pNodeClient, pNodeClientText));
			SkipIfFail(InsertNode(pNodeItemStatus, pNodeClient));
			SafeSysFreeString(bstrV3Client);

			// create <identity> node
			pNodeIdentity = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_IDENTITY);
			if (NULL == pNodeIdentity) continue;
			SkipIfFail(InsertNode(pNodeItemStatus, pNodeIdentity));
			
			// set the "name" attribute for <identity>
			bstrString = SysAllocString(A2OLE(szPUID));
			SkipIfFail(SetAttribute(pNodeIdentity, KEY_NAME, bstrString));
			// set the "itemID" attribute for <identity>
			SkipIfFail(SetAttribute(pNodeIdentity, KEY_ITEMID, bstrString));
			SafeSysFreeString(bstrString);

			// get "title" field
			V3History.CopyNextField(szTitle, ARRAYSIZE(szTitle));
			
			// create <description> node
			pNodeDescription = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_DESCRIPTION);
			if (NULL == pNodeDescription) continue;
			SkipIfFail(SetAttribute(pNodeDescription, KEY_HIDDEN, 0));
			SkipIfFail(InsertNode(pNodeItemStatus, pNodeDescription));

			// create <descriptionText> node
			pNodeDescriptionText = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_DESCRIPTIONTEXT);
			if (NULL == pNodeDescriptionText) continue;
			SkipIfFail(InsertNode(pNodeDescription, pNodeDescriptionText));

			// create <title> node
			pNodeTitle = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_TITLE);
			if (NULL == pNodeTitle) continue;
			pNodeTitleText = CreateDOMNode(m_pDocItems, NODE_TEXT, NULL);
			if (NULL == pNodeTitleText) continue;
			bstrString = SysAllocString(A2OLE(szTitle));
			SkipIfFail(SetValue(pNodeTitleText, bstrString));
			SkipIfFail(InsertNode(pNodeTitle, pNodeTitleText));
			SkipIfFail(InsertNode(pNodeDescriptionText, pNodeTitle));
			SafeSysFreeString(bstrString);

			// get "version" field
			V3History.CopyNextField(szVersion, ARRAYSIZE(szVersion));

			// create <version> node
			pNodeVersion = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_VERSION);
			if (NULL == pNodeVersion) continue;

			pNodeVersionText = CreateDOMNode(m_pDocItems, NODE_TEXT, NULL);
			if (NULL == pNodeVersionText) continue;
			bstrString = SysAllocString(A2OLE(szVersion));
			SkipIfFail(SetValue(pNodeVersionText, bstrString));
			SkipIfFail(InsertNode(pNodeVersion, pNodeVersionText));
			SkipIfFail(InsertNode(pNodeIdentity, pNodeVersion));
			SafeSysFreeString(bstrString);

			// get timestamp
			if ((_stricmp(szLineType, LOG_V3_2) == 0))
			{
				// read the timestamp and convert to "ISO 8601" format for "datatime" datatype in xml:
				// for example, 2001-05-01T18:30:00
				// so we only need to replace the space with 'T'
 
				// timestamp
				V3History.CopyNextField(szTimeStamp, ARRAYSIZE(szTimeStamp));
				char *p = strchr(szTimeStamp, ' ');
				if (NULL != p) // if (NULL == p): there's no space then leave it as is to pass into SetAttribute
				{
				    *p = 'T';
				}
			}
			else 
			{
				// V3 Beta had two fields for date and time, we need read both these fields:

				// date
				V3History.CopyNextField(szDate, ARRAYSIZE(szDate));

				// time
				V3History.CopyNextField(szTime, ARRAYSIZE(szTime));
				hr = StringCchPrintfExA(szTimeStamp, ARRAYSIZE(szTimeStamp), NULL, NULL, MISTSAFE_STRING_FLAGS,
				                        "%sT%s", szDate, szTime);
				SkipIfFail(hr);
			}
			// set the "timestamp" attribute for <itemStatus>
			bstrString = SysAllocString(A2OLE(szTimeStamp));
			SkipIfFail(SetAttribute(pNodeItemStatus, KEY_TIMESTAMP, bstrString));
			SafeSysFreeString(bstrString);

			// skip the "record type" field
			V3History.CopyNextField(szTemp, ARRAYSIZE(szTemp));

			// get "result" field
			V3History.CopyNextField(szResult, ARRAYSIZE(szResult));

			// create <InstallStatus> node
			pNodeInstallStatus = CreateDOMNode(m_pDocItems, NODE_ELEMENT, KEY_INSTALLSTATUS);
			if (NULL == pNodeInstallStatus) continue;
			SkipIfFail(InsertNode(pNodeItemStatus, pNodeInstallStatus));

			// set the "value" attribute for <installStatus>
			if (_stricmp(szResult, LOG_SUCCESS) == 0)
			{
				bstrStatus = SysAllocString(L"COMPLETE");
			}
			else if (_stricmp(szTemp, LOG_STARTED) == 0)
			{
				bstrStatus = SysAllocString(L"IN_PROGRESS");
			}
			else
			{
				bstrStatus = SysAllocString(L"FAILED");
			}
			SkipIfFail(SetAttribute(pNodeInstallStatus, KEY_VALUE, bstrStatus));
			
			if (_stricmp(szResult, LOG_SUCCESS) != 0)
			{
				// get "error code" field
				V3History.CopyNextField(szErrCode, ARRAYSIZE(szErrCode));

				// set the "errorCode" attribute for <installStatus>
				SkipIfFail(SetAttribute(pNodeInstallStatus, KEY_ERRORCODE,  atoh(szErrCode)));
			}
		}

	}
	V3History.StopReading();

CleanUp:
	
	SafeReleaseNULL(pNodeXML);
	SafeReleaseNULL(pNodeItemStatus);
	SafeReleaseNULL(pNodeIdentity);
	SafeReleaseNULL(pNodeDescriptionText);
	SafeReleaseNULL(pNodeTitle);
	SafeReleaseNULL(pNodeTitleText);
	SafeReleaseNULL(pNodeVersion);
	SafeReleaseNULL(pNodeVersionText);
	SafeReleaseNULL(pNodeInstallStatus);
	SafeReleaseNULL(pNodeClient);
	SafeReleaseNULL(pNodeClientText);
	SysFreeString(bstrString);
	SysFreeString(bstrNameSpaceSchema);
	SysFreeString(bstrStatus);
	SafeHeapFree(pszItemsSchema);
	SafeHeapFree(pszNameSpaceSchema);
	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetItemsBSTR()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::GetItemsBSTR(BSTR *pbstrXmlItems)
{
	LOG_Block("GetItemsBSTR()");

	if (NULL == m_pDocItems)
	{
		*pbstrXmlItems = NULL;
		return S_OK;
	}

	//
	// convert XML DOC into BSTR 
	//
	HRESULT hr = m_pDocItems->get_xml(pbstrXmlItems);
    if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}

	return hr;
}


/////////////////////////////////////////////////////////////////////////////
// GetFilteredHistoryBSTR()
/////////////////////////////////////////////////////////////////////////////
HRESULT CXmlItems::GetFilteredHistoryBSTR(BSTR bstrBeginDateTime,
											BSTR bstrEndDateTime,
											BSTR bstrClient,
											BSTR *pbstrXmlHistory)
{
	LOG_Block("GetFilteredHistoryBSTR()");

	USES_IU_CONVERSION;

	HRESULT		hr = S_OK;

	IXMLDOMNode*	pNodeItems = NULL;
	IXMLDOMNode*	pNodeItem = NULL;
	IXMLDOMNode*	pNodeItemOut = NULL;
	IXMLDOMNode*	pNodeClient = NULL;
	BSTR bstrTimeStamp = NULL;
	BSTR bstrClientInfo = NULL;
	BOOL fOutOfRange = FALSE;

	HANDLE_NODELIST	hNodeList = HANDLE_NODELIST_INVALID;

	if (NULL == pbstrXmlHistory)
	{
		SetHrMsgAndGotoCleanUp(E_INVALIDARG);
	}	
	
	if (NULL != m_pDocItems)
	{
		if (SUCCEEDED(FindSingleDOMNode(m_pDocItems, KEY_ITEMS, &pNodeItems)))
		{
			hNodeList = FindFirstDOMNode(pNodeItems, KEY_ITEMSTATUS, &pNodeItem);
			if (HANDLE_NODELIST_INVALID != hNodeList)
			{
				CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeItem, KEY_TIMESTAMP, &bstrTimeStamp));
				if (NULL != bstrTimeStamp)
				{
					if ((NULL != bstrBeginDateTime) && (0 != SysStringLen(bstrBeginDateTime)) &&
						(CompareBSTRs(bstrTimeStamp, bstrBeginDateTime) < 0))
					{
						//
						// remove the item whose timestamp is out of range;
						// set the flag to ignore the timestamp comparison for the rest of nodes
						//
						CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
						fOutOfRange = TRUE;
					}
					else if ((NULL != bstrEndDateTime) && (0 != SysStringLen(bstrEndDateTime)) &&
							 (CompareBSTRs(bstrTimeStamp, bstrEndDateTime) > 0))
					{
						//
						// remove the item whose timestamp is out of range
						//
						CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
					}
					else
					{
						CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_CLIENT, &pNodeClient));
						CleanUpIfFailedAndSetHrMsg(GetText(pNodeClient, &bstrClientInfo));
						if ((NULL != bstrClient) && (0 != SysStringLen(bstrClient)) &&
							(WUCompareStringI(OLE2T(bstrClientInfo), OLE2T(bstrClient)) != CSTR_EQUAL))
						{
							//
							// remove the item whose clieninfo does not match what we need
							//
							CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
						}
					}
				}
				SafeReleaseNULL(pNodeItem);
				SafeReleaseNULL(pNodeItemOut);
				SafeReleaseNULL(pNodeClient);
				SafeSysFreeString(bstrTimeStamp);
				SafeSysFreeString(bstrClientInfo);
				while (SUCCEEDED(FindNextDOMNode(hNodeList, &pNodeItem)))
				{
					if (fOutOfRange)
					{
						//
						// remove the item whose timestamp is out of range
						//
						CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
					}
					else
					{
						CleanUpIfFailedAndSetHrMsg(GetAttribute(pNodeItem, KEY_TIMESTAMP, &bstrTimeStamp));
						if (NULL != bstrTimeStamp)
						{
							if ((NULL != bstrBeginDateTime) && (0 != SysStringLen(bstrBeginDateTime)) &&
								(CompareBSTRs(bstrTimeStamp, bstrBeginDateTime) < 0))
							{
								//
								// remove the item whose timestamp is out of range;
								// set the flag to ignore the timestamp comparison for the rest of nodes
								//
								CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
								fOutOfRange = TRUE;
							}
							else if ((NULL != bstrEndDateTime) && (0 != SysStringLen(bstrEndDateTime)) &&
									 (CompareBSTRs(bstrTimeStamp, bstrEndDateTime) > 0))
							{
								//
								// remove the item whose timestamp is out of range
								//
								CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
							}
							else
							{
								CleanUpIfFailedAndSetHrMsg(FindSingleDOMNode(pNodeItem, KEY_CLIENT, &pNodeClient));
								CleanUpIfFailedAndSetHrMsg(GetText(pNodeClient, &bstrClientInfo));
								if ((NULL != bstrClient) && (0 != SysStringLen(bstrClient)) &&
									(WUCompareStringI(OLE2T(bstrClientInfo), OLE2T(bstrClient)) != CSTR_EQUAL))
								{
									//
									// remove the item whose clieninfo does not match what we need
									//
									CleanUpIfFailedAndSetHrMsg(pNodeItems->removeChild(pNodeItem, &pNodeItemOut));
								}
							}
						}
					}
					SafeReleaseNULL(pNodeItem);
					SafeReleaseNULL(pNodeItemOut);
					SafeReleaseNULL(pNodeClient);
					SafeSysFreeString(bstrTimeStamp);
					SafeSysFreeString(bstrClientInfo);
				}
			}
		}
	}

CleanUp:
	CloseItemList(hNodeList);
	SafeReleaseNULL(pNodeItems);
	SafeReleaseNULL(pNodeItem);
	SafeReleaseNULL(pNodeItemOut);
	SafeReleaseNULL(pNodeClient);
	SysFreeString(bstrTimeStamp);
	SysFreeString(bstrClientInfo);
	if (SUCCEEDED(hr))
	{
		hr = GetItemsBSTR(pbstrXmlHistory);
	}
	return hr;
}






/////////////////////////////////////////////////////////////////////////////
// CXmlClientInfo


CXmlClientInfo::CXmlClientInfo()
: m_pDocClientInfo(NULL)
{

}

CXmlClientInfo::~CXmlClientInfo()
{
	SafeReleaseNULL(m_pDocClientInfo);
}

//
// load and parse and validate an XML document from string
//
HRESULT CXmlClientInfo::LoadXMLDocument(BSTR bstrXml, BOOL fOfflineMode)
{
    LOG_Block("CXmlClientInfo::LoadXMLDocument()");

    SafeReleaseNULL(m_pDocClientInfo);
	HRESULT hr = LoadXMLDoc(bstrXml, &m_pDocClientInfo, fOfflineMode);
	if (FAILED(hr))
	{
		LOG_ErrorMsg(hr);
	}
	return hr;
}

//
// retrieve client name attribute
//
HRESULT CXmlClientInfo::GetClientName(BSTR* pbstrClientName)
{
	HRESULT hr= E_UNEXPECTED;

	IXMLDOMElement* pElement = NULL;
	BSTR bstrTagName = NULL;
	VARIANT vAttr;
	VariantInit(&vAttr);

	LOG_Block("GetClientName()");

	if (NULL == pbstrClientName)
	{
		return E_INVALIDARG;
	}

	if (NULL == m_pDocClientInfo)
	{
		return hr;
	}

	hr = m_pDocClientInfo->get_documentElement(&pElement);
	CleanUpIfFailedAndMsg(hr);
	if (NULL == pElement)
	{
		//
		// no root element
		//
		hr = E_INVALIDARG;		// clientInfo is bad! return this error back to caller
		LOG_ErrorMsg(hr);
		goto CleanUp;
	}

	hr = pElement->get_tagName(&bstrTagName);
	CleanUpIfFailedAndMsg(hr);

	if (!CompareBSTRsEqual(bstrTagName, KEY_CLIENTINFO))
	{
		//
		// root is not clientInfo
		//
		hr = E_INVALIDARG;
		LOG_ErrorMsg(hr);
		goto CleanUp;
	}

	hr = pElement->getAttribute(KEY_CLIENTNAME, &vAttr);
	CleanUpIfFailedAndMsg(hr);

	if (VT_BSTR == vAttr.vt)
	{
		*pbstrClientName = SysAllocString(vAttr.bstrVal);
	}
	else
	{
		hr = E_FAIL;
		CleanUpIfFailedAndMsg(hr);
	}

CleanUp:
	SafeReleaseNULL(pElement);
	if (bstrTagName)
	{
		SysFreeString(bstrTagName);
		bstrTagName = NULL;
	}
	VariantClear(&vAttr);

	return hr;
}