/***************************************************************************** * * DIRegUtl.c * * Copyright (c) 1996 Microsoft Corporation. All Rights Reserved. * * Abstract: * * Registry utility functions. * * Contents: * * *****************************************************************************/ #include "dinputpr.h" /***************************************************************************** * * The sqiffle for this file. * *****************************************************************************/ #define sqfl sqflRegUtils /***************************************************************************** * * @doc INTERNAL * * @func LONG | RegQueryString | * * Wrapper for that reads a * string value from the registry. An annoying quirk * is that on Windows NT, the returned string might * not end in a null terminator, so we might need to add * one manually. * * @parm IN HKEY | hk | * * Parent registry key. * * @parm LPCTSTR | ptszValue | * * Value name. * * @parm LPTSTR | ptsz | * * Output buffer. * * @parm DWORD | ctchBuf | * * Size of output buffer. * * @returns * * Registry error code. * *****************************************************************************/ LONG EXTERNAL RegQueryString(HKEY hk, LPCTSTR ptszValue, LPTSTR ptszBuf, DWORD ctchBuf) { LONG lRc; DWORD reg; #ifdef UNICODE DWORD cb; /* * NT quirk: Non-null terminated strings can exist. */ cb = cbCtch(ctchBuf); lRc = RegQueryValueEx(hk, ptszValue, 0, ®, (PV)ptszBuf, &cb); if(lRc == ERROR_SUCCESS) { if(reg == REG_SZ) { /* * Check the last character. If it is not NULL, then * append a NULL if there is room. */ DWORD ctch = ctchCb(cb); if(ctch == 0) { ptszBuf[ctch] = TEXT('\0'); } else if(ptszBuf[ctch-1] != TEXT('\0')) { if(ctch < ctchBuf) { ptszBuf[ctch] = TEXT('\0'); } else { lRc = ERROR_MORE_DATA; } } } else { lRc = ERROR_INVALID_DATA; } } #else /* * This code is executed only on Win95, so we don't have to worry * about the NT quirk. */ lRc = RegQueryValueEx(hk, ptszValue, 0, ®, (PV)ptszBuf, &ctchBuf); if(lRc == ERROR_SUCCESS && reg != REG_SZ) { lRc = ERROR_INVALID_DATA; } #endif return lRc; } /***************************************************************************** * * @doc INTERNAL * * @func LONG | RegQueryStringValueW | * * Wrapper for that handles ANSI/UNICODE * issues, as well as treating a nonexistent key as if it * were a null string. * * Note that the value name is still a . * * It is assumed that the thing being read is a string. * Don't use this function to read binary data. * * You cannot use this function to query the necessary * buffer size (again, because I'm lazy). It's not as * simple as doubling the ansi size, because DBCS may * result in a non-linear translation function. * * @parm IN HKEY | hk | * * Parent registry key. * * @parm LPCTSTR | ptszValue | * * Value name. * * @parm LPWSTR | pwsz | * * UNICODE output buffer. * * @parm LPDWORD | pcbBuf | * * Size of UNICODE output buffer. May not exceed * cbCwch(MAX_PATH). * * @returns * * Registry error code. On error, the output buffer is * set to a null string. On an ERROR_MORE_DATA, the * output buffer is null-terimated. * *****************************************************************************/ LONG EXTERNAL RegQueryStringValueW(HKEY hk, LPCTSTR ptszValue, LPWSTR pwszBuf, LPDWORD pcbBuf) { LONG lRc; #ifdef UNICODE AssertF(*pcbBuf > 0); AssertF(*pcbBuf <= cbCwch(MAX_PATH)); /* * NT quirk: Non-null terminated strings can exist. */ lRc = RegQueryString(hk, ptszValue, pwszBuf, ctchCb(*pcbBuf)); #else /* * NT quirk: Non-null terminated strings can exist. Fortunately, * this code is executed only on Win95, which terminates properly. */ DWORD cb; TCHAR tszBuf[MAX_PATH]; AssertF(*pcbBuf > 0); AssertF(*pcbBuf <= cbCwch(MAX_PATH)); /* * ISSUE-2001/03/29-timgill Incorrect size returned in single case * Note that we do not get the size perfect in the DBCS case. * * Fortunately, the slop is on the high end, where hopefully * nobody lives or will notice. * * Is it worth fixing? */ cb = cwchCb(*pcbBuf); lRc = RegQueryValueEx(hk, ptszValue, 0, 0, (PV)tszBuf, &cb); if(lRc == ERROR_SUCCESS) { DWORD cwch; /* * Convert the string up to UNICODE. */ cwch = AToU(pwszBuf, cwchCb(*pcbBuf), tszBuf); *pcbBuf = cbCwch(cwch); /* * If the buffer was not big enough, the return value * will be zero. */ if(cwch == 0 && tszBuf[0]) { lRc = ERROR_MORE_DATA; } else { lRc = ERROR_SUCCESS; } } #endif /* * If the buffer was too small, then null-terminate it just * to make sure. */ if(lRc == ERROR_MORE_DATA) { if(*pcbBuf) { pwszBuf[cwchCb(*pcbBuf)-1] = TEXT('\0'); } } else /* * If it was some other error, then wipe out the buffer * so the caller won't get confused. */ if(lRc != ERROR_SUCCESS) { pwszBuf[0] = TEXT('\0'); /* * If the error was that the key doesn't exist, then * treat it as if it existed with a null string. */ if(lRc == ERROR_FILE_NOT_FOUND) { lRc = ERROR_SUCCESS; } } return lRc; } /***************************************************************************** * * @doc INTERNAL * * @func LONG | RegSetStringValueW | * * Wrapper for that handles ANSI/UNICODE * issues, as well as converting null strings into nonexistent * values. * * Note that the value name is still a . * * It is assumed that the thing being written is a string. * Don't use this function to write binary data. * * @parm IN HKEY | hk | * * Parent registry key. * * @parm LPCTSTR | ptszValue | * * Value name. * * @parm LPCWSTR | pwsz | * * UNICODE value to write. A null pointer is valid, indicating * that the key should be deleted. * * @returns * * Registry error code. * *****************************************************************************/ LONG EXTERNAL RegSetStringValueW(HKEY hk, LPCTSTR ptszValue, LPCWSTR pwszData) { DWORD cwch; LONG lRc; if(pwszData) { cwch = lstrlenW(pwszData); } else { cwch = 0; } if(cwch) { #ifdef UNICODE lRc = RegSetValueExW(hk, ptszValue, 0, REG_SZ, (PV)pwszData, cbCwch(cwch+1)); #else DWORD ctch; TCHAR tszBuf[MAX_PATH]; /* * Convert the string down to ANSI. */ ctch = UToA(tszBuf, cA(tszBuf), pwszData); if(ctch) { lRc = RegSetValueEx(hk, ptszValue, 0, REG_SZ, (PV)tszBuf, cbCtch(ctch+1)); } else { lRc = ERROR_CANTWRITE; } #endif } else { lRc = RegDeleteValue(hk, ptszValue); /* * It is not an error if the key does not already exist. */ if(lRc == ERROR_FILE_NOT_FOUND) { lRc = ERROR_SUCCESS; } } return lRc; } #ifndef UNICODE /***************************************************************************** * * @doc INTERNAL * * @func LONG | RegDeleteKeyW | * * Wrapper for on non-UNICODE platforms. * * @parm IN HKEY | hk | * * Parent registry key. * * @parm LPCWSTR | pwsz | * * Subkey name. * * @returns * * Registry error code. * *****************************************************************************/ LONG EXTERNAL RegDeleteKeyW(HKEY hk, LPCWSTR pwsz) { LONG lRc; CHAR szBuf[MAX_PATH]; /* * Convert the string down to ANSI. */ UToA(szBuf, cA(szBuf), pwsz); lRc = RegDeleteKeyA(hk, szBuf); return lRc; } #endif /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | hresMumbleKeyEx | * * Either open or create the key, depending on the degree * of access requested. * * @parm HKEY | hk | * * Base key. * * @parm LPCTSTR | ptszKey | * * Name of subkey, possibly NULL. * * @parm REGSAM | sam | * * Security access mask. * * @parm DWORD | dwOptions | * Options for RegCreateEx * * @parm PHKEY | phk | * * Receives output key. * * @returns * * Return value from or , * converted to an . * *****************************************************************************/ STDMETHODIMP hresMumbleKeyEx(HKEY hk, LPCTSTR ptszKey, REGSAM sam, DWORD dwOptions, PHKEY phk) { HRESULT hres; LONG lRc; /* * If caller is requesting write access, then try opening it for writing; * if that fails with access denied error, then try opening it for reading; * if key doesn't exist, create the key. * Else just open it. */ if(IsWriteSam(sam)) { // on WinXP, we strip out WRITE_DAC and WRITE_OWNER bits if (DIGetOSVersion() == WINWH_OS) { sam &= ~DI_DAC_OWNER; } lRc = RegOpenKeyEx(hk, ptszKey, 0, sam, phk); if( lRc == ERROR_SUCCESS ) { // Don't need to create it already exists } else { // Change for server per Whistler bug 575181 // If error is access denied, try opening the key for reading if (lRc == ERROR_ACCESS_DENIED) { lRc = RegOpenKeyEx(hk, ptszKey, 0, KEY_READ, phk); } else { // Try to create it lRc = RegCreateKeyEx ( hk, // handle of an open key ptszKey, // address of subkey name 0, // reserved NULL, // address of class string dwOptions, // special options flag sam, // desired security access NULL, // inherit the parent's secuirty descriptor phk, // address of buffer for opened handle 0 // address of disposition value buffer); ); } } } else { lRc = RegOpenKeyEx(hk, ptszKey, 0, sam, phk); } if(lRc == ERROR_SUCCESS) { hres = S_OK; } else { if(lRc == ERROR_KEY_DELETED || lRc == ERROR_BADKEY) { lRc = ERROR_FILE_NOT_FOUND; } hres = hresLe(lRc); } return hres; } /***************************************************************************** * * @doc INTERNAL * * @func LONG | RegQueryDIDword | * * Read a dword value from a sub key of the DirectInput part of the * registry. * * @parm LPCTSTR | ptszSubKey | * * Optional path from root of DirectInput registry. * * @parm LPCTSTR | ptszValue | * * Value name. * * @parm DWORD | dwDefault | * * Default value to use if there was an error. * * @returns * * The value read, or the default. * *****************************************************************************/ DWORD EXTERNAL RegQueryDIDword(LPCTSTR ptszPath, LPCTSTR ptszValue, DWORD dwDefault) { HKEY hk; DWORD dw; if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_DINPUT, 0, KEY_QUERY_VALUE, &hk) == 0) { DWORD cb = cbX(dw); if( ptszPath ) { HKEY hkSub; if(RegOpenKeyEx(hk, ptszPath, 0, KEY_QUERY_VALUE, &hkSub) == 0) { RegCloseKey( hk ); hk = hkSub; } } if(RegQueryValueEx(hk, ptszValue, 0, 0, (LPBYTE)&dw, &cb) == 0 && cb == cbX(dw)) { } else { dw = dwDefault; } RegCloseKey(hk); } else { dw = dwDefault; } return dw; } // // A registry key that is opened by an application can be deleted // without error by another application in both Windows 95 and // Windows NT. This is by design. DWORD EXTERNAL DIWinnt_RegDeleteKey ( HKEY hStartKey , LPCTSTR pKeyName ) { #define MAX_KEY_LENGTH ( 256 ) DWORD dwRtn, dwSubKeyLength; TCHAR szSubKey[MAX_KEY_LENGTH]; // (256) this should be dynamic. HKEY hKey; // do not allow NULL or empty key name if( pKeyName && lstrlen(pKeyName)) { if( (dwRtn=RegOpenKeyEx(hStartKey,pKeyName, 0, KEY_ENUMERATE_SUB_KEYS | DELETE, &hKey )) == ERROR_SUCCESS) { while(dwRtn == ERROR_SUCCESS ) { dwSubKeyLength = MAX_KEY_LENGTH; dwRtn=RegEnumKeyEx( hKey, 0, // always index zero szSubKey, &dwSubKeyLength, NULL, NULL, NULL, NULL ); if(dwRtn == ERROR_SUCCESS) { dwRtn = DIWinnt_RegDeleteKey(hKey, szSubKey); } else if(dwRtn == ERROR_NO_MORE_ITEMS) { dwRtn = RegDeleteKey(hStartKey, pKeyName); break; } } RegCloseKey(hKey); // Do not save return code because error // has already occurred } } else dwRtn = ERROR_BADKEY; return dwRtn; } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | hresRegCopyValues | * * Copy all the values from one key to another. * * @parm HKEY | hkSrc | * * Key with values to be copied * (must be opened with at least KEY_READ access). * * @parm HKEY | hkDest | * * Key to receive copies (must be opened with at least KEY_WRITE). * * @returns * * S_OK if all values were successfully copied * S_FALSE if there were no values to copy. * Or a memory allocation error code or the failing registry function * return code converted to a . * *****************************************************************************/ STDMETHODIMP hresRegCopyValues( HKEY hkSrc, HKEY hkDest ) { HRESULT hres; LONG lRc; DWORD cItems; DWORD MaxNameLen; DWORD MaxDataLen; DWORD NameLen; DWORD DataLen; PTCHAR tszName; PBYTE pData; DWORD Type; EnterProcI(hresRegCopyValues, (_ "xx", hkSrc, hkDest)); lRc = RegQueryInfoKey( hkSrc, // Key, NULL, NULL, NULL,// Class, cbClass, Reserved, NULL, NULL, NULL,// NumSubKeys, MaxSubKeyLen, MaxClassLen, &cItems, // NumValues, &MaxNameLen, // MaxValueNameLen, &MaxDataLen, // MaxValueLen, NULL, NULL ); // Security descriptor, last write if( lRc == ERROR_SUCCESS ) { if( cItems ) { MaxNameLen++; // Take account of NULL terminator hres = AllocCbPpv( MaxDataLen + MaxNameLen * sizeof(tszName[0]), &pData ); if( FAILED(hres) ) { SquirtSqflPtszV(sqfl | sqflError, TEXT("Out of memory copying registry values") ); } else { tszName = (PTCHAR)(pData + MaxDataLen); do { DataLen = MaxDataLen; NameLen = MaxNameLen; lRc = RegEnumValue( hkSrc, --cItems, tszName, &NameLen, NULL, &Type, pData, &DataLen ); if( lRc != ERROR_SUCCESS ) { SquirtSqflPtszV(sqfl | sqflError, TEXT("RegEnumValues failed during copy values, code 0x%08x"), lRc ); break; } else { lRc = RegSetValueEx( hkDest, tszName, 0, Type, pData, DataLen ); if( lRc != ERROR_SUCCESS ) { SquirtSqflPtszV(sqfl | sqflError, TEXT("Failed to copy value %s code %x"), tszName, lRc ); break; } } } while( cItems ); FreePpv( &pData ); if( lRc != ERROR_SUCCESS ) { hres = hresReg( lRc ); } else { hres = S_OK; } } } else { SquirtSqflPtszV(sqfl, TEXT("No values to copy") ); hres = S_FALSE; } } else { SquirtSqflPtszV(sqfl | sqflBenign, TEXT("RegQueryInfoKey failed during value copy, code 0x%08x"), lRc ); hres = hresReg(lRc); } ExitOleProc(); return( hres ); } /* hresRegCopyValues */ /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | hresRegCopyKey | * * Make an empty copy of a key. * * @parm HKEY | hkSrcRoot | * * The Key under the key name to be copied exists. * (must be opened with at least KEY_READ). * * @parm PTCHAR | szSrcName | * Name of key to copy * * @parm PTCHAR | szClass | * Class of key to copy * * @parm HKEY | hkDestRoot | * * The Key under which the copy will be created * (must be opened with at least KEY_WRITE). * * @parm PTCHAR | szSrcName | * Name of new key * * @parm PHKEY | phkSub | * * The optional pointer to an HKEY to recieve the opened key if it is * successfully created. If this is NULL, the key is closed. * * @returns * * S_OK if the new key was created. * S_FALSE if the new key already existed * Or the return value of a failing registry function or * GetSecurityInfo converted to a . * *****************************************************************************/ STDMETHODIMP hresRegCopyKey( HKEY hkSrcRoot, PTCHAR szSrcName, PTCHAR szClass, HKEY hkDestRoot, PTCHAR szDestName, HKEY *phkSub ) { LONG lRc; HKEY hkSub; DWORD dwDisposition; HRESULT hres; #ifdef WINNT HKEY hkSrc; #endif EnterProcI(hresRegCopyKey, (_ "xssxs", hkSrcRoot, szSrcName, szClass, hkDestRoot, szDestName)); #ifdef WINNT lRc = RegOpenKeyEx( hkSrcRoot, szSrcName, 0, KEY_READ, &hkSrc ); if( lRc == ERROR_SUCCESS ) { SECURITY_ATTRIBUTES sa; SECURITY_INFORMATION si; sa.nLength = sizeof( sa ); sa.bInheritHandle = TRUE; si = OWNER_SECURITY_INFORMATION; lRc = GetSecurityInfo( hkSrc, SE_REGISTRY_KEY, si, NULL, NULL, // Don't care about SID or SID group NULL, NULL, // Don't care about DACL or SACL &sa.lpSecurityDescriptor ); RegCloseKey( hkSrc ); if( lRc == ERROR_SUCCESS ) { lRc = RegCreateKeyEx( hkDestRoot, szDestName, 0, szClass, REG_OPTION_NON_VOLATILE, KEY_WRITE, &sa, &hkSub, &dwDisposition ); LocalFree( sa.lpSecurityDescriptor ); if( lRc != ERROR_SUCCESS ) { SquirtSqflPtszV(sqfl | sqflBenign, TEXT("Failed to RegCreateKeyEx for key name %s, code 0x%08x"), szDestName, lRc ); } } else { SquirtSqflPtszV(sqfl | sqflBenign, TEXT("Failed to GetSecurityInfo for key name %s, code 0x%08x"), szSrcName, lRc ); } } else { SquirtSqflPtszV(sqfl | sqflBenign, TEXT("Failed to RegOpenKeyEx for key name %s, code 0x%08x"), szSrcName, lRc ); } #else /* On Win9x the source is not used as the name and class is all we need */ hkSrcRoot; szSrcName; lRc = RegCreateKeyEx( hkDestRoot, szDestName, 0, szClass, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkSub, &dwDisposition ); if( lRc != ERROR_SUCCESS ) { SquirtSqflPtszV(sqfl | sqflBenign, TEXT("Failed to RegCreateKeyEx for key name %s, code 0x%08x"), szDestName, lRc ); } #endif /* WINNT */ if( lRc == ERROR_SUCCESS ) { if( phkSub ) { *phkSub = hkSub; } else { RegCloseKey( hkSub ); } hres =( dwDisposition == REG_CREATED_NEW_KEY ) ? S_OK : S_FALSE; } else { hres = hresReg( lRc ); } ExitOleProc(); return( hres ); } /* hresRegCopyKey */ /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | hresRegCopyKeys | * * Copy all the keys under the source key to the root. * * @parm HKEY | hkSrc | * * Key be copied (must be opened with at least KEY_READ access). * * @parm HKEY | hkRoot | * * The Key under which the copy will be created * (must be opened with at least KEY_WRITE). * * @parm PDWORD | pMaxNameLen | * * An optional pointer to a value which will be filled with the number * of characters, incl. the NULL terminator, in the longest key name. * * @returns * * S_OK if all keys were successfully copied * S_FALSE if there were no keys to copy. * Or the memory allocation error code or the failing registry * function return code converted to a . * *****************************************************************************/ STDMETHODIMP hresRegCopyKeys( HKEY hkSrc, HKEY hkRoot, PDWORD OPTIONAL pMaxNameLen ) { HRESULT hres; LONG lRc; DWORD cSubKeys; DWORD MaxNameLen; DWORD cbName; PTCHAR szKeyName; DWORD MaxClassLen; DWORD cbClass; PTCHAR szClassName; EnterProcI(hresRegCopyKeys, (_ "xx", hkSrc, hkRoot )); lRc = RegQueryInfoKey( hkSrc, // handle to key to query NULL, NULL, NULL, // Class, cbClass, Reserved &cSubKeys, // NumSubKeys &MaxNameLen, // MaxSubKeyLen &MaxClassLen, // MaxClassLen NULL, NULL, NULL, // NumValues, MaxValueNameLen, MaxValueLen NULL, NULL ); // Security descriptor, last write if( lRc == ERROR_SUCCESS ) { if( cSubKeys ) { // Make space for NULL terminators MaxNameLen++; MaxClassLen++; if( pMaxNameLen ) { *pMaxNameLen = MaxNameLen; } /* * There are keys to copy so allocate buffer sapce for the key and * key class names. */ /* * Prefix warns (mb:34678) that things would go horribly wrong if * (MaxNameLen + MaxClassLen) * sizeof(szClassName[0]) == 0 * however this cannot happen unless RegQueryInfoKey has some * catestrophic failure _and_ returned OK. */ AssertF( (MaxNameLen + MaxClassLen) * sizeof(szClassName[0]) != 0 ); hres = AllocCbPpv( (MaxNameLen + MaxClassLen) * sizeof(szClassName[0]), &szKeyName ); if( FAILED( hres ) ) { SquirtSqflPtszV(sqfl | sqflError, TEXT("Out of memory copying subkeys") ); } else { szClassName = &szKeyName[MaxNameLen]; cSubKeys--; do { cbName = MaxNameLen; cbClass = MaxClassLen; lRc = RegEnumKeyEx( hkSrc, // Key containing subkeys to enumerate cSubKeys, // index of subkey to enumerate szKeyName, // address of buffer for subkey name &cbName, // address for size of subkey buffer NULL, // reserved szClassName,// address of buffer for class string &cbClass, // address for size of class buffer NULL ); // address for time key last written to if( lRc == ERROR_SUCCESS ) { hres = hresRegCopyKey( hkSrc, szKeyName, szClassName, hkRoot, szKeyName, NULL ); } else { SquirtSqflPtszV(sqfl | sqflError, TEXT("RegEnumKeyEx failed during copy keys, code 0x%08x"), lRc ); hres = hresReg( hres ); } if( FAILED( hres ) ) { break; } } while( cSubKeys-- ); FreePpv(&szKeyName); } } else { SquirtSqflPtszV(sqfl, TEXT("No keys to copy") ); hres = S_FALSE; } } else { SquirtSqflPtszV(sqfl | sqflBenign, TEXT("RegQueryInfoKey failed during value key, code 0x%08x"), lRc ); hres = hresReg(lRc); } ExitOleProc(); return( hres ); } /* hresRegCopyKeys */ /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | hresRegCopyBranch | * * Copy the contents of one key including sub-keys to another. * Since this function calls itself to copy the contents of subkeys, * the local variables should be kept to a minimum. * * @parm HKEY | hkSrc | * * Key to be copied (must be opened with at least KEY_READ access). * * @parm HKEY | hkDest | * * Key to receive copy (must be opened with at least KEY_WRITE). * * @returns * * S_OK if the copy completed succesfully * or the return value from , * , memory allocation error or a registry * function failure code converted to a . * *****************************************************************************/ STDMETHODIMP hresRegCopyBranch( HKEY hkSrc, HKEY hkDest ) { HKEY hkSrcSub; HKEY hkDestSub; HRESULT hres; DWORD dwIdx; DWORD cbMaxName; DWORD cbKeyName; PTCHAR szKeyName; EnterProcI(hresRegCopyBranch, (_ "xx", hkSrc, hkDest)); hres = hresRegCopyValues( hkSrc, hkDest ); if( SUCCEEDED( hres ) ) { hres = hresRegCopyKeys( hkSrc, hkDest, &cbMaxName ); if( hres == S_FALSE ) { /* No keys to recurse into */ hres = S_OK; } else if( hres == S_OK ) { /* * Assert that a non-zero size buffer is requested */ AssertF( cbMaxName * sizeof(szKeyName[0]) ); hres = AllocCbPpv( cbMaxName * sizeof(szKeyName[0]), &szKeyName ); if( SUCCEEDED( hres ) ) { for( dwIdx=0; SUCCEEDED( hres ); dwIdx++ ) { cbKeyName = cbMaxName; /* * Prefix warns (mb:34669 & win:170672) that szKeyName * could be NULL if the above alloc was for zero bytes. * This cannot happen as cbMaxName is the length of the * longest key name, incremented to leave space for null * termination on a successful call to RegQueryInfoKey * when at least one key was found. */ hres = hresReg( RegEnumKeyEx( hkSrc, dwIdx, szKeyName, &cbKeyName, NULL, NULL, NULL, NULL ) ); // Reserved, szClass, cbClass, Last Write if( SUCCEEDED( hres ) ) { hres = hresReg( RegOpenKeyEx( hkSrc, szKeyName, 0, KEY_READ, &hkSrcSub ) ); if( SUCCEEDED( hres ) ) { hres = hresReg( RegOpenKeyEx( hkDest, szKeyName, 0, KEY_WRITE, &hkDestSub ) ); if( SUCCEEDED( hres ) ) { hres = hresRegCopyBranch( hkSrcSub, hkDestSub ); } else { SquirtSqflPtszV(sqfl | sqflError, TEXT("Failed to open destination subkey %s for recursion, code 0x%04x"), szKeyName, LOWORD(hres) ); } } else { SquirtSqflPtszV(sqfl | sqflError, TEXT("Failed to open source subkey %s for recursion, code 0x%04x"), szKeyName, LOWORD(hres) ); } } else { if( hres == hresReg( ERROR_NO_MORE_ITEMS ) ) { /* Recursed all keys */ hres = S_OK; break; } else { SquirtSqflPtszV(sqfl | sqflError, TEXT("Failed RegEnumKeyEx during subkey recursion, code 0x%04x"), LOWORD(hres) ); } } } FreePpv( &szKeyName ); } else { SquirtSqflPtszV(sqfl | sqflError, TEXT("Out of memory recursing subkeys") ); } } else { if( SUCCEEDED( hres ) ) { RPF( "Unexpected success code 0x%08x from hresRegCopyKeys", hres ); } } } ExitOleProc(); return( hres ); } /* hresRegCopyBranch */