#include "general.h"
#include "ParseInf.h"
#include "resource.h"
#include "FileNode.h"
#include <shlwapi.h>
#include <shlobj.h>


//#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;
}

