/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: uninstall.cpp,v 1.1.2.1 2004/07/09 02:03:00 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hxtypes.h"
#if !(defined(_MSC_VER) && (_MSC_VER > 1100))
typedef int INT_PTR; // VC5 needs this, VC6 gets it from hxbastsd.h
#endif

#include <windows.h>
#include <stdio.h>
#include <direct.h>
#include <sys/stat.h>
#include <commctrl.h>

#include "wininstlib.h"
#include "uninstdefs.h"
#include "uninstall.h"
#include "resource.h"


INT_PTR CALLBACK
ProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_INITDIALOG:
            return TRUE;
    }

    return FALSE;
}


int WINAPI 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   LPSTR lpCmdLine, int nCmdShow)
{
    char szLog[MAX_PATH + 1];
    szLog[0] = '\0';
    UINT32 ulFlags = 0;

    if(lpCmdLine)
    {
        BOOL bInQuote = FALSE;        
        char* szOption = lpCmdLine;        
        char* szOptionEnd;
        UINT uSize;

        for(;*szOption != '\0' && isspace(*szOption); szOption++);
        
        while(*szOption != '\0')
        {
            if(*szOption == '\"')
            {
                szOption++;
                for(szOptionEnd = szOption; 
                    *szOptionEnd != '\0' && *szOptionEnd != '\"'; 
                    szOptionEnd++);
            }
            else
            {
                for(szOptionEnd = szOption;
                    szOptionEnd != '\0' && !isspace(*szOptionEnd);
                    szOptionEnd++);
            }

            uSize = szOptionEnd - szOption;

            if(strncmp(szOption, "-s", uSize) == 0 ||
                strncmp(szOption, "--silent", uSize) == 0 || 
                strncmp(szOption, "/s", uSize) == 0)
            {
                ulFlags |= INST_SILENT | INST_NON_INTERACTIVE;
            }
            else if(strncmp(szOption, "-p", uSize) == 0 ||
                    strncmp(szOption, "--progress-only", uSize) == 0)
            {
                ulFlags |= INST_NON_INTERACTIVE;
            }
            else if(*szOption != '-')
            {
                uSize = min(uSize, MAX_PATH);
                strncpy(szLog, szOption, uSize);
                szLog[uSize] = '\0';
            }

            for(szOption = *szOptionEnd == '\"' ? 
                szOptionEnd + 1 : szOptionEnd;
                *szOption != '\0' && isspace(*szOption); 
                szOption++);
        }
    }

    if(szLog[0] == '\0')
    {
        strcpy(szLog, "install.log");
    }

    CUninstaller uninst(hInstance);
    uninst.Uninstall(szLog, ulFlags);

    return 0;
}

CStringStack::~CStringStack() 
{
    SNode* pHead;
    while(m_pHead)
    {
        pHead = m_pHead;
        m_pHead = pHead->pNext;
        HX_VECTOR_DELETE(pHead->szString);            
        delete pHead;
    }
}

void 
CStringStack::Push(char* szVal)
{ 
    SNode* pNode = new SNode(szVal);
    pNode->pNext = m_pHead;
    if(m_pHead)
    {
        m_pHead->pBack = pNode;
    }
    
    m_pHead = pNode;
}

char* 
CStringStack::Pop() 
{ 
    if(!m_pHead)
    {
        return NULL;
    }

    SNode* pNode = m_pHead;
    char* szData = pNode->szString;

    m_pHead = m_pHead->pNext;
    if(m_pHead)
    {
        m_pHead->pBack = NULL;
    }

    delete pNode;
    return szData;
}

char*
CStringStack::Top()
{
    if(!m_pHead)
    {
        return NULL;
    }

    return m_pHead->szString;
}

STACKPOS
CStringStack::Find(const char* szVal)
{
    for(SNode* pNode = m_pHead; pNode != NULL; pNode = pNode->pNext)
    {
        if(stricmp(szVal, pNode->szString) == 0)
        {
            return pNode;
        }            
    }

    return NULL;
}

char*
CStringStack::Remove(STACKPOS pNode)
{
    if(m_pHead == pNode)
    {
        m_pHead = pNode->pNext;
    }
    if(pNode->pNext)
    {
        pNode->pNext->pBack = pNode->pBack;
    }
    if(pNode->pBack)
    {
        pNode->pBack->pNext = pNode->pNext;
    }

    char* szData = pNode->szString;
    delete[] pNode;

    return szData;
}

char*
CStringStack::FindAndRemove(const char* szVal)
{
    STACKPOS pos = Find(szVal);
    return pos ? Remove(pos) : NULL;
}

CUninstaller::CUninstaller(HINSTANCE hInstance) : 
    m_fpLog(NULL),
    m_szLog(NULL),
    m_ulInstFlags(0),
    m_ulItems(0),
    m_ulTotalItems(0),
    m_hInstance(hInstance),
    m_hwndDlg(NULL)
{
    m_szTitle[0] = '\0';
    m_szAppPath[0] = '\0';
    m_szUninstPath[0] = '\0';
}

CUninstaller::~CUninstaller()
{
    if(m_fpLog)
    {
        fclose(m_fpLog);
    }
}

/*
 * CUninstaller::Uninstall - does the whole uninstall
 */
void
CUninstaller::Uninstall(const char* szLog, UINT32 ulFlags)
{
    char szErr[MAX_PATH + 128];
    m_ulInstFlags = ulFlags;
    m_szLog = szLog;

    // Get the uninstaller path
    if(GetModuleFileName(NULL, m_szUninstPath, MAX_PATH+1))
    {
        // Get the short name
        GetShortPathName(m_szUninstPath, m_szUninstPath, MAX_PATH+1);
    }

    // Open the log file
    m_fpLog = fopen(m_szLog, "r");
    if(!m_fpLog)
    {
        if(!(m_ulInstFlags & INST_NON_INTERACTIVE))
        { 
            sprintf(szErr, STR_ERR_NO_LOG, m_szLog);
            MessageBox(NULL, szErr, NULL, MB_OK);
        }

        return;
    }

    // Get the title and app path
    if(!GetTitle())
    {
        if(!(m_ulInstFlags & INST_NON_INTERACTIVE))
        {
            sprintf(szErr, STR_ERR_BAD_LOG, m_szLog);
            MessageBox(NULL, szErr, NULL, MB_OK);
        }

        fclose(m_fpLog);
        m_fpLog = NULL;
        return;
    }

    // Ask the user ....
    if(!(m_ulInstFlags & INST_NON_INTERACTIVE) && !Confirm())
    {
        fclose(m_fpLog);
        m_fpLog = NULL;
        return;
    }

    // Check if the app is writeable. If not, warn the user to exit it.
    if(m_szAppPath)
    {        
        if(!CheckAppPath(m_szAppPath) && 
            ((m_ulInstFlags & INST_NON_INTERACTIVE) || 
            !WarnRunning()))
        {
            fclose(m_fpLog);
            m_fpLog = NULL;
            return;
        }
    }

    GetNumItems();

    // Display the progress
    if(!(m_ulInstFlags & INST_SILENT))
    {
        ShowProgress();
    }

    // Services must be removed before files, since a file might
    // be in use by a service
    RemoveServices();
    // Remove everything else
    RemoveAll();

    CloseProgress();
    if(!(m_ulInstFlags & INST_NON_INTERACTIVE))
    {
        ShowDoneMessage();
    }
}

/*
 * CUninstaller::GetTitle - gets the title item (e.g. "Helix Server")
 * from the log file
 */
BOOL
CUninstaller::GetTitle()
{
    char* szTitleStart = NULL;
    SInstItem* pItem;

    fseek(m_fpLog, 0, SEEK_SET);

    // Search for title
    while((m_szTitle[0] == '\0' || m_szAppPath[0] == '\0') && 
            GetNextItem(pItem))
    {
        if(m_szTitle[0] == '\0' && stricmp(pItem->szName, UNINST_TITLE) == 0)
        {
            strncpy(m_szTitle, pItem->szValue, READ_SIZE-1);
            m_szTitle[READ_SIZE-1] = '\0';
        }
        else if(m_szAppPath[0] == '\0' && 
                stricmp(pItem->szName, UNINST_APP_PATH) == 0)
        {
            strncpy(m_szAppPath, pItem->szValue, MAX_PATH);
            m_szAppPath[MAX_PATH] = '\0';
        }
        
        delete pItem;
    }

    return m_szTitle[0] == '\0' ? FALSE : TRUE;
}

/*
 * CUninstaller::CheckAppPath - Returns FALSE if we can't write to the app
 * path, so we can warn the user if they are uninstalling a running app.
 */
BOOL
CUninstaller::CheckAppPath(const char* szAppPath)
{
    // Check if we can write to the app path
    WIN32_FIND_DATA findData;
    HANDLE hFindFile = FindFirstFile(szAppPath, &findData);
    if(hFindFile != INVALID_HANDLE_VALUE)
    {
        FindClose(hFindFile);

        HANDLE hApp = CreateFile(szAppPath, GENERIC_WRITE, 0, NULL, 
            OPEN_EXISTING, 0, NULL);
        if(hApp == INVALID_HANDLE_VALUE)
        {
            return FALSE;
        }
        
        CloseHandle(hApp);
    }

    return TRUE;
}

/*
 * CUninstaller::Confirm - Displays a dummy dialog, asking the user 
 * if they really want to uninstall
 */
BOOL
CUninstaller::Confirm()
{
    char szText[READ_SIZE + 512];
    char szCaption[READ_SIZE + 64];
    sprintf(szText, STR_CONFIRM, m_szTitle);
    sprintf(szCaption, STR_CAPTION, m_szTitle);

    BOOL res = MessageBox(NULL, szText, szCaption, 
        MB_OKCANCEL | MB_ICONEXCLAMATION) == IDOK ? TRUE : FALSE;

    return res;
}

/*
 * CUninstaller::GetNextItem - Parses the next item out of the log
 */
BOOL
CUninstaller::GetNextItem(SInstItem*& pItem)
{
    char szLine[READ_SIZE];
    pItem = NULL;
    if(!fgets(szLine, READ_SIZE, m_fpLog) || feof(m_fpLog))
    {
        return FALSE;
    }
    
    char* szName;

    // Trim whitespace
    for(szName = szLine; *szName != '\0' && isspace(*szName); szName++);
    
    if(*szName == '#')
    {
        // Comment. Get next line.
        return GetNextItem(pItem);
    }

    // Get the item name.
    char* szNameEnd = strchr(szName, ':');
    if(szNameEnd < szName)
    {
        // No item name. Try the next line
        return GetNextItem(pItem);
    }

    // Find beginning of value.
    char* szValue;
    for(szValue = szNameEnd + 1; *szValue != '\0' && isspace(*szValue); szValue++);
    
    UINT uNameLen = szNameEnd - szName;
    UINT uValLen = strlen(szValue);

    if(uNameLen == 0 || uValLen == 0)
    {
        // Missing name or value. Go on to the next line
        return GetNextItem(pItem);
    }

    // Trim trailing whitespace
    for(;isspace(szValue[uValLen-1]);uValLen--);

    pItem = new SInstItem;
    if(!pItem)
    {
        // No memory!!
        return FALSE;
    }
    
    pItem->szName = new char[uNameLen + 1];
    pItem->szValue = new char[uValLen + 1];

    if(!pItem->szName || !pItem->szValue)
    {
        // no memory!
        HX_DELETE(pItem);
        return FALSE;
    }

    // Copy the name and value
    strncpy(pItem->szName, szName, uNameLen);
    pItem->szName[uNameLen] = '\0';    
    strncpy(pItem->szValue, szValue, uValLen);
    pItem->szValue[uValLen] = '\0';

    return TRUE;
}

/*
 * CUninstaller::RemoveServices - looks for all service items and stops
 * and removes them.
 */
void
CUninstaller::RemoveServices()
{
    fseek(m_fpLog, 0, SEEK_SET);

    SInstItem* pItem;
    while(GetNextItem(pItem))
    {
        if(stricmp(pItem->szName, UNINST_SERVICE) == 0)
        {
            UpdateDlgText(STR_DEL_SERVICE);
            RemoveService(pItem->szValue);
            m_ulItems++;
            SetProgress((UINT16)
                (((double)m_ulItems / (double)m_ulTotalItems) * 100));
        }
        delete pItem;
    }
}

/*
 * CUninstaller::RemoveAll - Removes everything that has not yet
 * been handled - files, reg entries, directories
 */
void
CUninstaller::RemoveAll()
{
    fseek(m_fpLog, 0, SEEK_SET);

    // Traverse the log.
    SInstItem* pItem;
    GetNextItem(pItem);
    while(pItem)
    {        
        m_ulItems++;
        SetProgress((UINT16)
            (((double) m_ulItems / (double) m_ulTotalItems) * 100));
        pItem = HandleItem(pItem);
    }

    // Close the log file and delete it
    fclose(m_fpLog);
    m_fpLog = 0;

    UpdateDlgText(STR_DEL_FILE);
    RemoveInstallFile(m_szLog);    

    // Try to change to the system directory, so we can
    // delete '.'
    char szSysDir[MAX_PATH+1];
    UINT uLen = GetSystemDirectory(szSysDir, MAX_PATH);
    if(uLen && uLen <= MAX_PATH)
    {
        chdir(szSysDir);
    }

    // Now remove all the directories and reg keys
    char* szVal;
    while(szVal = m_Dirs.Pop())
    {
        RemoveInstDirectory(szVal);        
        m_ulItems++;
        SetProgress((UINT16)
            (((double) m_ulItems / (double) m_ulTotalItems) * 100));
        delete[] szVal;
    }

    while(szVal = m_RegKeys.Pop())
    {
        RemoveRegTree(szVal, FALSE);
        m_ulItems++;
        SetProgress((UINT16)
            (((double) m_ulItems / (double) m_ulTotalItems) * 100));
        delete[] szVal;
    }

    SetProgress(100);
}

/*
 * CUninstaller::HandleItem - removes or otherwise handles pItem
 */
SInstItem*
CUninstaller::HandleItem(SInstItem* pItem)
{
    SInstItem* pNextItem = NULL;
    if(stricmp(pItem->szName, UNINST_FILE) == 0)
    {
        // delete the file
        UpdateDlgText(STR_DEL_FILE);
        RemoveInstallFile(pItem->szValue);
    }
    else if(stricmp(pItem->szName, UNINST_MULTIFILE) == 0)
    {
        // delete the files 
        UpdateDlgText(STR_DEL_FILE);
        RemoveMultipleFiles(pItem->szValue);            
    }
    else if(stricmp(pItem->szName, UNINST_SHFILE) == 0)
    {
        // deref and delete if appropriate
        UpdateDlgText(STR_DEL_FILE);
        RemoveSharedFile(pItem->szValue);            
    }
    else if(stricmp(pItem->szName, UNINST_DIRECTORY) == 0)
    {
        // Queue for deletion        
        char* szDir = new char[MAX_PATH+1];
        strncpy(szDir, pItem->szValue, MAX_PATH);
        szDir[MAX_PATH] = '\0';
        if(!m_Dirs.Find(szDir))
        {
            m_Dirs.Push(szDir);
        }
    }
    else if(stricmp(pItem->szName, UNINST_REGKEY) == 0)
    {
        // Remove values and key if empty
        UpdateDlgText(STR_DEL_REG);
        pNextItem = RemoveRegKeyVals(pItem->szValue);
    }
    else if (stricmp(pItem->szName, UNINST_REGTREE) == 0)
    {
        // Remove the entire tree!
        UpdateDlgText(STR_DEL_REG);
        RemoveRegTree(pItem->szValue, TRUE);
    }

    delete pItem;

    if(!pNextItem)
    {
        GetNextItem(pNextItem);
    }

    return pNextItem;
}

/*
 * CUninstaller::RemoveInstallFile - Removes szFile
 */
void
CUninstaller::RemoveInstallFile(const char* szFile)
{
    // Check if the file exists and try to remove it
    struct stat info;
    if(stat(szFile, &info) != 0 || DeleteFile(szFile))
    {
        return;
    }

    // If it exists and we can't delete it, then check if it's this exe
    char szPath[MAX_PATH+1];  
    UINT32 ulLen = GetShortPathName(szFile, szPath, MAX_PATH+1);

    if(ulLen && ulLen <= MAX_PATH && 
        stricmp(szPath, m_szUninstPath) == 0)
    {
        // Move it to a temp file.
        char szTempDir[MAX_PATH+1];
        ulLen = GetTempPath(MAX_PATH+1, szTempDir);

        if(ulLen && ulLen <= MAX_PATH && 
            GetTempFileName(szTempDir, "hxsetup", 0, szPath))
        {
            DeleteFile(szPath);
            if(MoveFile(szFile, szPath))
            {
                // Mark the temp file for removal on reboot
                ReplaceOnReboot(szPath, NULL);
            }
        }
    }
}

/*
 * CUninstaller::RemoveMultipleFiles - Removes files matching the
 * wildcard description
 */
void 
CUninstaller::RemoveMultipleFiles(const char* szFileDescriptor)
{
    WIN32_FIND_DATA fd;
    HANDLE hFind = FindFirstFile(szFileDescriptor, &fd);
    if(hFind == INVALID_HANDLE_VALUE)
    {
        // No matching file
        return;
    }

    char* szDirEnd = strrchr(szFileDescriptor, OS_SEPARATOR_CHAR);
    if(szDirEnd <= szFileDescriptor)
    {
        FindClose(hFind);
        return;
    }

    char szFullPath[MAX_PATH+1];
    char* pFileName;

    UINT uLen = min(szDirEnd-szFileDescriptor, MAX_PATH - 1);
    UINT uFNameSize = MAX_PATH - uLen - 1;
    strncpy(szFullPath, szFileDescriptor, uLen+1);
    pFileName = &szFullPath[uLen+1];

    // Delete all files
    do
    {
        // Don't delete "." or ".."
        if(strcmp(fd.cFileName, ".") != 0 && strcmp(fd.cFileName, "..") != 0
            && !(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
        {
            strncpy(pFileName, fd.cFileName, uFNameSize);
            pFileName[uFNameSize] = '\0';
            RemoveInstallFile(szFullPath);
        }
    } while(FindNextFile(hFind, &fd));

    FindClose(hFind);
}

/*
 * CUninstaller::RemoveSharedFile - decrements the ref count, and
 * removes the file and ref count if appropriate, of szFileName.
 */
void
CUninstaller::RemoveSharedFile(const char* szFileName)
{
    HKEY hKey;
    DWORD dwType;
    DWORD dwSize;
    DWORD dwRefCount = 0;

    // Open the key
    if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, SZ_SHARED_FILES_KEY, 0, 
            KEY_EXECUTE | KEY_SET_VALUE, &hKey) != ERROR_SUCCESS)
    {
        // Can't access shared files key! Just leave the file alone.
        return;
    }

    // Get the value
    dwSize = sizeof(DWORD);
    if(RegQueryValueEx(hKey, szFileName, NULL, &dwType,
            (LPBYTE)&dwRefCount, &dwSize) != ERROR_SUCCESS)
    {
        // If we couldn't find a ref count, leave the file and set to 1
        dwRefCount = 2;
    }

    if(dwRefCount > 1)
    {
        // decrement the ref count
        dwRefCount--;
        RegSetValueEx(hKey, szFileName, 0, REG_DWORD, 
            (LPBYTE)&dwRefCount, sizeof(DWORD));
    }
    else
    {
        // no more refs. delete the file and ref count.
        RegDeleteValue(hKey, szFileName);
        RemoveInstallFile(szFileName);
    }

    RegCloseKey(hKey);
}

/*
 * CUninstaller::RemoveService - Stops the service if it is running
 * and deletes it.
 */
void
CUninstaller::RemoveService(const char* szServiceName)
{
    SC_HANDLE schManager;
    SC_HANDLE schService;
    
    // Open service control manager
    schManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
    if(!schManager)
    {
        return;
    }

    // Open the service entry
    schService = OpenService(schManager, szServiceName, SERVICE_STOP | DELETE);
    if(schService != NULL)
    {
        SERVICE_STATUS ss;

        // Stop the service
        ControlService(schService, SERVICE_CONTROL_STOP, &ss);

        // Wait for it to stop
        DWORD dwWaitInterval;
        DWORD dwWaitTime = 0;

        while (QueryServiceStatus(schService, &ss) && 
                ss.dwCurrentState == SERVICE_STOP_PENDING && 
                dwWaitTime < ss.dwWaitHint)
        {
            // 1/10 wait hint, min 1 sec, max 10 sec
            dwWaitInterval = ss.dwWaitHint < 10000 ? 1000 : 
                (ss.dwWaitHint > 100000 ? 10000 : ss.dwWaitHint / 10);

            Sleep(dwWaitInterval);
            dwWaitTime += dwWaitInterval;
        }
    }
    else
    {
        // Try to just open for deleting if we failed for stop and delete
        schService = OpenService(schManager, szServiceName, DELETE);
    }

    if(schService != NULL)
    {
        // Delete the service
        DeleteService(schService);
        CloseServiceHandle(schService);    
    }

    CloseServiceHandle(schManager);
}

/*
 * CUninstaller::RemoveRegKeyVals - Removes all values associated with a key.
 * Returns the next install item to be processed.
 */
SInstItem*
CUninstaller::RemoveRegKeyVals(char* szFullKey)
{
    HKEY hkeyRoot;
    char* szKey = GetRootKey(szFullKey, hkeyRoot);

    // Parse the root key (e.g. HKEY_CLASSES_ROOT)
    if(!szKey)
    {
        return NULL;
    }

    HKEY hkey;

    // Open the key
    if(RegOpenKeyEx(hkeyRoot, szKey, 0, KEY_READ | KEY_SET_VALUE, 
            &hkey) != ERROR_SUCCESS)
    {
        return NULL;
    }

    // Remove the values associated with this key
    SInstItem* pItem;
    SInstItem* pNextItem = NULL;

    GetNextItem(pItem);
    
    // Key default value
    while(pItem && stricmp(pItem->szName, UNINST_REGVALUE) == 0)
    {
        RemoveRegValue(hkey, NULL, pItem->szValue);
        delete pItem;
        GetNextItem(pItem);
    }

    // Named values
    while(pItem && stricmp(pItem->szName, UNINST_REGNAME) == 0)
    {
        GetNextItem(pNextItem);

        if(pNextItem && stricmp(pNextItem->szName, UNINST_REGVALUE) == 0)
        {
            do
            {
                RemoveRegValue(hkey, pItem->szValue, pNextItem->szValue);
                delete pNextItem;
                GetNextItem(pNextItem);
            } while(pNextItem && 
                    stricmp(pNextItem->szName, UNINST_REGVALUE) == 0);
        }
        else
        {
            // If a value was not given, delete regardless of value
            RegDeleteValue(hkey, pItem->szValue);
        }

        delete pItem;
        pItem = pNextItem;
    }

    RegCloseKey(hkey);

    // Queue it for deletion
    if(!m_RegKeys.Find(szFullKey))
    {
        char* szNewKey = new char[strlen(szFullKey)+1];
        strcpy(szNewKey, szFullKey);
        m_RegKeys.Push(szNewKey);
    }

    return pItem;
}

/*
 * CUninstaller::RemoveRegValue - removes the registry value specified
 * if its value matches the one we wrote
 */
void
CUninstaller::RemoveRegValue(HKEY hkey, const char* szName, 
                             const char* szValue)
{
    DWORD dwType;
    DWORD dwSize = READ_SIZE;
    BYTE  pData[READ_SIZE];

    if(RegQueryValueEx(hkey, szName, 0, &dwType, pData, &dwSize) 
        == ERROR_SUCCESS)
    {
        // Check if it's the value we wrote
        // Only bother with string values for now, since that's
        // all we use.
        if(dwType == REG_SZ && strcmp(szValue, (char*)pData) == 0)
        {
            RegDeleteValue(hkey, szName);
        }
    }
}

/*
 * CUninstaller::RemoveInstDirectory - removes the directory if it is
 * empty. If it contains subdirectories, recursively removes them if empty. 
 * Returns TRUE if successfully removed.
 */
BOOL
CUninstaller::RemoveInstDirectory(const char* szPath)
{
    // All file removals should have already been handled by now.

    // Check if it exists
    struct stat info;
    if(stat(szPath, &info) != 0)
    {
        // Doesn't exist
        return TRUE;
    }

    // Check if the directory is empty
    WIN32_FIND_DATA fd;
    char szFileDesc[MAX_PATH+1];

    _snprintf(szFileDesc, MAX_PATH, "%s\\*", szPath);
    szFileDesc[MAX_PATH] = '\0';
    HANDLE hFind = FindFirstFile(szFileDesc, &fd);
    BOOL bEmpty = TRUE;
    if(hFind != INVALID_HANDLE_VALUE)
    {
        do
        {
            // Skip "." and ".."
            if(strcmp(fd.cFileName, ".") != 0 && 
                strcmp(fd.cFileName, "..") != 0)
            {
                // Remove empty subdirectories
                if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                {
                    char szSubDir[MAX_PATH];
                    if(szSubDir)
                    {
                        _snprintf(szSubDir, MAX_PATH, "%s%c%s", szPath, 
                            OS_SEPARATOR_CHAR, fd.cFileName);
                        szFileDesc[MAX_PATH] = '\0';
                        if(RemoveInstDirectory(szSubDir))
                        {
                            m_Dirs.FindAndRemove(szSubDir);
                        }
                    }
                }
            }
        } while(FindNextFile(hFind, &fd));

        FindClose(hFind);
    }
    
    // remove the directory if it is empty
    return RemoveDirectory(szPath);
}

/*
 * CUninstaller::RemoveRegTree - Recursively removes szFullKey and all 
 * subkeys. If bDeleteVals is TRUE, removes values, otherwise removes   
 * keys only if they are empty of values.
 * Returns FALSE if the key cannot be deleted because there are value left.
 */
BOOL
CUninstaller::RemoveRegTree(const char* szFullKey, BOOL bDeleteVals)
{
    HKEY hkeyRoot;

    // Parse the root key (e.g. HKEY_CLASSES_ROOT)
    char* szKey = GetRootKey(szFullKey, hkeyRoot);
    if(!szKey)
    {
        return TRUE;
    }
    
    return RemoveRegTree(hkeyRoot, szKey, bDeleteVals);
}

BOOL
CUninstaller::RemoveRegTree(HKEY hkeyRoot, const char* szKey, BOOL bDelVals)
{
    HKEY hkey;

    // Open the key
    if(RegOpenKeyEx(hkeyRoot, szKey, 0, KEY_ALL_ACCESS,
            &hkey) != ERROR_SUCCESS)
    {
        return TRUE;
    }

    LONG res;
    DWORD dwNumKeys;
    DWORD dwNumVals;
    DWORD dwKeyLen;
    FILETIME ftWrite;
    BOOL bEmpty = TRUE;

    res = RegQueryInfoKey(hkey, NULL, NULL, NULL, &dwNumKeys, &dwKeyLen, 
            NULL, &dwNumVals, NULL, NULL, NULL, NULL);
    if(res != ERROR_SUCCESS)
    {
        RegCloseKey(hkey);
        return FALSE;
    }

    // Are there any values?
    if(!bDelVals && dwNumVals)
    {
        // There are values, so we cannot delete the key
        bEmpty = FALSE;
    }

    // Are there any keys?
    if(bEmpty && dwNumKeys)
    {
        DWORD dwIndex;
        DWORD dwLen;
        char** ppKeys = new char*[dwNumKeys];
        
        // Get all the key names
        for(dwIndex = 0; dwIndex < dwNumKeys; dwIndex++)
        {
            ppKeys[dwIndex] = new char[dwKeyLen+1];
            ppKeys[dwIndex][0] = '\0';
            dwLen = dwKeyLen+1;

            if(ppKeys[dwIndex])
            {
                RegEnumKeyEx(hkey, dwIndex, ppKeys[dwIndex], &dwLen, NULL,
                    NULL, NULL, &ftWrite);
            }
        }

        // delete them
        for(dwIndex = 0; dwIndex < dwNumKeys; dwIndex++)
        {
            if((!ppKeys[dwIndex] || ppKeys[dwIndex][0] == '\0' ||
                !RemoveRegTree(hkey, ppKeys[dwIndex], bDelVals)) &&
                !bDelVals)
            {
                bEmpty = FALSE;
            }
        }
    }

    RegCloseKey(hkey);

    // Remove the key if it has no values that we do not want to delete
    // RegDeleteKey also deletes all of the keys values
    return bEmpty ? RegDeleteKey(hkeyRoot, szKey) : FALSE;
}

/*
 * CUninstaller::GetRootKey - parses the key name and translates to the
 * proper predefined root key HKEY (e.g. HKEY_CLASSES_ROOT). returns a pointer 
 * to the beginning of the actual key name in szName, or NULL if no
 * translation could be made.
 */
char*
CUninstaller::GetRootKey(const char* szName, HKEY& hkey)
{
    char* szKey = strchr(szName, '\\');
    if(szKey <= szName)
    {
        return FALSE;
    }
    
    for(int i=0; HKEY_MAP[i].szName != NULL; i++)
    {
        if(strnicmp(szName, HKEY_MAP[i].szName, szKey - szName) == 0)
        {
            hkey = HKEY_MAP[i].hkey;
            return szKey + 1;
        }
    }
    
    return NULL;
}

/* 
 * CUninstaller::WarnRunning - warns the user to quit the application before
 * uninstalling it. Reutrns TRUE if they click okay to continue uninstalling.
 */
BOOL
CUninstaller::WarnRunning()
{
    char szText[READ_SIZE + 512];
    char szCaption[READ_SIZE + 64];
    sprintf(szText, STR_ERR_RUNNING, m_szTitle);
    sprintf(szCaption, STR_CAPTION, m_szTitle);

    if(MessageBox(NULL, szText, szCaption, MB_OKCANCEL) != IDOK)
    {
        return FALSE;
    }

    return TRUE;
}

/*
 * CUninstaller::ShowProgress - Displays the progress dialog.
 * Single threaded, no cancelling or anything.
 */
BOOL
CUninstaller::ShowProgress(void)
{
    // Necessary for the progress bar
    InitCommonControls();

    m_hwndDlg = CreateDialog(m_hInstance, MAKEINTRESOURCE(IDD_PROGRESS), 0,
                                ProgressDlgProc);

    if(!m_hwndDlg)
    {
        return FALSE;
    }

    // Set the window title
    char szCaption[READ_SIZE + 512];
    sprintf(szCaption, STR_CAPTION, m_szTitle);
    SetWindowText(m_hwndDlg, szCaption);

    return TRUE;
}

/*
 * CUninstaller::SetProgress - Sets the progress bar to the specified percentage
 */
void
CUninstaller::SetProgress(UINT16 uProgress)
{
    if(uProgress > 100)
    {
        uProgress = 100;
    }

    // Just check messages here
    CheckMessages();

    // set the progress bar
    HWND hwndProgress = GetDlgItem(m_hwndDlg, IDC_PROGRESSBAR);
    if(hwndProgress)
    {
        PostMessage(hwndProgress, PBM_SETPOS, uProgress, 0);
    }
    char str[256];
    sprintf(str, "Progress: %u\n", uProgress);
    OutputDebugString(str);
}

/*
 * CUninstaller::GetNumItems - attempts to determine the number of install
 * "items" in order to make a lame approximation of progress
 */
void
CUninstaller::GetNumItems()
{
    // Count the number of lines in each install instance
    // amd just use the biggest one. Close enough.
    char szLine[READ_SIZE];
    UINT32 ulNumItems = 0;
    m_ulItems = 0;
    m_ulTotalItems = 0;

    while(fgets(szLine, READ_SIZE, m_fpLog) && !feof(m_fpLog))
    {
        // skip any obviously blank lines or comments
        if(*szLine != '#' || *szLine != '\n' || *szLine != '\0')
        {
            // if we find a title item, this is a new installation
            if(strncmp(szLine, UNINST_TITLE, strlen(UNINST_TITLE)) == 0)
            {
                if(ulNumItems > m_ulItems)
                {
                    m_ulTotalItems = ulNumItems;
                }
                ulNumItems = 0;
            }
            else
            {
                ulNumItems++;
            }
        }
    }

    if(ulNumItems > m_ulItems)
    {
        m_ulTotalItems = ulNumItems;
    }

    // plus one for the log file
    m_ulTotalItems++;
}

/*
 * CUninstaller::UpdateDlgText - updates the progress dialog text
 */
void
CUninstaller::UpdateDlgText(const char* szText)
{
    SetDlgItemText(m_hwndDlg, IDC_TEXT, szText);
}

/*
 * CUninstaller::CloseProgress - destroys the progress dialog
 */
void
CUninstaller::CloseProgress()
{
    if(m_hwndDlg)
    {
        DestroyWindow(m_hwndDlg);
    }
}

/*
 * CUninstaller::ShowDoneMessage - Pops up a message to say we're done.
 */
void
CUninstaller::ShowDoneMessage()
{
    char szText[READ_SIZE + 512];
    char szCaption[READ_SIZE + 64];
    sprintf(szText, STR_DONE_MSG, m_szTitle);
    sprintf(szCaption, STR_CAPTION, m_szTitle);

    MessageBox(NULL, szText, szCaption, MB_OK | MB_ICONINFORMATION);
}

void
CUninstaller::CheckMessages(void)
{
    MSG msg;
    while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
	if(!m_hwndDlg || !IsDialogMessage(m_hwndDlg, &msg))
	{
	    TranslateMessage(&msg);
	    DispatchMessage(&msg);
	}
    }
}

