Click here to Skip to main content
15,886,077 members
Articles / Desktop Programming / MFC

Import/Export Registry Sections as XML

Rate me:
Please Sign up or sign in to vote.
4.33/5 (16 votes)
21 Jan 20033 min read 166.1K   6.1K   73  
Export registry sections as XML to simplify registry diffs
This article details a tool aimed to import/export registry sections in XML format, to make registry diff easier in practice.
#include "stdafx.h"
#include "registryxmllib.h"


void AddChildren(CTreeCtrl &tree, HTREEITEM hItem, CString &szPath)
{
	// build path from item name
	//
	if (hItem==NULL || tree.GetParentItem(hItem)==NULL) return;

	if (szPath.IsEmpty()) return;


	CString szMainKeyname = szPath;

	int nSlash = szPath.Find(_T("\\") );
	if (nSlash>-1)
	{
		szMainKeyname = szPath.Left( nSlash );
		szPath = szPath.Right( szPath.GetLength()-(nSlash+1));
	}
	else
		szPath.Empty();

	// open the key now
	HKEY hKey;
	long hr;

	if (szMainKeyname.CompareNoCase("HKEY_CLASSES_ROOT")==0)
	{
		hr = ::RegOpenKey(HKEY_CLASSES_ROOT, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_CURRENT_USER")==0)
	{
		hr = ::RegOpenKey(HKEY_CURRENT_USER, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_LOCAL_MACHINE")==0)
	{
		hr = ::RegOpenKey(HKEY_LOCAL_MACHINE, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_USERS")==0)
	{
		hr = ::RegOpenKey(HKEY_USERS, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_CURRENT_CONFIG")==0)
	{
		hr = ::RegOpenKey(HKEY_CURRENT_CONFIG, szPath, &hKey);
	}
	else 
		return; // break here

	if (hr==ERROR_SUCCESS) // it's ok
	{
		AddChildRegistryKeys(tree, hItem, hKey);

		::RegCloseKey(hKey);
	}

}



void AddChildRegistryKeys(CTreeCtrl &tree, HTREEITEM hParent, HKEY hKey) 
{ 
    
    DWORD    cSubKeys;                 // number of subkeys 
    DWORD    cbMaxSubKey;              // longest subkey size 
    DWORD    cchMaxClass;              // longest class string 
    DWORD    cValues;              // number of values for key 
    DWORD    cchMaxValue;          // longest value name 
    DWORD    cbMaxValueData;       // longest value data 
    DWORD    cbSecurityDescriptor; // size of security descriptor 
    FILETIME ftLastWriteTime;      // last write time 
 
    DWORD i; 
    DWORD retCode; 
 

    // Get the class name and the value count. 
    retCode = ::RegQueryInfoKey(hKey,        // key handle 
        NULL,					// buffer for class name 
        NULL,					// length of class string 
        NULL,                    // reserved 
        &cSubKeys,               // number of subkeys 
        &cbMaxSubKey,            // longest subkey size 
        &cchMaxClass,            // longest class string 
        &cValues,                // number of values for this key 
        &cchMaxValue,            // longest value name 
        &cbMaxValueData,         // longest value data 
        &cbSecurityDescriptor,   // security descriptor 
        &ftLastWriteTime);       // last write time 
 

	if (retCode!=ERROR_SUCCESS) return;

    // Enumerate the child keys, until RegEnumKeyEx fails. 

    CHAR  *achKey = new CHAR[cbMaxSubKey+1]; 
	if (!achKey) return;

    for (i = 0, retCode = ERROR_SUCCESS; retCode == ERROR_SUCCESS; i++) 
    { 
		DWORD achKeyMaxLength = cbMaxSubKey + 1;

        retCode = ::RegEnumKeyEx(hKey, 
                     i, 
                     achKey, 
                     &achKeyMaxLength, 
                     NULL, 
                     NULL, 
                     NULL, 
                     &ftLastWriteTime); 

		if (retCode == ERROR_SUCCESS && achKey && achKeyMaxLength>0)
		{
			achKey[achKeyMaxLength]=0; // force EOL
			HTREEITEM hNewItem = 
				tree.InsertItem(achKey, ILI_FOLDER, ILI_FOLDERS, hParent, TVI_LAST);

			// now try to guess if this item has children ?
			// - if yes, ten add a fake item, just to make sure it is expandable
			// - if no, ok good bye!
			if (hNewItem)
			{
				HKEY hSubkey;
				if (::RegOpenKey(hKey, achKey, &hSubkey)==ERROR_SUCCESS)
				{

					DWORD cChildSubKeys = 0;  

					if (::RegQueryInfoKey(hSubkey,  // key handle 
						NULL,					// buffer for class name 
						NULL,					// length of class string 
						NULL,                   // reserved 
						&cChildSubKeys,         // number of subkeys 
						NULL,					// longest subkey size 
						NULL,					// longest class string 
						NULL,					// number of values for this key 
						NULL,					// longest value name 
						NULL,					// longest value data 
						NULL,					// security descriptor 
						NULL)==ERROR_SUCCESS )					// last write time 
					{
						if (cChildSubKeys>0)
							tree.InsertItem(FAKEDITEM, ILI_DEFAULT, ILI_DEFAULT, hNewItem, TVI_LAST);
					}


					::RegCloseKey(hSubkey);
				}
			}

		}
    } 

	delete [] achKey;


} 


void AddRegistryValues(HKEY hKey, CPtrArray &arrValues)
{

    DWORD    cSubKeys;                 // number of subkeys 
    DWORD    cbMaxSubKey;              // longest subkey size 
    DWORD    cchMaxClass;              // longest class string 
    DWORD    cValues = 0;              // number of values for key 
    DWORD    cchMaxValue;          // longest value name 
    DWORD    cbMaxValueData;       // longest value data 
    DWORD    cbSecurityDescriptor; // size of security descriptor 
    FILETIME ftLastWriteTime;      // last write time 
 
    DWORD j; 
    DWORD retValue; 
 
    // Get the class name and the value count. 
    retValue = ::RegQueryInfoKey(hKey,        // key handle 
        NULL,					// buffer for class name 
        NULL,					// length of class string 
        NULL,                    // reserved 
        &cSubKeys,               // number of subkeys 
        &cbMaxSubKey,            // longest subkey size 
        &cchMaxClass,            // longest class string 
        &cValues,                // number of values for this key 
        &cchMaxValue,            // longest value name 
        &cbMaxValueData,         // longest value data 
        &cbSecurityDescriptor,   // security descriptor 
        &ftLastWriteTime);       // last write time 
 
    // Enumerate the child keys, until RegEnumKeyEx fails.

    CHAR  *achValueName = new CHAR[cchMaxValue+1]; 
	if (!achValueName) return;

    CHAR  *achValueData = new CHAR[cbMaxValueData+1]; 
	if (!achValueData)
	{
		delete [] achValueName;
		return;
	}
	
	CString szDefaultName;
	szDefaultName.LoadString(IDS_DEFAULTVALUENAME); // (Default)

 // Enumerate the key values. 

	if (cValues && cValues!=-1 && retValue==ERROR_SUCCESS)
	{
		for (j = 0, retValue = ERROR_SUCCESS; j < cValues; j++) 
		{ 
			DWORD cchValueName = cchMaxValue + 1;
			DWORD cchValueData = cbMaxValueData + 1;
			DWORD dwType;

			achValueName[0] = _T('\0'); 
			achValueData[0] = _T('\0'); 

			retValue = ::RegEnumValue(hKey, j, achValueName, 
				&cchValueName, 
				NULL, 
				&dwType, 
				(LPBYTE) achValueData,
				&cchValueData);

			if (retValue == ERROR_SUCCESS) 
			{ 

				KeyValue *p = new KeyValue();
				if (!p) continue; //
				
				CStringArray a;

				if (cchValueName==0 && strlen(achValueName)==0)
				{
					if (cchValueData==0 && strlen(achValueData)==0)
						p->SetKeyValue( szDefaultName,
										CString ("(value not set)"),
										dwType);
					else
						p->SetKeyValue( szDefaultName,
										CString (achValueData),
										dwType);
				}
				else
				{
					p->SetKeyValue( CString(achValueName),
									ConvertToString(dwType,achValueData,cchValueData),
									dwType );
				}


				arrValues.Add( p );
				
			} 
 
		}

	} // end if (cValues && cValues!=-1)


	// now sort all this
	//
	int nNbItems = arrValues.GetSize();
	BOOL bSorted = FALSE;
	while (!bSorted)
	{
		bSorted = TRUE;

		if (nNbItems<=1) break; // no need to sort trivial arrays

		int i=0;
		while ( bSorted && i<(nNbItems-1) )
		{
			KeyValue *pCur = (KeyValue*) arrValues.GetAt(i);
			KeyValue *pNext = (KeyValue*) arrValues.GetAt(i+1);
			if (pCur && pNext)
			{
				if (pCur->GetName().CompareNoCase(szDefaultName)==0)
				{
					i++;
					continue; 
				}

				if (pCur->GetName().CompareNoCase( pNext->GetName() ) >0 )
				{
					bSorted = FALSE;
					arrValues.SetAt(i,   pNext); // swap values
					arrValues.SetAt(i+1, pCur);
				}
			}

			i++;

		} // first-pass
	} // second-pass


	// make sure we have at least a default value to play with
	BOOL bFound = FALSE;
	int i = 0;
	while (!bFound && i<nNbItems)
	{
		KeyValue *p = (KeyValue*) arrValues.GetAt(i++);
		if (p)
		{
			bFound = ( p->GetName().Compare(szDefaultName)==0 );
		}
		
	}

	if (!bFound)
	{
		// this is a fake add, just to visually match what's shown in regedit.exe
		KeyValue *p = new KeyValue();
		if (p)
		{
			p->SetKeyValue( CString (szDefaultName),
							CString ("(value not set)"),
							REG_SZ);

			arrValues.InsertAt(0, p);
		}
	}

	

	delete [] achValueData;
	delete [] achValueName;

}




CString ConvertToString(DWORD dwType, LPTSTR szRawBuffer, DWORD nLen)
{
	CString s;

	// conversion from number to string
	if ( (dwType>=REG_BINARY && dwType<=REG_DWORD_BIG_ENDIAN) || dwType==11 || dwType==REG_RESOURCE_LIST)
	{
		switch (dwType)
		{
			case REG_BINARY :
			case REG_RESOURCE_LIST :
			{
				CString sByte;
				for (int i=0; i<(long)nLen; i++)
				{
					byte c = szRawBuffer[i];
					sByte.Format(_T("%02x"), c);
					if (!s.IsEmpty()) s += _T(" ");
					s += sByte;
				}
			}
			break;
			case REG_DWORD : // == REG_DWORD_LITTLE_ENDIAN
			{
				byte a = szRawBuffer[3];
				byte b = szRawBuffer[2];
				byte c = szRawBuffer[1];
				byte d = szRawBuffer[0];
				s.Format(_T("0x%02x%02x%02x%02x"), a, b, c, d);
				DWORD n = (a<<24) | (b<<16) | (c<<8) | d;
				CString sDword;
				sDword.Format(_T(" (%d)"), n);
				s += sDword;
			}
			break;
			case REG_DWORD_BIG_ENDIAN :
			{
				byte a = szRawBuffer[0];
				byte b = szRawBuffer[1];
				byte c = szRawBuffer[2];
				byte d = szRawBuffer[3];
				s.Format(_T("0x%02x%02x%02x%02x"), a, b, c, d);
				DWORD n = (a<<24) | (b<<16) | (c<<8) | d;
				CString sDword;
				sDword.Format(_T(" (%d)"), n);
				s += sDword;
			}
			break;
			case 11 : // QWORD, QWORD_LITTLE_ENDIAN (64-bit integer)
			{
				byte a = szRawBuffer[7];
				byte b = szRawBuffer[6];
				byte c = szRawBuffer[5];
				byte d = szRawBuffer[4];
				byte e = szRawBuffer[3];
				byte f = szRawBuffer[2];
				byte g = szRawBuffer[1];
				byte h = szRawBuffer[0];
				s.Format(_T("0x%02x%02x%02x%02x%02x%02x%02x%02x"), a, b, c, d, e, f, g, h);
			}
			break;
		}
	}
	else
	{
		if (dwType==REG_LINK)
		{
			// convert the Unicode string to local charset string

			char *temps = new char[nLen+1];

			int nActualLength = ::WideCharToMultiByte(CP_ACP,
					0,
					(wchar_t *)szRawBuffer,
					-1,
					temps,
					nLen,
					NULL,
					NULL);

			temps[nActualLength]= _T('\0'); // EOL

			s = temps;

			delete [] temps;
		}
		else if (dwType==REG_MULTI_SZ)
		{
			// a MULTI_SZ value is a set of strings separated by a 0 char, and
			// finishes with a double 0

			for (int i=0; i<long(nLen-2); i++) // nLen-1 instead of nLen, because we don't care the second 0 of the double 0
			{
				if (szRawBuffer[i]==0)
					s += _T("\r\n");
				else
					s += szRawBuffer[i];
			}

		}
		else
		{
			s = szRawBuffer;
		}


	}
	

	return s;
}



CString StringFromValueType(DWORD nType)
{
	CString s;

	switch (nType) // see winnt.h
	{
		case REG_BINARY: s = "REG_BINARY"; break;
		case REG_DWORD : s = "REG_DWORD"; break;
		case REG_DWORD_BIG_ENDIAN : s = "REG_DWORD"; break;
		case REG_EXPAND_SZ : s = "REG_SZ"; break;
		case REG_LINK : s = "REG_SZ"; break;
		case REG_MULTI_SZ : s = "REG_MULTISZ"; break;
		case REG_NONE : s = "REG_SZ"; break;
		case 11 : s = "REG_QWORD"; break; // QWORD (64-bit integer)
		case REG_RESOURCE_LIST : s = "REG_RESOURCE_LIST"; break;
		case REG_SZ : s = "REG_SZ"; break;
	}
	return s;
}









BOOL SaveAsXml( XmlWriter &w, BOOL bFakedXml, CString &szInPath)
{
	CString szPath = szInPath;

	if (szPath.IsEmpty()) return FALSE;

	CString szMainKeyname = szPath;

	int nSlash = szPath.Find(_T("\\") );
	if (nSlash>-1)
	{
		szMainKeyname = szPath.Left( nSlash );
		szPath = szPath.Right( szPath.GetLength()-(nSlash+1));
	}
	else
		szPath.Empty();

	// open the key now
	HKEY hKey;
	long hr;

	if (szMainKeyname.CompareNoCase("HKEY_CLASSES_ROOT")==0)
	{
		hr = ::RegOpenKey(HKEY_CLASSES_ROOT, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_CURRENT_USER")==0)
	{
		hr = ::RegOpenKey(HKEY_CURRENT_USER, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_LOCAL_MACHINE")==0)
	{
		hr = ::RegOpenKey(HKEY_LOCAL_MACHINE, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_USERS")==0)
	{
		hr = ::RegOpenKey(HKEY_USERS, szPath, &hKey);
	}
	else if (szMainKeyname.CompareNoCase("HKEY_CURRENT_CONFIG")==0)
	{
		hr = ::RegOpenKey(HKEY_CURRENT_CONFIG, szPath, &hKey);
	}
	else 
		return FALSE; // break here

	if (hr==ERROR_SUCCESS) // it's ok
	{

		// write the main key here
		//
		CStringArray arKeyPath;

		int nIndexSlash = 0, i;
		CString szTmpPath = szInPath;

		do
		{
			nIndexSlash = szTmpPath.Find("\\", 0);
			if (nIndexSlash>-1)
			{
				arKeyPath.Add( szTmpPath.Left(nIndexSlash++) );
				szTmpPath = szTmpPath.Right( szTmpPath.GetLength()-nIndexSlash );
			}
			else
				arKeyPath.Add( szTmpPath );
		}
		while ( nIndexSlash>-1);

		int nSize = arKeyPath.GetSize();
		BOOL bResult;

		if (!bFakedXml)
		{

			for (i=0; i<nSize; i++)
			{
				XmlElement wkey( CString(XML_KEY) );

				wkey.AddAttrib( GetEscapedXmlString( CString(XML_NAME) ), 
								GetEscapedXmlString( arKeyPath.GetAt(i) ) );

				wkey.Write(w,1);
			}

			bResult = SaveAsXml(w, bFakedXml, CString(""), hKey);

			for (i=0; i<nSize; i++)
			{
				if (!bFakedXml)
				{
					XmlElement wkey( CString(XML_KEY) );
					wkey.WriteClosingTag(w,-1);
				}
			}

		}
		else
		{
				
			nSize--;

			for (i=0; i<nSize; i++)
			{
				XmlElement wkey( arKeyPath.GetAt(i) );
				wkey.Write(w,1);
			}

			bResult = SaveAsXml(w, bFakedXml, arKeyPath.GetAt(nSize), hKey);
		
			for (i=0; i<nSize; i++)
			{
				XmlElement wkey( arKeyPath.GetAt(nSize-1-i) );
				wkey.WriteClosingTag(w,-1);
			}
		}


		::RegCloseKey(hKey);

		return bResult;
	}

	return FALSE;

}


long g_nSaveCounter = 0;


BOOL SaveAsXml( XmlWriter &w, BOOL bFakedXml, CString &szKeyname, HKEY hKey)
{
	// write key name
	//
    DWORD    cSubKeys;                 // number of subkeys 
    DWORD    cbMaxSubKey;              // longest subkey size 
    DWORD    cchMaxClass;              // longest class string 
    DWORD    cValues;              // number of values for key 
    DWORD    cchMaxValue;          // longest value name 
    DWORD    cbMaxValueData;       // longest value data 
    DWORD    cbSecurityDescriptor; // size of security descriptor 
    FILETIME ftLastWriteTime;      // last write time 
 
    DWORD i; 
    DWORD retCode; 
 

    // Get the class name and the value count. 
    retCode = ::RegQueryInfoKey(hKey,        // key handle 
        NULL,					// buffer for class name 
        NULL,					// length of class string 
        NULL,                    // reserved 
        &cSubKeys,               // number of subkeys 
        &cbMaxSubKey,            // longest subkey size 
        &cchMaxClass,            // longest class string 
        &cValues,                // number of values for this key 
        &cchMaxValue,            // longest value name 
        &cbMaxValueData,         // longest value data 
        &cbSecurityDescriptor,   // security descriptor 
        &ftLastWriteTime);       // last write time 
 

	if (retCode!=ERROR_SUCCESS) return FALSE;

    // Enumerate the child keys, until RegEnumKeyEx fails. 

    CHAR  *achKey = new CHAR[cbMaxSubKey+1]; 
	if (!achKey) return FALSE;


	XmlElement wkey( CString(XML_KEY) );


	if (!szKeyname.IsEmpty())
	{
		if (!bFakedXml) // standard xml
		{
		
			wkey.AddAttrib( GetEscapedXmlString( CString(XML_NAME) ), 
							GetEscapedXmlString( szKeyname ) );

			wkey.Write(w,1);
		}
		else // faked xml
		{
			wkey.SetName( GetEscapedXmlString( szKeyname ) );
		}
	}	


	// each 50 values, we pump a window message, to check out whether the user hit ESCAPE
	if ( (g_nSaveCounter++ % 50)==0 )
	{
		MSG msg ;
		if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);

			if (msg.message==WM_KEYDOWN)
				if (msg.wParam==VK_ESCAPE) // escape process is the user presses the ESC key
					return FALSE;
		}

	}


	// save values
	//
	CPtrArray arrValues;

	AddRegistryValues(hKey, arrValues);

	// add the values to the ListView
	//
	int nbItems = arrValues.GetSize();
	for (int j=0; j<nbItems; j++)
	{

		KeyValue *p = (KeyValue*) arrValues.GetAt(j);
		if (!p) continue;

		DWORD dwType = p->GetType();

		int nValueIcon = ILI_STRING;
		if ( (dwType>=REG_BINARY && dwType<=REG_DWORD_BIG_ENDIAN) || dwType==11)
			nValueIcon = ILI_BINARY;

		if (!bFakedXml) // standard xml
		{
			XmlElement wvalue( CString(XML_VALUE) );

			wvalue.AddAttrib( CString(XML_NAME), GetEscapedXmlString( p->GetName() ) );
			wvalue.AddAttrib( CString(XML_VALUE2), GetEscapedXmlString( p->GetValue() ) );

			if (dwType!=REG_SZ && dwType!=REG_NONE)
			{
				wvalue.AddAttrib( CString(XML_TYPE), GetEscapedXmlString( StringFromValueType(dwType) ) );
			}

			wvalue.WriteEmpty(w, 1);
		}
		else // faked xml
		{
			wkey.AddAttrib( GetEscapedXmlString( p->GetName() ), GetEscapedXmlString( p->GetValue() ) );
		}

		delete p; // destroy the now useless temp struct
	}

	if (!szKeyname.IsEmpty())
	{

		if (bFakedXml)
		{
			if (cSubKeys>0)
				wkey.Write(w,1);
			else
				wkey.WriteEmpty(w,1);
		}

	}


    for (i = 0, retCode = ERROR_SUCCESS; retCode == ERROR_SUCCESS; i++) 
    { 
		DWORD achKeyMaxLength = cbMaxSubKey + 1;

        retCode = ::RegEnumKeyEx(hKey, 
                     i, 
                     achKey, 
                     &achKeyMaxLength, 
                     NULL, 
                     NULL, 
                     NULL, 
                     &ftLastWriteTime); 

		if (retCode == ERROR_SUCCESS && achKey && achKeyMaxLength>0)
		{
			achKey[achKeyMaxLength]=0; // force EOL

			// open sub keys
			//

			HKEY hSubkey;
			if (::RegOpenKey(hKey, achKey, &hSubkey)==ERROR_SUCCESS)
			{
		
				if (!SaveAsXml(w, bFakedXml, CString(achKey), hSubkey))
					return FALSE; //


				::RegCloseKey(hSubkey);
			}

		}

    }  // end for


	if (!szKeyname.IsEmpty())
	{

		if (!bFakedXml)
		{
			wkey.WriteClosingTag(w,-1);
		}
		else
		{
			// with faked xml, we only need to actually close the tag when there
			// are keys under it, otherwise, we did a WriteEmpty above.
			if (cSubKeys>0)
				wkey.WriteClosingTag(w,-1);
		}

	}

	delete [] achKey;

	return TRUE;
}




CString GetEscapedXmlString(CString &szInput)
{
	CString s;
	if (szInput.GetLength()==0) return s;

	long nLength = szInput.GetLength();
	for (long i=0; i<nLength; i++)
	{
		char c = szInput.GetAt(i);
		if (c=='&')
			s += "&amp;";
		else if (c=='"')
			s += "&quot;";
		else if (c=='<')
			s += "&lt;";
		else
			s += c;
	}

	return UTF8Conversion(s);
}


CString UTF8Conversion(CString &s)
{

	wchar_t *pWideString = new wchar_t[s.GetLength()+1];
	if (!pWideString) return s;

	// local charset to widechar
	//

	int nActualWideLength = ::MultiByteToWideChar(CP_ACP,
			0,
			s.GetBuffer(0),
			s.GetLength(),
			pWideString,
			s.GetLength());

	pWideString[nActualWideLength] = 0;

	// widechar to UTF8
	//		
	char *temps = new char[2*nActualWideLength+5]; // 2*x+5 (why not?)

	int nActualLength = ::WideCharToMultiByte(CP_UTF8,
			0,
			pWideString,
			-1,
			temps,
			2*nActualWideLength+5,
			NULL,
			NULL);

	temps[nActualLength]= _T('\0'); // force EOL

	CString st = temps;

	delete [] temps;

	delete [] pWideString;

	return st;

}



By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
France France
Addicted to reverse engineering. At work, I am developing business intelligence software in a team of smart people (independent software vendor).

Need a fast Excel generation component? Try xlsgen.

Comments and Discussions