From d66fcb1784fc6b5a6b01748dda7f99e0afa3fc69 Mon Sep 17 00:00:00 2001 From: Kevin Wasserman Date: Tue, 5 Jun 2012 13:03:21 -0400 Subject: Fix NSIS uninstall to work with UAC Use ShellExecuteEx() to elevate privilege if CreateProcess() fails. Signed-off-by: Kevin Wasserman ticket: 7265 (new) queue: kfw target_version: 1.10.4 tags: pullup --- src/windows/installer/wix/custom/custom.cpp | 307 +++++++++++++++++----------- 1 file changed, 192 insertions(+), 115 deletions(-) (limited to 'src/windows/installer') diff --git a/src/windows/installer/wix/custom/custom.cpp b/src/windows/installer/wix/custom/custom.cpp index 5f0f42f655..3ef726d1de 100644 --- a/src/windows/installer/wix/custom/custom.cpp +++ b/src/windows/installer/wix/custom/custom.cpp @@ -83,11 +83,13 @@ SOFTWARE. // Only works for Win2k and above #define _WIN32_WINNT 0x500 #include "custom.h" +#include // linker stuff #pragma comment(lib, "msi") #pragma comment(lib, "advapi32") - +#pragma comment(lib, "shell32") +#pragma comment(lib, "user32") void ShowMsiError( MSIHANDLE hInstall, DWORD errcode, DWORD param ){ MSIHANDLE hRecord; @@ -102,6 +104,22 @@ void ShowMsiError( MSIHANDLE hInstall, DWORD errcode, DWORD param ){ MsiCloseHandle( hRecord ); } +static void ShowMsiErrorEx(MSIHANDLE hInstall, DWORD errcode, LPTSTR str, + DWORD param ) +{ + MSIHANDLE hRecord; + + hRecord = MsiCreateRecord(3); + MsiRecordClearData(hRecord); + MsiRecordSetInteger(hRecord, 1, errcode); + MsiRecordSetString(hRecord, 2, str); + MsiRecordSetInteger(hRecord, 3, param); + + MsiProcessMessage(hInstall, INSTALLMESSAGE_ERROR, hRecord); + + MsiCloseHandle(hRecord); +} + #define LSA_KERBEROS_KEY "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos" #define LSA_KERBEROS_PARM_KEY "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos\\Parameters" #define KFW_CLIENT_KEY "SOFTWARE\\MIT\\Kerberos\\Client\\" @@ -520,130 +538,189 @@ _cleanup: return rv; } -/* Uninstall NSIS */ -MSIDLLEXPORT UninstallNsisInstallation( MSIHANDLE hInstall ) +static bool IsNSISInstalled() { - DWORD rv = ERROR_SUCCESS; - // lookup the NSISUNINSTALL property value - LPTSTR cNsisUninstall = _T("UPGRADENSIS"); - HANDLE hIo = NULL; - DWORD dwSize = 0; - LPTSTR strPathUninst = NULL; - HANDLE hJob = NULL; - STARTUPINFO sInfo; - PROCESS_INFORMATION pInfo; - - pInfo.hProcess = NULL; - pInfo.hThread = NULL; - - rv = MsiGetProperty( hInstall, cNsisUninstall, _T(""), &dwSize ); - if(rv != ERROR_MORE_DATA) goto _cleanup; - - strPathUninst = new TCHAR[ ++dwSize ]; - - rv = MsiGetProperty( hInstall, cNsisUninstall, strPathUninst, &dwSize ); - if(rv != ERROR_SUCCESS) goto _cleanup; - - // Create a process for the uninstaller - sInfo.cb = sizeof(sInfo); - sInfo.lpReserved = NULL; - sInfo.lpDesktop = _T(""); - sInfo.lpTitle = _T("NSIS Uninstaller for Kerberos for Windows"); - sInfo.dwX = 0; - sInfo.dwY = 0; - sInfo.dwXSize = 0; - sInfo.dwYSize = 0; - sInfo.dwXCountChars = 0; - sInfo.dwYCountChars = 0; - sInfo.dwFillAttribute = 0; - sInfo.dwFlags = 0; - sInfo.wShowWindow = 0; - sInfo.cbReserved2 = 0; - sInfo.lpReserved2 = 0; - sInfo.hStdInput = 0; - sInfo.hStdOutput = 0; - sInfo.hStdError = 0; - - if(!CreateProcess( - strPathUninst, - _T("Uninstall /S"), - NULL, - NULL, - FALSE, - CREATE_SUSPENDED, - NULL, - NULL, - &sInfo, - &pInfo)) { - DWORD lastError = GetLastError(); - MSIHANDLE hRecord; - - hRecord = MsiCreateRecord(4); - MsiRecordClearData(hRecord); - MsiRecordSetInteger(hRecord, 1, ERR_NSS_FAILED_CP); - MsiRecordSetString(hRecord, 2, strPathUninst); - MsiRecordSetInteger(hRecord, 3, lastError); - - MsiProcessMessage( hInstall, INSTALLMESSAGE_ERROR, hRecord ); - - MsiCloseHandle( hRecord ); - - pInfo.hProcess = NULL; - pInfo.hThread = NULL; - rv = 40; - goto _cleanup; - }; - - // Create a job object to contain the NSIS uninstall process tree - - JOBOBJECT_ASSOCIATE_COMPLETION_PORT acp; + HKEY nsisKfwKey = NULL; + // Note: check Wow6432 node if 64 bit build + HRESULT res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion" + "\\Uninstall\\Kerberos for Windows", + 0, + KEY_READ | KEY_WOW64_32KEY, + &nsisKfwKey); + if (res != ERROR_SUCCESS) + return FALSE; + + RegCloseKey(nsisKfwKey); + return TRUE; +} - acp.CompletionKey = 0; +static HANDLE NSISUninstallShellExecute(LPTSTR pathUninstall) +{ + SHELLEXECUTEINFO sei; + ZeroMemory ( &sei, sizeof(sei) ); + + sei.cbSize = sizeof(sei); + sei.hwnd = GetForegroundWindow(); + sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | + SEE_MASK_NOCLOSEPROCESS; + sei.lpVerb = _T("runas"); // run as administrator + sei.lpFile = pathUninstall; + sei.lpParameters = _T(""); + sei.nShow = SW_SHOWNORMAL; + + if (!ShellExecuteEx(&sei)) { + // FAILED! TODO: report details? + } + return sei.hProcess; +} - hJob = CreateJobObject(NULL, _T("NSISUninstallObject")); - if(!hJob) { - rv = 41; - goto _cleanup; - } +static HANDLE NSISUninstallCreateProcess(LPTSTR pathUninstall) +{ + STARTUPINFO sInfo; + PROCESS_INFORMATION pInfo; + pInfo.hProcess = NULL; + pInfo.hThread = NULL; + + // Create a process for the uninstaller + sInfo.cb = sizeof(sInfo); + sInfo.lpReserved = NULL; + sInfo.lpDesktop = _T(""); + sInfo.lpTitle = _T("NSIS Uninstaller for Kerberos for Windows"); + sInfo.dwX = 0; + sInfo.dwY = 0; + sInfo.dwXSize = 0; + sInfo.dwYSize = 0; + sInfo.dwXCountChars = 0; + sInfo.dwYCountChars = 0; + sInfo.dwFillAttribute = 0; + sInfo.dwFlags = 0; + sInfo.wShowWindow = 0; + sInfo.cbReserved2 = 0; + sInfo.lpReserved2 = 0; + sInfo.hStdInput = 0; + sInfo.hStdOutput = 0; + sInfo.hStdError = 0; + + if (!CreateProcess(pathUninstall, + _T("Uninstall /S"), + NULL, + NULL, + FALSE, + CREATE_SUSPENDED, + NULL, + NULL, + &sInfo, + &pInfo)) { + // failure; could grab info, but we should be able to recover by + // using NSISUninstallShellExecute... + } else { + // success + // start up the thread + ResumeThread(pInfo.hThread); + // done with thread handle + CloseHandle(pInfo.hThread); + } + return pInfo.hProcess; +} - hIo = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0); - if(!hIo) { - rv = 42; - goto _cleanup; - } - acp.CompletionPort = hIo; +/* Uninstall NSIS */ +MSIDLLEXPORT UninstallNsisInstallation( MSIHANDLE hInstall ) +{ + DWORD rv = ERROR_SUCCESS; + DWORD lastError; + // lookup the NSISUNINSTALL property value + LPTSTR cNsisUninstall = _T("UPGRADENSIS"); + LPTSTR strPathUninst = NULL; + DWORD dwSize = 0; + HANDLE hProcess = NULL; + HANDLE hIo = NULL; + HANDLE hJob = NULL; + + rv = MsiGetProperty( hInstall, cNsisUninstall, _T(""), &dwSize ); + if(rv != ERROR_MORE_DATA) goto _cleanup; + + strPathUninst = new TCHAR[ ++dwSize ]; + + rv = MsiGetProperty(hInstall, cNsisUninstall, strPathUninst, &dwSize); + if(rv != ERROR_SUCCESS) goto _cleanup; + + hProcess = NSISUninstallCreateProcess(strPathUninst); + if (hProcess == NULL) // expected when run on UAC-limited account + hProcess = NSISUninstallShellExecute(strPathUninst); + + if (hProcess == NULL) { + // still no uninstall process? ick... + lastError = GetLastError(); + rv = 40; + goto _cleanup; + } + // note that it is not suffiecient to wait for the initial process to + // finish; there is a whole process tree that we need to wait for. sigh. + JOBOBJECT_ASSOCIATE_COMPLETION_PORT acp; + acp.CompletionKey = 0; + hJob = CreateJobObject(NULL, _T("NSISUninstallObject")); + if(!hJob) { + rv = 41; + goto _cleanup; + } - SetInformationJobObject( hJob, JobObjectAssociateCompletionPortInformation, &acp, sizeof(acp)); + hIo = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0); + if(!hIo) { + rv = 42; + goto _cleanup; + } - AssignProcessToJobObject( hJob, pInfo.hProcess ); + acp.CompletionPort = hIo; + + SetInformationJobObject(hJob, + JobObjectAssociateCompletionPortInformation, + &acp, + sizeof(acp)); + + AssignProcessToJobObject(hJob, hProcess); + + DWORD msgId; + ULONG_PTR unusedCompletionKey; + LPOVERLAPPED unusedOverlapped; + for (;;) { + if (!GetQueuedCompletionStatus(hIo, + &msgId, + &unusedCompletionKey, + &unusedOverlapped, + INFINITE)) { + Sleep(1000); + } else if (msgId == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) { + break; + } + } - ResumeThread( pInfo.hThread ); +_cleanup: + if (hProcess) CloseHandle(hProcess); + if (hIo) CloseHandle(hIo); + if (hJob) CloseHandle(hJob); + + if (IsNSISInstalled()) { + // uninstall failed: maybe user cancelled uninstall, or something else + // went wrong... + if (rv == ERROR_SUCCESS) + rv = 43; + } else { + // Maybe something went wrong, but it doesn't matter as long as nsis + // is gone now... + rv = ERROR_SUCCESS; + } - DWORD a,b,c; - for(;;) { - if(!GetQueuedCompletionStatus(hIo, &a, (PULONG_PTR) &b, (LPOVERLAPPED *) &c, INFINITE)) { - Sleep(1000); - continue; - } - if(a == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO) { - break; - } - } + if (rv == 40) { + // CreateProcess() / ShellExecute() errors get extra data + ShowMsiErrorEx(hInstall, ERR_NSS_FAILED_CP, strPathUninst, lastError); + } else if (rv != ERROR_SUCCESS) { + ShowMsiError(hInstall, ERR_NSS_FAILED, rv); + } - rv = ERROR_SUCCESS; - -_cleanup: - if(hIo) CloseHandle(hIo); - if(pInfo.hProcess) CloseHandle( pInfo.hProcess ); - if(pInfo.hThread) CloseHandle( pInfo.hThread ); - if(hJob) CloseHandle(hJob); - if(strPathUninst) delete strPathUninst; - - if(rv != ERROR_SUCCESS && rv != 40) { - ShowMsiError( hInstall, ERR_NSS_FAILED, rv ); - } - return rv; + if (strPathUninst) delete strPathUninst; + return rv; } /* Check and add or remove networkprovider key value -- cgit