/*++

  Copyright (c) 2001 Microsoft Corporation

  Module Name:

    Duuninst.cpp

  Abstract:

    Contains the main uninstallation code
    used by the app.

  Notes:

    Unicode only.

  History:

    03/02/2001      rparsons    Created
    
--*/

#include "precomp.h"

extern SETUP_INFO g_si;

/*++

  Routine Description:

    Removes the specified registry key
    Assumes HKEY_LOCAL_MACHINE

  Arguments:
  
    lpwKey      -   Path of the key to open
    lpwSubKey   -   Path of the subkey to delete
    
  Return Value:

    None

--*/
void
UninstallDeleteSubKey(
    IN LPCWSTR lpwKey,
    IN LPCWSTR lpwSubKey
    )
{
    CRegistry   creg;

    //
    // Remove the key from the registry
    //
    creg.DeleteRegistryKey(HKEY_LOCAL_MACHINE,
                           lpwKey,
                           lpwSubKey,
                           TRUE,
                           TRUE);
    
    return;
}

/*++

  Routine Description:

    Retrieves the section names from the uninstall
    INF file. This dictates what operations will be
    performed during uninstall

  Arguments:

    None

  Return Value:

    TRUE on success, FALSE otherwise

--*/
BOOL
UninstallGetSectionsFromINF()
{
    BOOL        fReturn = FALSE;
    DWORD       dwType = 0;
    char        szSectionName[MAX_QUEUE_SIZE] = "";
    char        *pSectionName;
    WCHAR       wszDirective[MAX_PATH] = L"";
    INFCONTEXT  InfContext;

    //
    // Loop through all the lines in the Sections section(s),    
    //
    fReturn = SetupFindFirstLineA(g_si.hInf, INF_MASTER_SECTIONS, NULL,
                                  &InfContext) &&
              SetupGetLineTextA(&InfContext,
                                NULL, NULL, NULL,
                                szSectionName, MAX_QUEUE_SIZE, NULL);

    while (fReturn) {

        //
        // Determine which section we're working with
        //
        if (strstr(szSectionName, INF_RESTORE_FILES)) {
            dwType = dwRestoreFiles;
                   
        } else if (strstr(szSectionName, INF_DELETE_REGISTRY)) {
            dwType = dwDeleteRegistry;
    
        } else if (strstr(szSectionName, INF_RESTORE_REGISTRY)) {
            dwType = dwRestoreRegistry;
    
        } else if (strstr(szSectionName, INF_UNREGISTRATIONS)) {
            dwType = dwUnRegistrations;
    
        } else if (strstr(szSectionName, INF_EXCLUDE)) {
            dwType = dwExclusionsUninstall;
    
        } else {
            Print(ERROR,
                  L"[UninstallGetSectionsFromINF] Illegal section name passed %s\n",
                  szSectionName);
            return FALSE; // illegal section name
        }

        pSectionName = strtok(szSectionName, ",");

        do {
            
            pAnsiToUnicode(pSectionName, wszDirective, MAX_PATH);

            //
            // Loop through each section name and add it to the
            // appropriate queue
            //
            switch (dwType) {
                case dwRestoreFiles:
                    g_si.RestoreFileQueue.Enqueue(wszDirective);
                    break;

                case dwDeleteRegistry:
                    g_si.DeleteRegistryQueue.Enqueue(wszDirective);
                    break;

                case dwRestoreRegistry:
                    g_si.RestoreRegistryQueue.Enqueue(wszDirective);
                    break;

                case dwUnRegistrations:
                    g_si.RegistrationQueue.Enqueue(wszDirective);
                    break;

                case dwExclusionsUninstall:
                    g_si.ExclusionQueue.Enqueue(wszDirective);
                    break;

                default:
                    return FALSE;   // illegal section name
            }

            pSectionName = strtok(NULL, ",");

        } while (NULL != pSectionName);

        fReturn = SetupFindNextLine(&InfContext, &InfContext) &&
                      SetupGetLineTextA(&InfContext,
                                        NULL, NULL, NULL,
                                        szSectionName, MAX_QUEUE_SIZE, NULL);
    }
    
    return TRUE;
}

/*++

  Routine Description:

    Restores registry keys that were saved
    during the install

  Arguments:

    None

  Return Value:

    TRUE on success, FALSE otherwise

--*/
BOOL
UninstallRestoreRegistryKeys()
{
    BOOL        fReturn = FALSE, fResult = FALSE;
    HKEY        hKeyRoot = NULL;
    char        szEntry[MAX_QUEUE_SIZE] = "";
    WCHAR       wszEntry[MAX_QUEUE_SIZE] = L"";
    char        szKeyPath[MAX_PATH*3] = "";
    WCHAR       wszKeyPath[MAX_PATH*3] = L"";
    LPWSTR      lpwSubKey = NULL, lpwFilePath = NULL, lpwKeyRoot = NULL;
    UINT        uCount = 0;
    CRegistry   creg;
    INFCONTEXT  InfContext;

    //
    // The entry in the uninstall INF will have the following format:
    // HKxx,SubKeyPath,PathToBackupFile
    //

    //
    // Step through each entry in the queue
    //
    while (g_si.RestoreRegistryQueue.GetSize()) {

        g_si.RestoreRegistryQueue.Dequeue(wszEntry, MAX_PATH - 1, FALSE);

        pUnicodeToAnsi(wszEntry, szEntry, MAX_PATH);

        //
        // Loop through all the lines in the restore registry section(s),
        // and perform the restore
        //
        fReturn = SetupFindFirstLineA(g_si.hInf, szEntry, NULL,
                                      &InfContext) &&
                  SetupGetLineTextA(&InfContext,
                                    NULL, NULL, NULL,
                                    szKeyPath, MAX_PATH*3, NULL);


        while (fReturn) {

            pAnsiToUnicode(szKeyPath, wszKeyPath, MAX_PATH*3);

            //
            // Split the key path into three separate parts
            //
            lpwKeyRoot = GetNextToken(wszKeyPath, L",");
            
            if (NULL == lpwKeyRoot) {
                break;
            }

            Print(TRACE, L"[UninstallRestoreRegistryKeys] Key root: %s\n", lpwKeyRoot);

            if (!_wcsicmp(lpwKeyRoot, L"HKLM")) {
                hKeyRoot = HKEY_LOCAL_MACHINE;

            } else if (!_wcsicmp(lpwKeyRoot, L"HKCR")) {
                hKeyRoot = HKEY_CLASSES_ROOT;
            
            } else if (!_wcsicmp(lpwKeyRoot, L"HKCU")) {
                hKeyRoot = HKEY_CURRENT_USER;
            
            } else if (!_wcsicmp(lpwKeyRoot, L"HKU")) {
                hKeyRoot = HKEY_USERS;
            
            } else {
                break;
            }

            //
            // Get the subkey path
            //
            lpwSubKey = GetNextToken(NULL, L",");

            if (NULL == lpwSubKey) {
                break;
            }

            Print(TRACE, L"[UninstallRestoreRegistryKeys] Subkey path: %s\n", lpwSubKey);

            //
            // Get the file name with full path
            //
            lpwFilePath = GetNextToken(NULL, L",");

            if (NULL == lpwFilePath) {
                break;
            }
            
            Print(TRACE, L"[UninstallRestoreRegistryKeys] File path: %s\n", lpwFilePath);

            //
            // Restore the key
            //
            fResult = creg.RestoreKey(hKeyRoot, lpwSubKey, lpwFilePath, TRUE);

            if (!fResult) {
                Print(ERROR,
                      L"[UninstallRestoreRegistryKeys] Failed to restore key\n");
                return FALSE;
            }

            fReturn = SetupFindNextLine(&InfContext, &InfContext) &&
                      SetupGetLineTextA(&InfContext,
                                        NULL, NULL, NULL,
                                        szKeyPath, MAX_PATH*3, NULL);
        }
    }

    return TRUE;
}

/*++

  Routine Description:

    Removes files from the installation directory

  Arguments:
  
    None
    
  Return Value:

    None

--*/
BOOL
UninstallRemoveFiles()
{
    CEnumDir    ced;

    //
    // Remove files from the installation directory,
    // but leave subdirectories alone
    //
    ced.EnumerateDirectoryTree(g_si.lpwInstallDirectory,
                               DeleteOneFile,
                               FALSE,
                               (PVOID) TRUE);

    return TRUE;
}

/*++

  Routine Description:

    Restores files that were backed up during the install

  Arguments:

    None

  Return Value:

    TRUE on success, FALSE otherwise

--*/
BOOL
UninstallRestoreFiles()
{
    WCHAR       wszBackupDir[MAX_PATH] = L"";
    WCHAR       wszDestFileName[MAX_PATH] = L"";
    WCHAR       wszSourceFileName[MAX_PATH] = L"";
    char        szEntry[MAX_PATH] = "";
    WCHAR       wszEntry[MAX_PATH] = L"";
    char        szFileName[MAX_PATH] = "";
    WCHAR       wszFileName[MAX_PATH] = L"";
    BOOL        fReturn = FALSE, fResult = FALSE;
    LPWSTR      lpwDestDir = NULL;
    DWORDLONG   dwlSourceVersion = 0, dwlDestVersion = 0;
    INFCONTEXT  InfContext;
    
    //
    // Step through each entry in the queue
    //
    while (g_si.RestoreFileQueue.GetSize()) {

        g_si.RestoreFileQueue.Dequeue(wszEntry, MAX_PATH - 1, FALSE);

        pUnicodeToAnsi(wszEntry, szEntry, MAX_PATH);

        //
        // Get the destination directory
        //
        GetNextToken(wszEntry, L".");
        GetNextToken(NULL, L".");
        lpwDestDir = GetNextToken(NULL, L".");

        if (NULL == lpwDestDir) {
            break;
        }

        //
        // Loop through all the lines in the restore files section(s),
        // and perform the copy
        //
        fReturn = SetupFindFirstLineA(g_si.hInf, szEntry, NULL,
                                      &InfContext) &&
                  SetupGetLineTextA(&InfContext,
                                    NULL, NULL, NULL,
                                    szFileName, MAX_PATH, NULL);
        
        while (fReturn) {

            pAnsiToUnicode(szFileName, wszFileName, MAX_PATH);            

            //
            // Build the path to the destination file
            //
            wsprintf(wszDestFileName,
                     L"%s\\%s\\%s", 
                     g_si.lpwWindowsDirectory,
                     lpwDestDir,
                     wszFileName);

            //
            // Build the path to the source file
            //
            wsprintf(wszSourceFileName,
                     L"%s\\%s\\%s",
                     g_si.lpwExtractPath,
                     g_si.lpwUninstallDirectory,
                     wszFileName);
            
            //
            // Ensure that this file is not under WFP
            //
            fResult = IsFileProtected(wszDestFileName);
    
            //
            // Copy the file - be sensitive to WFP
            // 
            if (fResult) {
                
                Print(TRACE,
                      L"[UninstallRestoreFiles] Preparing to restore WFP file from %s to %s\n",
                      wszSourceFileName, wszDestFileName);

                fResult = CommonEnableProtectedRenames();

                if (!fResult) {
                    Print(ERROR,
                          L"[UninstallRestoreFiles] Failed to enable protected renames\n");
                    return FALSE;
                }

                Print(TRACE,
                      L"[UninstallRestoreFiles] Preparing to install WFP file from %s to %s\n",
                      wszSourceFileName, wszDestFileName);
            
                fResult = UninstallWFPFile(wszSourceFileName,
                                           wszFileName,
                                           wszDestFileName,
                                           g_si.fUpdateDllCache);

                if (!fResult) {
                        Print(ERROR,
                              L"[UninstallRestoreFiles] Failed to install WFP file %s\n",
                              wszFileName);
                    return FALSE;
                }
                
            } else {

                Print(TRACE,
                      L"[UninstallRestoreFiles] Preparing to restore file from %s to %s\n",
                      wszSourceFileName, wszDestFileName);

                fResult = ForceMove(wszSourceFileName, wszDestFileName);

                if (!fResult) {
                    Print(ERROR,
                          L"[UninstallRestoreFiles] Failed to restore file from %s to %s. GLE = %d\n",
                          wszSourceFileName, wszDestFileName, GetLastError());
                    return FALSE;
                }
                
            }        

        fReturn = SetupFindNextLine(&InfContext, &InfContext) &&
                  SetupGetLineTextA(&InfContext,
                                    NULL, NULL, NULL,
                                    szFileName, MAX_PATH, NULL);
        }
    }

    return TRUE;
}

/*++

  Routine Description:

    Restores a file that is protected
    by WFP

  Arguments:

    lpwSourceFileName       -   Path to the source file
    lpwDestFileName         -   Name of the destination file
    lpwDestFileNamePath         Name & path to the destination file
    fUpdateDllCache         -   A flag to indicate if the file
                                should be placed in the DllCache directory

  Return Value:

    TRUE on success, FALSE otherwise

--*/
BOOL
UninstallWFPFile(
    IN LPCWSTR lpwSourceFileName,
    IN LPCWSTR lpwDestFileName,
    IN LPCWSTR lpwDestFileNamePath,
    IN BOOL    fUpdateDllCache
    )
{
    LPWSTR      lpwCachePath = NULL;
    LPWSTR      lpwExpandedCachePath = NULL;
    LPWSTR      lpwTempFileName = NULL;
    DWORD       cbSize = 0;
    WCHAR       wszDllCachePath[MAX_PATH] = L"";
    CRegistry   creg;

    if (g_si.fUpdateDllCache) {

        Print(TRACE,
              L"[UninstallWFPFile] Restoring %s to DllCache dir\n",
              lpwSourceFileName);
        
        //
        // Try to get the dllcache directory path from
        // the registry
        //
        lpwCachePath = creg.GetString(HKEY_LOCAL_MACHINE,
                                      REG_WINFP_PATH,
                                      L"SfcDllCacheDir",
                                      TRUE);
    
        if (lpwCachePath) {
            
            if (cbSize = ExpandEnvironmentStrings(lpwCachePath, 
                                                  lpwExpandedCachePath, 
                                                  0)) {
                
                lpwExpandedCachePath = (LPWSTR) MALLOC(cbSize*sizeof(WCHAR));
    
                if (lpwExpandedCachePath) {
                    
                    if (ExpandEnvironmentStrings(lpwCachePath,
                                                 lpwExpandedCachePath,
                                                 cbSize)) {
                            
                        //
                        // Build a full path to \%windir%\system32\dllcache\filename.xxx
                        //
                        wsprintf(wszDllCachePath,
                                 L"%s\\%s", 
                                 lpwExpandedCachePath, 
                                 lpwDestFileName);
                        
                    }
                }
            }
        }
    
        //
        // If we couldn't get it from that key, try another
        //
        if (NULL == lpwExpandedCachePath) {
    
            lpwCachePath = creg.GetString(HKEY_LOCAL_MACHINE,
                                          REG_WINLOGON_PATH,
                                          L"SfcDllCacheDir",
                                          TRUE);
    
            if (lpwCachePath) {
            
                if (cbSize = ExpandEnvironmentStrings(lpwCachePath, 
                                                      lpwExpandedCachePath, 
                                                      0)) {
                    
                    lpwExpandedCachePath = (LPWSTR) MALLOC(cbSize*sizeof(WCHAR));
        
                    if (lpwExpandedCachePath) {
                        
                        if (ExpandEnvironmentStrings(lpwCachePath,
                                                     lpwExpandedCachePath,
                                                     cbSize)) {
                                
                            //
                            // Build a full path to \%windir%\system32\dllcache\filename.xxx
                            //
                            wsprintf(wszDllCachePath,
                                     L"%s\\%s", 
                                     lpwExpandedCachePath, 
                                     lpwDestFileName);
                            
                        }
                    }
                }
            }
        }
    
        //
        // If neither key worked, build the path manually
        //
        if (NULL == lpwExpandedCachePath) {
            
            wsprintf(wszDllCachePath,
                     L"%s\\DllCache\\%s",
                     g_si.lpwSystem32Directory,
                     lpwDestFileName);
        }
    
        //
        // Replace the file in the DllCache directory
        //
        if (!CopyFile(lpwSourceFileName, wszDllCachePath, FALSE)) {
            
            Print(ERROR, 
                  L"[UninstallWFPFile] Failed to copy %s to %s\n",
                  lpwSourceFileName, wszDllCachePath);
            goto cleanup;
            return FALSE;
        }
    }

    //
    // This is semi-custom, but because some of the 
    // WFPs will be in use, we'll delay replace them
    // and let the Session Manager rename them
    //
    lpwTempFileName = CopyTempFile(lpwSourceFileName,
                                   g_si.lpwSystem32Directory);
    
    if (NULL == lpwTempFileName) {
        
        Print(ERROR, L"[UninstallWFPFile] Failed to get temp file\n");
        goto cleanup;
        return FALSE;
    }
    
    if (!MoveFileEx(lpwTempFileName,
                    lpwDestFileNamePath, 
                    MOVEFILE_REPLACE_EXISTING | MOVEFILE_DELAY_UNTIL_REBOOT)) {
    
        Print(ERROR, L"[UninstallWFPFile] Failed to delay replace from %s to %s\n",
              lpwTempFileName, lpwDestFileNamePath);
        goto cleanup;
        return FALSE;
    }

    cleanup:

        if (lpwTempFileName) {
            FREE(lpwTempFileName);
        }

        if (lpwExpandedCachePath) {
            FREE(lpwExpandedCachePath);
        }

    return TRUE;
}

/*++

  Routine Description:

    Custom worker routine for uninstall

  Arguments:

    None

  Return Value:

    None

--*/
void
UninstallCustomWorker()
{
    //
    // This is where determine if we should remove
    // any regsvr32s that we did during install.
    // We also remove the $Sources$ directory,
    // if necessary.
    // Our method of detection is to see if our
    // Guid is present under the Active Setup
    // key. If not, it's safe to assume that
    // no package is currently installed
    //

    BOOL        fReturn = FALSE;
    CRegistry   creg;
    char        szActiveSetupKey[MAX_PATH] = "";
    WCHAR       wszActiveSetupKey[MAX_PATH] = L"";
    WCHAR       wszSourcesDir[MAX_PATH] = L"";       

    fReturn = SetupGetLineTextA(NULL,
                                g_si.hInf,
                                "Strings",
                                "ActiveSetupKey",
                                szActiveSetupKey,
                                sizeof(szActiveSetupKey),
                                NULL);

    if (!fReturn) {
        return;
    }

    pAnsiToUnicode(szActiveSetupKey, wszActiveSetupKey, MAX_PATH);

    fReturn = creg.IsRegistryKeyPresent(HKEY_LOCAL_MACHINE,
                                        wszActiveSetupKey);

    if (!fReturn) {
        wsprintf(wszSourcesDir, L"%s\\$Sources$", g_si.lpwInstallDirectory);
        CommonRemoveDirectoryAndFiles(wszSourcesDir, (PVOID) FALSE, TRUE, FALSE);
        CommonRegisterServers(FALSE);
    }
    
    return;
}