1274 lines
35 KiB
C
1274 lines
35 KiB
C
|
/*****************************************************************************
|
||
|
*
|
||
|
* 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 <f RegQueryValueEx> 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 <f RegQueryValueEx> 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 <t LPCTSTR>.
|
||
|
*
|
||
|
* 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 <f RegSetValueEx> that handles ANSI/UNICODE
|
||
|
* issues, as well as converting null strings into nonexistent
|
||
|
* values.
|
||
|
*
|
||
|
* Note that the value name is still a <t LPCTSTR>.
|
||
|
*
|
||
|
* 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 <f RegDeleteKeyA> 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 <f RegOpenKeyEx> or <f RegCreateKeyEx>,
|
||
|
* converted to an <t HRESULT>.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
STDMETHODIMP
|
||
|
hresMumbleKeyEx(HKEY hk, LPCTSTR ptszKey, REGSAM sam, DWORD dwOptions, PHKEY phk)
|
||
|
{
|
||
|
HRESULT hres;
|
||
|
LONG lRc;
|
||
|
BOOL bWinXP = FALSE;
|
||
|
|
||
|
/*
|
||
|
* If caller is requesting write access, then 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;
|
||
|
bWinXP = TRUE;
|
||
|
}
|
||
|
lRc = RegOpenKeyEx(hk, ptszKey, 0, sam, phk);
|
||
|
|
||
|
if( lRc == ERROR_SUCCESS )
|
||
|
{
|
||
|
// Don't need to create it already exists
|
||
|
} else
|
||
|
{
|
||
|
#ifdef WINNT
|
||
|
EXPLICIT_ACCESS ExplicitAccess;
|
||
|
PACL pACL;
|
||
|
DWORD dwErr;
|
||
|
SECURITY_DESCRIPTOR SecurityDesc;
|
||
|
DWORD dwDisposition;
|
||
|
SECURITY_ATTRIBUTES sa;
|
||
|
PSID pSid = NULL;
|
||
|
SID_IDENTIFIER_AUTHORITY authority = SECURITY_WORLD_SID_AUTHORITY;
|
||
|
|
||
|
|
||
|
// Describe the access we want to create the key with
|
||
|
ZeroMemory (&ExplicitAccess, sizeof(ExplicitAccess) );
|
||
|
//set the access depending on the OS (see Whistler bug 318865)
|
||
|
if (bWinXP == TRUE)
|
||
|
{
|
||
|
ExplicitAccess.grfAccessPermissions = DI_KEY_ALL_ACCESS;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ExplicitAccess.grfAccessPermissions = KEY_ALL_ACCESS;
|
||
|
}
|
||
|
ExplicitAccess.grfAccessMode = GRANT_ACCESS;
|
||
|
ExplicitAccess.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
|
||
|
|
||
|
if (AllocateAndInitializeSid(
|
||
|
&authority,
|
||
|
1,
|
||
|
SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0,
|
||
|
&pSid
|
||
|
))
|
||
|
{
|
||
|
BuildTrusteeWithSid(&(ExplicitAccess.Trustee), pSid );
|
||
|
|
||
|
dwErr = SetEntriesInAcl( 1, &ExplicitAccess, NULL, &pACL );
|
||
|
|
||
|
|
||
|
if( dwErr == ERROR_SUCCESS )
|
||
|
{
|
||
|
AssertF( pACL );
|
||
|
|
||
|
if( InitializeSecurityDescriptor( &SecurityDesc, SECURITY_DESCRIPTOR_REVISION ) )
|
||
|
{
|
||
|
if( SetSecurityDescriptorDacl( &SecurityDesc, TRUE, pACL, FALSE ) )
|
||
|
{
|
||
|
// Initialize a security attributes structure.
|
||
|
sa.nLength = sizeof (SECURITY_ATTRIBUTES);
|
||
|
sa.lpSecurityDescriptor = &SecurityDesc;
|
||
|
sa.bInheritHandle = TRUE;// Use the security attributes to create a key.
|
||
|
|
||
|
lRc = RegCreateKeyEx
|
||
|
(
|
||
|
hk, // handle of an open key
|
||
|
ptszKey, // address of subkey name
|
||
|
0, // reserved
|
||
|
NULL, // address of class string
|
||
|
dwOptions, // special options flag
|
||
|
ExplicitAccess.grfAccessPermissions, // desired security access
|
||
|
&sa, // address of key security structure
|
||
|
phk, // address of buffer for opened handle
|
||
|
&dwDisposition // address of disposition value buffer);
|
||
|
);
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SquirtSqflPtszV( sqflError | sqflRegUtils,
|
||
|
TEXT("SetSecurityDescriptorDacl failed lastError=0x%x "),
|
||
|
GetLastError());
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SquirtSqflPtszV( sqflError | sqflRegUtils,
|
||
|
TEXT("InitializeSecurityDescriptor failed lastError=0x%x "),
|
||
|
GetLastError());
|
||
|
}
|
||
|
|
||
|
LocalFree( pACL );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SquirtSqflPtszV( sqflError | sqflRegUtils,
|
||
|
TEXT("SetEntriesInACL failed, dwErr=0x%x"), dwErr );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SquirtSqflPtszV( sqflError | sqflRegUtils,
|
||
|
TEXT("AllocateAndInitializeSid failed lastError=0x%x "), GetLastError());
|
||
|
|
||
|
}
|
||
|
|
||
|
//Cleanup pSid
|
||
|
if (pSid != NULL)
|
||
|
{
|
||
|
FreeSid(pSid);
|
||
|
}
|
||
|
|
||
|
|
||
|
if( lRc != ERROR_SUCCESS )
|
||
|
{
|
||
|
SquirtSqflPtszV( sqflError,
|
||
|
TEXT("Failed to create regkey %s with security descriptor, lRc=0x%x "),
|
||
|
ptszKey, lRc);
|
||
|
}
|
||
|
#else
|
||
|
lRc = RegCreateKeyEx(hk, ptszKey, 0, 0,
|
||
|
dwOptions,
|
||
|
sam, 0, phk, 0);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
} 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 <t HRESULT>.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
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 <t HRESULT>.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
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 <t HRESULT>.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
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];
|
||
|
|
||
|
// redefine Max*Len to cbMax* for the loop
|
||
|
MaxNameLen *= sizeof( szKeyName[0] );
|
||
|
MaxNameLen *= sizeof( szClassName[0] );
|
||
|
|
||
|
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 <f hresRegCopyValues>,
|
||
|
* <f hresRegCopyKeys>, memory allocation error or a registry
|
||
|
* function failure code converted to a <t HRESULT>.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
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 */
|
||
|
|