#include "general.h" #include "ParseInf.h" #include "resource.h" #include "FileNode.h" #include #include //#define USE_SHORT_PATH_NAME 1 // also defined in \nt\private\inet\urlmon\download\isctrl.cxx LPCTSTR g_lpszUpdateInfo = TEXT("UpdateInfo"); LPCTSTR g_lpszCookieValue = TEXT("Cookie"); LPCTSTR g_lpszSavedValue = TEXT("LastSpecifiedInterval"); // This is a 'private' entry point into URLMON that we use // to convert paths from their current, possibly short-file-name // form to their canonical long-file-name form. extern "C" { #ifdef UNICODE #define STR_CDLGETLONGPATHNAME "CDLGetLongPathNameW" typedef DWORD (STDAPICALLTYPE *CDLGetLongPathNamePtr)(LPWSTR lpszLongPath, LPCWSTR lpszShortPath, DWORD cchBuffer); #else // not UNICODE #define STR_CDLGETLONGPATHNAME "CDLGetLongPathNameA" typedef DWORD (STDAPICALLTYPE *CDLGetLongPathNamePtr)(LPSTR lpszLong, LPCSTR lpszShort, DWORD cchBuffer); #endif // else not UNICODE } // given the typelib id, loop through HKEY_CLASSES_ROOT\Interface section and // remove those entries with "TypeLib" subkey equal to the given type lib id HRESULT CleanInterfaceEntries(LPCTSTR lpszTypeLibCLSID) { Assert(lpszTypeLibCLSID != NULL); if (lpszTypeLibCLSID == NULL || lpszTypeLibCLSID[0] == '\0') return HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS); HRESULT hr = S_OK; HKEY hkey = NULL; DWORD cStrings = 0; LONG lResult = ERROR_SUCCESS, lSize = 0; TCHAR szKeyName[OLEUI_CCHKEYMAX]; TCHAR szTmpID[MAX_PATH]; // open key HKEY_CLASS_ROOT\Interface if (RegOpenKeyEx( HKEY_CLASSES_ROOT, HKCR_INTERFACE, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) { // loop through all entries while ((lResult = RegEnumKey( hkey, cStrings, szKeyName, OLEUI_CCHKEYMAX)) == ERROR_SUCCESS) { lSize = MAX_PATH; lstrcat(szKeyName, TEXT("\\")); lstrcat(szKeyName, HKCR_TYPELIB); // if typelib id's match, remove the key if ((RegQueryValue( hkey, szKeyName, szTmpID, &lSize) == ERROR_SUCCESS) && (lstrcmpi(szTmpID, lpszTypeLibCLSID) == 0)) { hr = NullLastSlash(szKeyName, 0); if (SUCCEEDED(hr)) { DeleteKeyAndSubKeys(hkey, szKeyName); } } else { cStrings += 1; } } RegCloseKey(hkey); if (SUCCEEDED(hr)) { if (lResult != ERROR_NO_MORE_ITEMS) hr = HRESULT_FROM_WIN32(lResult); } } return hr; } // If the OCX file being removed does not exist, then we cannot prompt the control // to unregister itself. In this case, we call this function of clean up as many // registry entries as we could for the control. HRESULT CleanOrphanedRegistry( LPCTSTR szFileName, LPCTSTR szClientClsId, LPCTSTR szTypeLibCLSID) { HRESULT hr = S_OK; LONG lResult = 0; TCHAR szTmpID[MAX_PATH]; TCHAR szTmpRev[MAX_PATH]; TCHAR szKeyName[OLEUI_CCHKEYMAX+50]; HKEY hkey = NULL, hkeyCLSID = NULL; int nKeyLen = 0; DWORD cStrings = 0; long lSize = MAX_PATH; Assert(lstrlen(szFileName) > 0); Assert(lstrlen(szClientClsId) > 0); // Delete CLSID keys CatPathStrN( szTmpID, HKCR_CLSID, szClientClsId, MAX_PATH ); if (DeleteKeyAndSubKeys(HKEY_CLASSES_ROOT, szTmpID) != ERROR_SUCCESS) hr = S_FALSE; // Keep going, but mark that there was a failure // Delete TypeLib info if (szTypeLibCLSID != NULL && szTypeLibCLSID[0] != '\0') { CatPathStrN( szTmpID, HKCR_TYPELIB, szTypeLibCLSID, MAX_PATH); if (DeleteKeyAndSubKeys(HKEY_CLASSES_ROOT, szTmpID) != ERROR_SUCCESS) hr = S_FALSE; } // Delete ModuleUsage keys // The canonicalizer can fail if the target file isn't there, so in that case, fall back // on szFileName, which may well have come in canonical form from the DU file list. if ( OCCGetLongPathName(szTmpRev, szFileName, MAX_PATH) == 0 ) lstrcpy( szTmpRev, szFileName ); ReverseSlashes(szTmpRev); // Guard against the subkey name being empty, as this will cause us to nuke // the entire Module Usage subtree, which is a bad thing to do. if ( szTmpRev[0] != '\0' ) { CatPathStrN(szTmpID, REGSTR_PATH_MODULE_USAGE, szTmpRev, MAX_PATH); if (DeleteKeyAndSubKeys(HKEY_LOCAL_MACHINE, szTmpID) != ERROR_SUCCESS) hr = S_FALSE; // Delete SharedDLL value if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_SHAREDDLLS, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) { hr = (RegDeleteValue(hkey, szFileName) == ERROR_SUCCESS ? hr : S_FALSE); RegCloseKey(hkey); } else { hr = S_FALSE; } } // loop through entries under HKEY_CLASSES_ROOT to clear entries // whose CLSID subsection is equal to the CLSID of the control // being removed while ((lResult = RegEnumKey( HKEY_CLASSES_ROOT, cStrings++, szKeyName, OLEUI_CCHKEYMAX)) == ERROR_SUCCESS) { lSize = MAX_PATH; nKeyLen = lstrlen(szKeyName); lstrcat(szKeyName, "\\"); lstrcat(szKeyName, HKCR_CLSID); if ((RegQueryValue( HKEY_CLASSES_ROOT, szKeyName, szTmpID, &lSize) == ERROR_SUCCESS) && (lstrcmpi(szTmpID, szClientClsId) == 0)) { szKeyName[nKeyLen] = '\0'; DeleteKeyAndSubKeys(HKEY_CLASSES_ROOT, szKeyName); lResult = ERROR_NO_MORE_ITEMS; break; } } Assert(lResult == ERROR_NO_MORE_ITEMS); if (lResult != ERROR_NO_MORE_ITEMS) hr = S_FALSE; // loop through all HKEY_CLASSES_ROOT\CLSID entries and remove // those with InprocServer32 subsection equal to the name of // the OCX file being removed if (RegOpenKeyEx( HKEY_CLASSES_ROOT, HKCR_CLSID, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) { cStrings = 0; while ((lResult = RegEnumKey( hkey, cStrings, szKeyName, OLEUI_CCHKEYMAX)) == ERROR_SUCCESS) { // check InprocServer32 lSize = MAX_PATH; lstrcat(szKeyName, "\\"); lstrcat(szKeyName, INPROCSERVER32); if ((RegQueryValue( hkey, szKeyName, szTmpID, &lSize) == ERROR_SUCCESS) && (lstrcmpi(szTmpID, szFileName) == 0)) { hr = NullLastSlash(szKeyName, 0); if (SUCCEEDED(hr)) { DeleteKeyAndSubKeys(hkey, szKeyName); } continue; } // check LocalServer32 hr = NullLastSlash(szKeyName, 1); if (SUCCEEDED(hr)) { lstrcat(szKeyName, LOCALSERVER32); if ((RegQueryValue( hkey, szKeyName, szTmpID, &lSize) == ERROR_SUCCESS) && (lstrcmpi(szTmpID, szFileName) == 0)) { hr = NullLastSlash(szKeyName, 0); if (SUCCEEDED(hr)) { DeleteKeyAndSubKeys(hkey, szKeyName); } continue; } } // check LocalServerX86 hr = NullLastSlash(szKeyName, 1); if (SUCCEEDED(hr)) { lstrcat(szKeyName, LOCALSERVERX86); if ((RegQueryValue( hkey, szKeyName, szTmpID, &lSize) == ERROR_SUCCESS) && (lstrcmpi(szTmpID, szFileName) == 0)) { hr = NullLastSlash(szKeyName, 0); if (SUCCEEDED(hr)) { DeleteKeyAndSubKeys(hkey, szKeyName); } continue; } } // check InProcServerX86 hr = NullLastSlash(szKeyName, 1); if (SUCCEEDED(hr)) { lstrcat(szKeyName, INPROCSERVERX86); if ((RegQueryValue( hkey, szKeyName, szTmpID, &lSize) == ERROR_SUCCESS) && (lstrcmpi(szTmpID, szFileName) == 0)) { hr = NullLastSlash(szKeyName, 0); if (SUCCEEDED(hr)) { DeleteKeyAndSubKeys(hkey, szKeyName); } continue; } } cStrings += 1; } RegCloseKey(hkey); Assert(lResult == ERROR_NO_MORE_ITEMS); if (lResult != ERROR_NO_MORE_ITEMS) hr = S_FALSE; } return hr; } // Get from a abbreviated filename its full, long name // eg. from C:\DOC\MyMath~1.txt to C:\DOC\MyMathFile.txt // lpszShortFileName must has in it both the file name and its full path // if bToUpper is TRUE, the name returned will be in uppercase HRESULT ConvertToLongFileName( LPTSTR lpszShortFileName, BOOL bToUpper /* = FALSE */) { Assert(lpszShortFileName != NULL); if (lpszShortFileName == NULL) return HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS); HRESULT hr = S_OK; WIN32_FIND_DATA filedata; TCHAR *pEndPath = NULL; HANDLE h = FindFirstFile(lpszShortFileName, &filedata); if (h != INVALID_HANDLE_VALUE) { FindClose(h); // separate filename from path pEndPath = ReverseStrchr(lpszShortFileName, '\\'); if (pEndPath != NULL) { *(++pEndPath) = '\0'; lstrcat(pEndPath, filedata.cFileName); } else { lstrcpy(lpszShortFileName, filedata.cFileName); } // to upper case if requested if (bToUpper) CharUpper(lpszShortFileName); } else { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } //=--------------------------------------------------------------------------= // DeleteKeyAndSubKeys //=--------------------------------------------------------------------------= // delete's a key and all of it's subkeys. // // Parameters: // HKEY - [in] delete the descendant specified // LPSTR - [in] i'm the descendant specified // // Output: // LONG - ERROR_SUCCESS if successful // - else, a nonzero error code defined in WINERROR.H // // Notes: // - I don't feel too bad about implementing this recursively, since the // depth isn't likely to get all the great. // - Manually removing subkeys is needed for NT. Win95 does that // automatically // // This code was stolen from the ActiveX framework (util.cpp). LONG DeleteKeyAndSubKeys(HKEY hkIn, LPCTSTR pszSubKey) { HKEY hk; TCHAR szTmp[MAX_PATH]; DWORD dwTmpSize; LONG lResult; lResult = RegOpenKeyEx(hkIn, pszSubKey, 0, KEY_ALL_ACCESS, &hk); if (lResult != ERROR_SUCCESS) return lResult; // loop through all subkeys, blowing them away. for (/* DWORD c = 0 */; lResult == ERROR_SUCCESS; /* c++ */) { dwTmpSize = MAX_PATH; lResult = RegEnumKeyEx(hk, 0, szTmp, &dwTmpSize, 0, NULL, NULL, NULL); if (lResult == ERROR_NO_MORE_ITEMS) break; lResult = DeleteKeyAndSubKeys(hk, szTmp); } // there are no subkeys left, [or we'll just generate an error and return FALSE]. // let's go blow this dude away. // dwTmpSize = MAX_PATH; Assert(RegEnumKeyEx(hk, 0, szTmp, &dwTmpSize, 0, NULL, NULL, NULL) == ERROR_NO_MORE_ITEMS); RegCloseKey(hk); lResult = RegDeleteKey(hkIn, pszSubKey); return lResult; } // return TRUE if file szFileName exists, FALSE otherwise BOOL FileExist(LPCTSTR lpszFileName) { DWORD dwErrMode; BOOL fResult; dwErrMode = SetErrorMode(SEM_FAILCRITICALERRORS); fResult = ((UINT)GetFileAttributes(lpszFileName) != (UINT)-1); SetErrorMode(dwErrMode); return fResult; } // given a flag, return the appropriate directory // possible flags are: // GD_WINDOWDIR : return WINDOWS directory // GD_SYSTEMDIR : return SYSTEM directory // GD_CONTAINERDIR : return directory of app used to view control (ie IE) // GD_CACHEDIR : return OCX cache directory, read from registry // GD_CONFLICTDIR : return OCX conflict directory, read from registry // GD_EXTRACTDIR : require an extra parameter szOCXFullName, // extract and return its path HRESULT GetDirectory( UINT nDirType, LPTSTR szDirBuffer, int nBufSize, LPCTSTR szOCXFullName /* = NULL */) { LONG lResult = 0; TCHAR *pCh = NULL, *pszKeyName = NULL; HRESULT hr = S_OK; HKEY hkeyIntSetting = NULL; unsigned long ulSize = nBufSize; switch (nDirType) { case GD_WINDOWSDIR: if (GetWindowsDirectory(szDirBuffer, nBufSize) == 0) hr = HRESULT_FROM_WIN32(GetLastError()); break; case GD_SYSTEMDIR: if (GetSystemDirectory(szDirBuffer, nBufSize) == 0) hr = HRESULT_FROM_WIN32(GetLastError()); break; case GD_EXTRACTDIR: if (szOCXFullName == NULL) { hr = HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS); break; } lstrcpy(szDirBuffer, szOCXFullName); pCh = ReverseStrchr(szDirBuffer, '\\'); if (pCh == NULL) hr = HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS); else pCh[0] = '\0'; break; case GD_CONTAINERDIR: pszKeyName = new TCHAR[MAX_PATH]; if (pszKeyName == NULL) { hr = E_OUTOFMEMORY; break; } CatPathStrN(pszKeyName, REGSTR_PATH_IE, CONTAINER_APP, MAX_PATH); if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszKeyName, 0x0, KEY_READ, &hkeyIntSetting)) == ERROR_SUCCESS) { lResult = RegQueryValueEx( hkeyIntSetting, VALUE_PATH, NULL, NULL, (unsigned char*)szDirBuffer, &ulSize); } if (lResult != ERROR_SUCCESS) hr = HRESULT_FROM_WIN32(lResult); else szDirBuffer[lstrlen(szDirBuffer)-1] = '\0'; // take away the ending ';' delete [] pszKeyName; break; case GD_CACHEDIR: case GD_CONFLICTDIR: if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_IE_SETTINGS, 0x0, KEY_READ, &hkeyIntSetting)) == ERROR_SUCCESS) { lResult = RegQueryValueEx( hkeyIntSetting, VALUE_ACTIVEXCACHE, NULL, NULL, (unsigned char*)szDirBuffer, &ulSize); } hr = (lResult == ERROR_SUCCESS ? S_OK : HRESULT_FROM_WIN32(lResult)); // if looking for cache dir, append "\\CONFLICT" if (SUCCEEDED(hr) && nDirType == GD_CONFLICTDIR) lstrcat(szDirBuffer, DEFAULT_CONFLICT); break; default: Assert(FALSE); hr = E_UNEXPECTED; } if (hkeyIntSetting != NULL) RegCloseKey(hkeyIntSetting); if (FAILED(hr)) szDirBuffer[0] = '\0'; return hr; } // retrieve file size for file szFile. Size returne in pSize. HRESULT GetSizeOfFile(LPCTSTR lpszFile, LPDWORD lpSize) { HANDLE hFile = NULL; WIN32_FIND_DATA fileData; *lpSize = 0; Assert(lpszFile != NULL); hFile = FindFirstFile(lpszFile, &fileData); if (hFile == INVALID_HANDLE_VALUE) return HRESULT_FROM_WIN32(GetLastError()); FindClose(hFile); *lpSize = fileData.nFileSizeLow; // Get cluster size to calculate the real # of bytes // taken up by the file DWORD dwSectorPerCluster, dwBytePerSector; DWORD dwFreeCluster, dwTotalCluster; TCHAR szRoot[4]; lstrcpyn(szRoot, lpszFile, 4); if (!GetDiskFreeSpace( szRoot, &dwSectorPerCluster, &dwBytePerSector, &dwFreeCluster, &dwTotalCluster)) return HRESULT_FROM_WIN32(GetLastError()); DWORD dwClusterSize = dwSectorPerCluster * dwBytePerSector; *lpSize = ((*lpSize/dwClusterSize) * dwClusterSize + (*lpSize % dwClusterSize ? dwClusterSize : 0)); return S_OK; } // Return S_OK is lpszCLSID is in ModuleUsage section of lpszFileName. // Return E_... otherwise. // lpszCLSID can be NULL, in this case it does not search for the CLSID. // If lpszOwner is not NULL, it must point to a buffer which will be // used to store the owner of the ModuleUsage section for lpszFileName // dwOwnerSize is the size of the buffer pointed to by lpszOwner . HRESULT LookUpModuleUsage( LPCTSTR lpszFileName, LPCTSTR lpszCLSID, LPTSTR lpszOwner /* = NULL */, DWORD dwOwnerSize /* = 0 */) { HKEY hkey = NULL, hkeyMod = NULL; HRESULT hr = S_OK; TCHAR szBuf[MAX_PATH]; LONG lResult = ERROR_SUCCESS; if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_MODULE_USAGE, 0, KEY_READ, &hkeyMod)) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto EXITLOOKUPMODULEUSAGE; } if ( OCCGetLongPathName(szBuf, lpszFileName, MAX_PATH) == 0 ) lstrcpyn( szBuf, lpszFileName, MAX_PATH ); szBuf[256] = '\0'; // truncate if longer than 255 ude to win95 registry bug lResult = RegOpenKeyEx( hkeyMod, szBuf, 0, KEY_READ, &hkey); if (lResult != ERROR_SUCCESS) { ReverseSlashes(szBuf); lResult = RegOpenKeyEx(hkeyMod, szBuf, 0, KEY_READ, &hkey); if (lResult != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto EXITLOOKUPMODULEUSAGE; } } // Get owner if requested if (lpszOwner != NULL) { DWORD dwSize = dwOwnerSize; lResult = RegQueryValueEx( hkey, VALUE_OWNER, NULL, NULL, (unsigned char*)lpszOwner, &dwSize); if (lResult != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); lpszOwner[0] = '\0'; goto EXITLOOKUPMODULEUSAGE; } } // see if lpszCLSID is a client of this module usage section if (lpszCLSID != NULL) { lResult = RegQueryValueEx( hkey, lpszCLSID, NULL, NULL, NULL, NULL); if (lResult != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto EXITLOOKUPMODULEUSAGE; } } EXITLOOKUPMODULEUSAGE: if (hkey) RegCloseKey(hkey); if (hkeyMod) RegCloseKey(hkeyMod); return hr; } // ReverseSlashes() takes a string, that's assumed to be pointing to a // valid string and is null-terminated, and reverses all forward slashes // to backslashes and all backslashes to forward slashes. void ReverseSlashes(LPTSTR pszStr) { while (*pszStr) { if (*pszStr == '\\') *pszStr = '/'; else if (*pszStr == '/') *pszStr = '\\'; pszStr++; } } // find the last occurance of ch in string szString TCHAR* ReverseStrchr(LPCTSTR szString, TCHAR ch) { if (szString == NULL || szString[0] == '\0') return NULL; TCHAR *pCh = (TCHAR*)(szString + lstrlen(szString)); for (;pCh != szString && *pCh != ch; pCh--); return (*pCh == ch ? pCh : NULL); } // set the last backslash (or the character offset from that by 1) to NULL // returns S_OK if fine, E_FAIL if last backslash not found HRESULT NullLastSlash(LPTSTR pszString, UINT uiOffset) { LPTSTR pszLastSlash; HRESULT hr; ASSERT(pszString); ASSERT((uiOffset == 0) || (uiOffset == 1)); pszLastSlash = ReverseStrchr(pszString, TEXT('\\')); if (!pszLastSlash) { hr = E_FAIL; } else { *(pszLastSlash + uiOffset) = TEXT('\0'); hr = S_OK; } return hr; } // If lpszGUID is an owner of the module lpszFileName in Module Usage, // remove it, updating the .Owner as necessary. If we remove an owner, // then decrement the SharedDlls count. Never drop the SharedDlls count // below 1 if the owner is 'Unknown Owner'. // If modules usage drops to zero, remove MU. If SharedDlls count drops // to zero, remove that value. // Return the resulting owner count. DWORD SubtractModuleOwner( LPCTSTR lpszFileName, LPCTSTR lpszGUID ) { LONG cRef = 0; HRESULT hr = S_OK; LONG lResult; HKEY hkeyMU = NULL; HKEY hkeyMod = NULL; TCHAR szBuf[MAX_PATH]; TCHAR szOwner[MAX_PATH]; DWORD dwSize = MAX_PATH; BOOL bHasUnknownOwner; BOOL bGUIDIsOwner; Assert(lpszFileName != NULL); Assert(lpszGUID != NULL); // Get the current ref count, passing -1 to set is a get. Go figure. hr = SetSharedDllsCount( lpszFileName, -1, &cRef ); if ( FAILED(hr) ) return 1; // in event of failure, say something safe // check if Usage section is present for this dll // open the file's section we are concerned with if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_MODULE_USAGE, 0, KEY_ALL_ACCESS, &hkeyMU)) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto ExitSubtractModuleOwner; } if ( OCCGetLongPathName(szBuf, lpszFileName, MAX_PATH) == 0 ) lstrcpyn( szBuf, lpszFileName, MAX_PATH ); szBuf[256] = '\0'; // truncate if longer than 255 ude to win95 registry bug ReverseSlashes(szBuf); // open section for szFileName under ModuleUsage if ((lResult = RegOpenKeyEx( hkeyMU, szBuf, 0, KEY_ALL_ACCESS, &hkeyMod)) != ERROR_SUCCESS) { goto ExitSubtractModuleOwner; } dwSize = MAX_PATH; if ((lResult = RegQueryValueEx( hkeyMod, VALUE_OWNER, NULL, NULL, (unsigned char*)szOwner, &dwSize)) != ERROR_SUCCESS) { goto ExitSubtractModuleOwner; } bHasUnknownOwner = lstrcmp( szOwner, MODULE_UNKNOWN_OWNER ) == 0; bGUIDIsOwner = lstrcmp( szOwner, lpszGUID ) == 0; // remove the owner value entry, if any. lResult = RegDeleteValue(hkeyMod, lpszGUID); // if this worked, then we'll need to drop the SharedDlls count, // being careful not to let it fall below 1 if bHasUnknownOwner if ( lResult == ERROR_SUCCESS ) { if ( !bHasUnknownOwner || cRef > 1 ) SetSharedDllsCount( lpszFileName, --cRef, NULL ); if ( cRef > 0 && bGUIDIsOwner ) { DWORD dwEnumIndex; // lpszGUID was the .Owner, now that it's gone, replace it // with another owner for ( dwEnumIndex = 0, dwSize = MAX_PATH; RegEnumValue(hkeyMod, dwEnumIndex, (char *)szOwner, &dwSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS; dwEnumIndex++, dwSize = MAX_PATH ) { if (szOwner[0] != '.') { lResult = RegSetValueEx( hkeyMod,VALUE_OWNER, 0, REG_SZ, (LPBYTE)szOwner, (lstrlen( szOwner ) + 1) * sizeof(TCHAR) ); break; // we've done our job } } // for find a new owner } // if there are still owners, but we've nuked the owner of record. else if ( cRef == 0 ) { // that was the last ref, so nuke the MU entry RegCloseKey( hkeyMod ); hkeyMod = NULL; RegDeleteKey( hkeyMU, szBuf ); // note - we assume this key has no subkeys. // Take out the shared DLL's value HKEY hkey; lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_SHAREDDLLS, 0, KEY_ALL_ACCESS, &hkey); if ( lResult == ERROR_SUCCESS ) { ReverseSlashes(szBuf); // revert to file sys lResult = RegDeleteValue( hkey, szBuf ); RegCloseKey( hkey ); } // if opened SharedDlls } // else last reference } // if removed an owner ExitSubtractModuleOwner: if (hkeyMU) RegCloseKey(hkeyMU); if (hkeyMod) RegCloseKey(hkeyMod); return cRef; } // Set manually the count in SharedDlls. // If dwCount is < 0, nothing is set. // If pdwOldCount is non-null, the old count is returned HRESULT SetSharedDllsCount( LPCTSTR lpszFileName, LONG cRef, LONG *pcRefOld /* = NULL */) { HRESULT hr = S_OK; LONG lResult = ERROR_SUCCESS; DWORD dwSize = 0; HKEY hkey = NULL; Assert(lpszFileName != NULL); if (lpszFileName == NULL) { hr = HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS); goto EXITSETSHAREDDLLSCOUNT; } if (cRef < 0 && pcRefOld == NULL) { goto EXITSETSHAREDDLLSCOUNT; } // open HKLM, Microsoft\Windows\CurrentVersion\SharedDlls lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_SHAREDDLLS, 0, KEY_ALL_ACCESS, &hkey); if (lResult != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto EXITSETSHAREDDLLSCOUNT; } // if pdwOldCount is not NULL, save the old count in it if (pcRefOld != NULL) { dwSize = sizeof(DWORD); lResult = RegQueryValueEx( hkey, lpszFileName, 0, NULL, (unsigned char*)pcRefOld, &dwSize); if (lResult != ERROR_SUCCESS) { *pcRefOld = 0; hr = S_FALSE; goto EXITSETSHAREDDLLSCOUNT; } } // if dwCount >= 0, set it as the new count if (cRef >= 0) { lResult = RegSetValueEx( hkey, lpszFileName, 0, REG_DWORD, (unsigned char*)&cRef, sizeof(DWORD)); if (lResult != ERROR_SUCCESS) { hr = S_FALSE; goto EXITSETSHAREDDLLSCOUNT; } } EXITSETSHAREDDLLSCOUNT: if (hkey != NULL) RegCloseKey(hkey); return hr; } // UnregisterOCX() attempts to unregister a DLL or OCX by calling LoadLibrary // and then DllUnregisterServer if the LoadLibrary succeeds. This function // returns TRUE if the DLL or OCX could be unregistered or if the file isn't // a loadable module. HRESULT UnregisterOCX(LPCTSTR pszFile) { HINSTANCE hLib; HRESULT hr = S_OK; HRESULT (FAR STDAPICALLTYPE * pUnregisterEntry)(void); hLib = LoadLibrary(pszFile); if (hLib == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); } else { (FARPROC &) pUnregisterEntry = GetProcAddress( hLib, "DllUnregisterServer" ); if (pUnregisterEntry != NULL) { hr = (*pUnregisterEntry)(); } FreeLibrary(hLib); } return hr; } // Return S_OK if dll can be removed, or S_FALSE if it cannot. // Return E_... if an error has occured HRESULT UpdateSharedDlls(LPCTSTR szFileName, BOOL bUpdate) { HKEY hkeySD = NULL; HRESULT hr = S_OK; DWORD dwType; DWORD dwRef = 1; DWORD dwSize = sizeof(DWORD); LONG lResult; // get the main SHAREDDLLS key ready; this is never freed! if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_SHAREDDLLS, 0, KEY_ALL_ACCESS, &hkeySD)) != ERROR_SUCCESS) { hkeySD = NULL; hr = HRESULT_FROM_WIN32(lResult); goto ExitUpdateSharedDlls; } // now look for szFileName lResult = RegQueryValueEx(hkeySD, szFileName, NULL, &dwType, (unsigned char*)&dwRef, &dwSize); if (lResult != ERROR_SUCCESS) { hr = S_FALSE; goto ExitUpdateSharedDlls; } // decrement reference count by 1. // // if (count equals to 0) // if (bUpdate is TRUE) // remove the key from SharedDlls // return S_OK // otherwise // if (bUpdate is TRUE) // update the count // return S_FALSE if ((--dwRef) > 0) { hr = S_FALSE; if (bUpdate && (lResult = RegSetValueEx(hkeySD, szFileName, 0, REG_DWORD, (unsigned char *)&dwRef, sizeof(DWORD))) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); } goto ExitUpdateSharedDlls; } Assert(dwRef == 0); // remove entry from SharedDlls if (bUpdate && (lResult = RegDeleteValue(hkeySD, szFileName)) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto ExitUpdateSharedDlls; } ExitUpdateSharedDlls: if (hkeySD) RegCloseKey(hkeySD); return hr; } void RemoveList(LPCLSIDLIST_ITEM lpListHead) { LPCLSIDLIST_ITEM lpItemPtr = NULL; while (TRUE) { lpItemPtr = lpListHead; if (lpItemPtr == NULL) break; lpListHead = lpItemPtr->pNext; delete lpItemPtr; } lpListHead = NULL; } BOOL ReadInfFileNameFromRegistry( LPCTSTR lpszCLSID, LPTSTR lpszInf, LONG nBufLen) { if (lpszCLSID == NULL || lpszInf == NULL) return FALSE; HKEY hkey = NULL; TCHAR szKey[100]; LONG lResult = ERROR_SUCCESS; CatPathStrN( szKey, HKCR_CLSID, lpszCLSID, 100); CatPathStrN( szKey, szKey, INFFILE, 100); if (RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, KEY_READ, &hkey) != ERROR_SUCCESS) return FALSE; lResult = RegQueryValue(hkey, NULL, lpszInf, &nBufLen); RegCloseKey(hkey); if (lResult != ERROR_SUCCESS) { lpszInf[0] = '\0'; } if (lpszInf[0] == '\0') return FALSE; return TRUE; } BOOL WriteInfFileNameToRegistry(LPCTSTR lpszCLSID, LPTSTR lpszInf) { if (lpszCLSID == NULL) return FALSE; HKEY hkey = NULL; LONG lResult = ERROR_SUCCESS; TCHAR szKey[100]; CatPathStrN(szKey, HKCR_CLSID, lpszCLSID, 100); CatPathStrN(szKey, szKey, INFFILE, 100); if (RegCreateKey(HKEY_CLASSES_ROOT, szKey, &hkey) != ERROR_SUCCESS) return FALSE; lResult = RegSetValue( hkey, NULL, REG_SZ, (lpszInf == NULL ? TEXT("") : lpszInf), (lpszInf == NULL ? 0 : lstrlen(lpszInf))); RegCloseKey(hkey); return (lResult == ERROR_SUCCESS); } // Define a macro to make life easier #define QUIT_IF_FAIL if (FAILED(hr)) goto Exit HRESULT ExpandVar( LPCSTR& pchSrc, // passed by ref! LPSTR& pchOut, // passed by ref! DWORD& cbLen, // passed by ref! DWORD cbBuffer, const char * szVars[], const char * szValues[]) { HRESULT hr = S_FALSE; int cbvar = 0; Assert (*pchSrc == '%'); for (int i=0; szVars[i] && (cbvar = lstrlen(szVars[i])) ; i++) { // for each variable int cbneed = 0; if ( (szValues[i] == NULL) || !(cbneed = lstrlen(szValues[i]))) continue; cbneed++; // add for nul if (0 == strncmp(szVars[i], pchSrc, cbvar)) { // found something we can expand if ((cbLen + cbneed) >= cbBuffer) { // out of buffer space *pchOut = '\0'; // term hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); goto Exit; } lstrcpy(pchOut, szValues[i]); cbLen += (cbneed -1); //don't count the nul pchSrc += cbvar; // skip past the var in pchSrc pchOut += (cbneed -1); // skip past dir in pchOut hr = S_OK; goto Exit; } } Exit: return hr; } // from urlmon\download\hooks.cxx (ExpandCommandLine and ExpandVars) // used to expand variables HRESULT ExpandCommandLine( LPCSTR szSrc, LPSTR szBuf, DWORD cbBuffer, const char * szVars[], const char * szValues[]) { Assert(cbBuffer); HRESULT hr = S_FALSE; LPCSTR pchSrc = szSrc; // start parsing at begining of cmdline LPSTR pchOut = szBuf; // set at begin of out buffer DWORD cbLen = 0; while (*pchSrc) { // look for match of any of our env vars if (*pchSrc == '%') { HRESULT hr1 = ExpandVar(pchSrc, pchOut, cbLen, // all passed by ref! cbBuffer, szVars, szValues); if (FAILED(hr1)) { hr = hr1; goto Exit; } if (hr1 == S_OK) { // expand var expanded this hr = hr1; continue; } } // copy till the next % or nul if ((cbLen + 1) < cbBuffer) { *pchOut++ = *pchSrc++; cbLen++; } else { // out of buffer space *pchOut = '\0'; // term hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); goto Exit; } } *pchOut = '\0'; // term Exit: return hr; } // Find dependent DLLs in ModuleUsage // given the clsid it will enumerate all the DLLs in the ModuleUsage // that were used by this clsid HRESULT FindDLLInModuleUsage( LPTSTR lpszFileName, LPCTSTR lpszCLSID, DWORD &iSubKey) { HKEY hkey = NULL, hkeyMod = NULL; HRESULT hr = S_OK; TCHAR szBuf[MAX_PATH]; LONG lResult = ERROR_SUCCESS; if (lpszCLSID == NULL) { hr = E_INVALIDARG; // req clsid goto Exit; } // get the main MODULEUSAGE key if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_MODULE_USAGE, 0, KEY_READ, &hkeyMod)) != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); goto Exit; } while ( ((lResult = RegEnumKey( hkeyMod, iSubKey++, szBuf, MAX_PATH)) == ERROR_SUCCESS) ) { lResult = RegOpenKeyEx( hkeyMod, szBuf, 0, KEY_READ, &hkey); if (lResult != ERROR_SUCCESS) break; // see if lpszCLSID is a client of this module usage section lResult = RegQueryValueEx( hkey, lpszCLSID, NULL, NULL, NULL, NULL); if (lResult == ERROR_SUCCESS) { // got the filename, return it lstrcpy(lpszFileName, szBuf); goto Exit; } if (hkey) { RegCloseKey(hkey); hkey = NULL; } } // while if (lResult != ERROR_SUCCESS) { hr = HRESULT_FROM_WIN32(lResult); } Exit: if (hkey) RegCloseKey(hkey); if (hkeyMod) RegCloseKey(hkeyMod); return hr; } BOOL PatternMatch(LPCTSTR szModName, LPTSTR szSectionName) { LPCTSTR pch = ReverseStrchr(szModName, '/'); DWORD len = 0; if (!pch) pch = szModName; else pch++; // pch points at base name of module if ((len = lstrlen(pch)) != (DWORD)lstrlen(szSectionName)) return FALSE; LPTSTR pchSecStar = StrChr(szSectionName, '*'); Assert(pchSecStar); DWORD cbLen1 = (DWORD) (pchSecStar - szSectionName); // compare upto '*' if (StrCmpNI(szSectionName, pch, cbLen1) != 0) return FALSE; // compare after the 3 stars if ( (cbLen1 + 3) < len) // *s not at end if (StrCmpNI(pchSecStar+3, pch + (cbLen1+3), len -(cbLen1+3)) != 0) return FALSE; // simlar strings but for the stars. // modify the szSectionName to hold the value for the stars // in-effect this will substitute the original variable with // the value that was used when installing the OCX lstrcpy(pchSecStar, pch + cbLen1); return TRUE; } DWORD OCCGetLongPathName( LPTSTR szLong, LPCTSTR szShort, DWORD cchBuffer ) { DWORD dwLen = 0; HMODULE hmodUrlMon; CDLGetLongPathNamePtr pfnGetLongPathName = NULL; hmodUrlMon = LoadLibrary( "URLMON.DLL" ); // Set up our globals with short and long versions of the base cache path if ( hmodUrlMon != NULL ) { pfnGetLongPathName = (CDLGetLongPathNamePtr)GetProcAddress(hmodUrlMon, (LPCSTR)STR_CDLGETLONGPATHNAME ); if ( pfnGetLongPathName != NULL ) { dwLen = pfnGetLongPathName( szLong, szShort, cchBuffer ); } FreeLibrary( hmodUrlMon ); } return dwLen; } TCHAR *CatPathStrN( TCHAR *szDst, const TCHAR *szHead, const TCHAR *szTail, int cchDst ) { TCHAR *szRet = szDst; int cchHead = lstrlen(szHead); int cchTail = lstrlen(szTail); if ( cchHead + cchTail >= (cchDst - 2) ) {// - 2 for / and null Assert(FALSE); szRet = NULL; *szDst = 0; } else { // we know the whole thing is safe lstrcpy(szDst, szHead); lstrcpy(&szDst[cchHead], TEXT("\\")); lstrcpy(&szDst[cchHead + 1], szTail); } return szRet; } BOOL IsCanonicalName( LPTSTR szName ) { // simple test - if there's a ~ in it, it has a contraction in it // and is therefore non-canonical for ( ; *szName != '\0' && *szName != '~'; szName++ ); return *szName != '~'; }; struct RegPathName { LPTSTR m_szName; LPTSTR m_szCanonicalName; RegPathName(void) : m_szName(NULL), m_szCanonicalName(NULL) {}; ~RegPathName() { if ( m_szName ) delete m_szName; if ( m_szCanonicalName ) delete m_szCanonicalName; }; void MakeRegFriendly( LPTSTR szName ) { TCHAR *pch; // If szName is going to be a reg key name, we can't have it lookin' like a path for ( pch = szName; *pch != '\0'; pch++ ) if ( *pch == '\\' ) *pch = '/'; } void MakeFileSysFriendly( LPTSTR szName ) { TCHAR *pch; // change the slashes back into DOS // directory \'s for ( pch = szName; *pch != '\0'; pch++ ) if ( *pch == '/' ) *pch = '\\'; } BOOL FSetCanonicalName(void) { BOOL fSet = FALSE; TCHAR *szT = new TCHAR[MAX_PATH]; if ( m_szName != NULL && szT != NULL ) { LPITEMIDLIST pidl = NULL; // WE jump through some hoops to get the all-long // name version of szName. First we convert it to // an ITEMIDLIST. // but first, we must change the slashes back into DOS // directory \'s MakeFileSysFriendly( m_szName ); if ( OCCGetLongPathName( szT, m_szName, MAX_PATH ) != 0 ) { m_szCanonicalName = szT; fSet = TRUE; } else delete [] szT; // restore m_szName to it's registry-friendly form MakeRegFriendly( m_szName ); } // if we can get our temp string if ( fSet ) { // whatever its source, our canonical form has reversed slashes MakeRegFriendly( m_szCanonicalName ); } return fSet; }; BOOL FSetName( LPTSTR szName, int cchName ) { BOOL fSet = FALSE; if ( m_szName != NULL ) { delete m_szName; m_szName = NULL; } if ( m_szCanonicalName != NULL ) { delete m_szCanonicalName; m_szCanonicalName = NULL; } // we got a short name, so szName is the short name m_szName = new TCHAR[cchName + 1]; if ( m_szName != NULL ) { lstrcpy( m_szName, szName ); fSet = FSetCanonicalName(); } return fSet; }; }; struct ModuleUsageKeys : public RegPathName { ModuleUsageKeys *m_pmukNext; HKEY m_hkeyShort; // key with the short file name name ModuleUsageKeys(void) : m_pmukNext(NULL), m_hkeyShort(NULL) {}; ~ModuleUsageKeys(void) { if ( m_hkeyShort ) RegCloseKey( m_hkeyShort ); }; HRESULT MergeMU( HKEY hkeyCanon, HKEY hkeyMU ) { HRESULT hr = E_FAIL; DWORD dwIndex = 0; DWORD cchNameMax; DWORD cbValueMax; if ( RegQueryInfoKey( m_hkeyShort, NULL, NULL, NULL, NULL, NULL, NULL, &dwIndex, &cchNameMax, &cbValueMax, NULL, NULL ) == ERROR_SUCCESS ) { LPTSTR szName = new TCHAR[cchNameMax + 1]; if (szName != NULL) { LPBYTE lpbValue = new BYTE[cbValueMax]; if (lpbValue != NULL) { // Examine each value. for ( dwIndex--, hr = S_OK; (LONG)dwIndex >= 0 && SUCCEEDED(hr); dwIndex-- ) { LONG lResult; DWORD cchName = cchNameMax + 1; DWORD dwType; DWORD dwSize = cbValueMax; // fetch key and value lResult = RegEnumValue( m_hkeyShort, dwIndex, szName, &cchName, 0, &dwType, lpbValue, &dwSize ); if ( lResult == ERROR_SUCCESS ) { // Do not replace if the canonical entry already has a // .Owner value that is "Unknown Owner" if ( lstrcmp( szName, ".Owner" ) == 0 ) { TCHAR szCanonValue[MAX_PATH]; DWORD dwType; DWORD lcbCanonValue = MAX_PATH; if ( RegQueryValueEx( hkeyCanon, ".Owner", NULL, &dwType, (LPBYTE)szCanonValue, &lcbCanonValue ) == ERROR_SUCCESS && lstrcmp( szCanonValue, "Unknown Owner" ) == 0 ) continue; } // Add the value to the canonical version of the key if ( RegSetValueEx( hkeyCanon, szName, NULL, dwType, lpbValue, dwSize ) != ERROR_SUCCESS ) hr = E_FAIL; } else hr = E_FAIL; } // for each value in the non-canoncical key // Now we are finished with the non-canonical key if ( SUCCEEDED(hr) && RegDeleteKey( hkeyMU, m_szName ) != ERROR_SUCCESS ) hr = E_FAIL; } else // lpbValue. { delete [] szName; hr = E_OUTOFMEMORY; } } else // szName { hr = E_OUTOFMEMORY; } } return hr; }; HRESULT MergeSharedDlls( HKEY hkeySD ) { HRESULT hr = E_FAIL; DWORD dwShortVal = 0; DWORD dwCanonicalVal = 0; DWORD dwType; DWORD dwSize; // The value names under shared DLLs are raw paths MakeFileSysFriendly( m_szName ); MakeFileSysFriendly( m_szCanonicalName ); dwSize = sizeof(DWORD); if ( RegQueryValueEx( hkeySD, m_szName, NULL, &dwType, (LPBYTE)&dwShortVal, &dwSize ) == ERROR_SUCCESS && dwType == REG_DWORD ) { dwCanonicalVal = 0; dwSize = sizeof(DWORD); // the canonical form may not be there, so we don't care if this // fails. RegQueryValueEx( hkeySD, m_szCanonicalName, NULL, &dwType, (LPBYTE)&dwCanonicalVal, &dwSize ); dwCanonicalVal += dwShortVal; dwSize = sizeof(DWORD); if ( RegSetValueEx( hkeySD, m_szCanonicalName, NULL, REG_DWORD, (LPBYTE)&dwCanonicalVal, dwSize ) == ERROR_SUCCESS ) { RegDeleteValue( hkeySD, m_szName ); } } else { dwCanonicalVal = 1; dwSize = sizeof(DWORD); if ( RegSetValueEx( hkeySD, m_szCanonicalName, NULL, REG_DWORD, (LPBYTE)&dwCanonicalVal, dwSize ) == ERROR_SUCCESS ) hr = S_OK; } MakeRegFriendly( m_szName ); MakeRegFriendly( m_szCanonicalName ); return hr; } HRESULT CanonicalizeMU( HKEY hkeyMU, HKEY hkeySD ) { HRESULT hr = E_FAIL; HKEY hkeyCanon; LONG lResult = RegOpenKeyEx( hkeyMU, m_szCanonicalName, 0, KEY_ALL_ACCESS, &hkeyCanon); if ( lResult != ERROR_SUCCESS ) lResult = RegCreateKey( hkeyMU, m_szCanonicalName, &hkeyCanon ); if ( lResult == ERROR_SUCCESS ) { hr = MergeMU( hkeyCanon, hkeyMU ); if ( SUCCEEDED(hr) ) hr = MergeSharedDlls( hkeySD ); RegCloseKey( hkeyCanon ); } else hr = E_FAIL; return S_OK; }; }; // FAddModuleUsageKeys adds a module usage key to the list. BOOL FAddModuleUsageKeys( ModuleUsageKeys*&pmuk, // head of ModuleUsageKeys list LPTSTR szName, // name of key value DWORD cchName, // length of szName, minus null terminator HKEY hkeyMU // hkey of parent ) { BOOL fAdd = FALSE; ModuleUsageKeys* pmukNew; HKEY hkeySub = NULL; LRESULT lr; pmukNew = new ModuleUsageKeys; if ( pmukNew && (lr = RegOpenKeyEx( hkeyMU, szName, 0, KEY_ALL_ACCESS, &hkeySub)) == ERROR_SUCCESS ) { fAdd = pmukNew->FSetName( szName, cchName ); if ( fAdd ) { // append to head of the list pmukNew->m_hkeyShort = hkeySub; pmukNew->m_pmukNext = pmuk; pmuk = pmukNew; } } if ( !fAdd ) { if ( hkeySub ) RegCloseKey( hkeySub ); if ( pmukNew != NULL ) delete pmukNew; } return fAdd; } EXTERN_C HRESULT CanonicalizeModuleUsage(void) { HKEY hkeyMU = NULL; HKEY hkeySD = NULL; HRESULT hr = S_OK; LONG lResult; // get the main SHAREDDLLS key ready; this is never freed! if ((lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_SHAREDDLLS, 0, KEY_ALL_ACCESS, &hkeySD)) == ERROR_SUCCESS && (lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, REGSTR_PATH_MODULE_USAGE, 0, KEY_ALL_ACCESS, &hkeyMU)) == ERROR_SUCCESS ) { DWORD dwIndex = 0; ModuleUsageKeys *pmukUpdate = NULL; // records for values we want to update. ModuleUsageKeys *pmuk; ModuleUsageKeys *pmukNext; // Examine each value. do { TCHAR szName[MAX_PATH]; // Value name DWORD cchName = MAX_PATH; FILETIME ftT; // fetch key and value lResult = RegEnumKeyEx( hkeyMU, dwIndex, szName, &cchName, 0, NULL, NULL, &ftT ); if ( lResult == ERROR_SUCCESS ) { if ( !IsCanonicalName( szName ) ) if ( !FAddModuleUsageKeys( pmukUpdate, szName, cchName, hkeyMU ) ) hr = E_OUTOFMEMORY; dwIndex++; } else if ( lResult == ERROR_NO_MORE_ITEMS ) hr = S_FALSE; else hr = E_FAIL; } while ( hr == S_OK ); if ( SUCCEEDED(hr) ) { hr = S_OK; // don't need S_FALSE any longer for ( pmuk = pmukUpdate; pmuk != NULL; pmuk = pmukNext ) { HRESULT hr2 = pmuk->CanonicalizeMU( hkeyMU, hkeySD ); if ( FAILED(hr2) ) hr = hr2; pmukNext = pmuk->m_pmukNext; delete pmuk; } // for } // if enumeration succeeded } // if keys opened if (hkeyMU) RegCloseKey(hkeyMU); if ( hkeySD ) RegCloseKey( hkeySD ); return hr; }