| /* | |
| * Uninstaller | |
| * | |
| * Copyright 2000 Andreas Mohr | |
| * Copyright 2004 Hannu Valtonen | |
| * Copyright 2005 Jonathan Ernst | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| * | |
| */ | |
| #include <string.h> | |
| #include <windows.h> | |
| #include <shlwapi.h> | |
| #include "resource.h" | |
| #include "regstr.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(uninstaller); | |
| extern void WINAPI Control_RunDLL(HWND hWnd, HINSTANCE hInst, LPCSTR cmd, DWORD nCmdShow); | |
| typedef struct { | |
| HKEY root; | |
| WCHAR *key; | |
| WCHAR *descr; | |
| WCHAR *command; | |
| int active; | |
| } uninst_entry; | |
| static uninst_entry *entries = NULL; | |
| static unsigned int numentries = 0; | |
| static int oldsel = -1; | |
| static WCHAR *sFilter; | |
| static int FetchUninstallInformation(void); | |
| static void UninstallProgram(void); | |
| static int cmp_by_name(const void *a, const void *b); | |
| static const WCHAR DisplayNameW[] = {'D','i','s','p','l','a','y','N','a','m','e',0}; | |
| static const WCHAR PathUninstallW[] = { | |
| 'S','o','f','t','w','a','r','e','\\', | |
| 'M','i','c','r','o','s','o','f','t','\\', | |
| 'W','i','n','d','o','w','s','\\', | |
| 'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\', | |
| 'U','n','i','n','s','t','a','l','l',0 }; | |
| static const WCHAR UninstallCommandlineW[] = {'U','n','i','n','s','t','a','l','l','S','t','r','i','n','g',0}; | |
| static const WCHAR WindowsInstallerW[] = {'W','i','n','d','o','w','s','I','n','s','t','a','l','l','e','r',0}; | |
| static const WCHAR SystemComponentW[] = {'S','y','s','t','e','m','C','o','m','p','o','n','e','n','t',0}; | |
| static void output_writeconsole(const WCHAR *str, DWORD len) | |
| { | |
| DWORD written, ret, lenA; | |
| char *strA; | |
| ret = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), str, len, &written, NULL); | |
| if (ret) return; | |
| /* WriteConsole fails if its output is redirected to a file. | |
| * If this occurs, we should use an OEM codepage and call WriteFile. | |
| */ | |
| lenA = WideCharToMultiByte(GetConsoleOutputCP(), 0, str, len, NULL, 0, NULL, NULL); | |
| strA = HeapAlloc(GetProcessHeap(), 0, lenA); | |
| if (strA) | |
| { | |
| WideCharToMultiByte(GetConsoleOutputCP(), 0, str, len, strA, lenA, NULL, NULL); | |
| WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), strA, lenA, &written, FALSE); | |
| HeapFree(GetProcessHeap(), 0, strA); | |
| } | |
| } | |
| static void output_formatstring(const WCHAR *fmt, __ms_va_list va_args) | |
| { | |
| WCHAR *str; | |
| DWORD len; | |
| SetLastError(NO_ERROR); | |
| len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER, | |
| fmt, 0, 0, (LPWSTR)&str, 0, &va_args); | |
| if (len == 0 && GetLastError() != NO_ERROR) | |
| { | |
| WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(fmt)); | |
| return; | |
| } | |
| output_writeconsole(str, len); | |
| LocalFree(str); | |
| } | |
| static void __cdecl output_message(unsigned int id, ...) | |
| { | |
| WCHAR fmt[1024]; | |
| __ms_va_list va_args; | |
| if (!LoadStringW(GetModuleHandleW(NULL), id, fmt, sizeof(fmt)/sizeof(fmt[0]))) | |
| { | |
| WINE_FIXME("LoadString failed with %d\n", GetLastError()); | |
| return; | |
| } | |
| __ms_va_start(va_args, id); | |
| output_formatstring(fmt, va_args); | |
| __ms_va_end(va_args); | |
| } | |
| static void __cdecl output_array(WCHAR *fmt, ...) | |
| { | |
| __ms_va_list va_args; | |
| __ms_va_start(va_args, fmt); | |
| output_formatstring(fmt, va_args); | |
| __ms_va_end(va_args); | |
| } | |
| /** | |
| * Used to output program list when used with --list | |
| */ | |
| static void ListUninstallPrograms(void) | |
| { | |
| unsigned int i; | |
| static WCHAR fmtW[] = {'%','1','|','|','|','%','2','\n',0}; | |
| FetchUninstallInformation(); | |
| for (i=0; i < numentries; i++) | |
| output_array(fmtW, entries[i].key, entries[i].descr); | |
| } | |
| static void RemoveSpecificProgram(WCHAR *nameW) | |
| { | |
| unsigned int i; | |
| FetchUninstallInformation(); | |
| for (i=0; i < numentries; i++) | |
| { | |
| if (CompareStringW(GetThreadLocale(), NORM_IGNORECASE, entries[i].key, -1, nameW, -1) == CSTR_EQUAL) | |
| { | |
| entries[i].active++; | |
| break; | |
| } | |
| } | |
| if (i < numentries) | |
| UninstallProgram(); | |
| else | |
| output_message(STRING_NO_APP_MATCH, nameW); | |
| } | |
| int wmain(int argc, WCHAR *argv[]) | |
| { | |
| LPCWSTR token = NULL; | |
| static const WCHAR helpW[] = { '-','-','h','e','l','p',0 }; | |
| static const WCHAR listW[] = { '-','-','l','i','s','t',0 }; | |
| static const WCHAR removeW[] = { '-','-','r','e','m','o','v','e',0 }; | |
| int i = 1; | |
| while( i<argc ) | |
| { | |
| token = argv[i++]; | |
| if( !lstrcmpW( token, helpW ) ) | |
| { | |
| output_message(STRING_HEADER); | |
| output_message(STRING_USAGE); | |
| return 0; | |
| } | |
| else if( !lstrcmpW( token, listW ) ) | |
| { | |
| ListUninstallPrograms(); | |
| return 0; | |
| } | |
| else if( !lstrcmpW( token, removeW ) ) | |
| { | |
| if( i >= argc ) | |
| { | |
| output_message(STRING_PARAMETER_REQUIRED); | |
| return 1; | |
| } | |
| RemoveSpecificProgram( argv[i++] ); | |
| return 0; | |
| } | |
| else | |
| { | |
| output_message(STRING_INVALID_OPTION, token); | |
| return 1; | |
| } | |
| } | |
| /* Start the GUI control panel */ | |
| Control_RunDLL(GetDesktopWindow(), 0, "appwiz.cpl", SW_SHOW); | |
| return 1; | |
| } | |
| /** | |
| * Used to sort entries by name. | |
| */ | |
| static int cmp_by_name(const void *a, const void *b) | |
| { | |
| return lstrcmpiW(((const uninst_entry *)a)->descr, ((const uninst_entry *)b)->descr); | |
| } | |
| /** | |
| * Fetch information from the uninstall key. | |
| */ | |
| static int FetchFromRootKey(HKEY root) | |
| { | |
| HKEY hkeyApp; | |
| int i; | |
| DWORD sizeOfSubKeyName, displen, uninstlen, value, type, size; | |
| WCHAR subKeyName[256]; | |
| sizeOfSubKeyName = 255; | |
| for (i=0; RegEnumKeyExW( root, i, subKeyName, &sizeOfSubKeyName, NULL, NULL, NULL, NULL ) != ERROR_NO_MORE_ITEMS; ++i) | |
| { | |
| RegOpenKeyExW(root, subKeyName, 0, KEY_READ, &hkeyApp); | |
| size = sizeof(value); | |
| if (!RegQueryValueExW(hkeyApp, SystemComponentW, NULL, &type, (LPBYTE)&value, &size) && | |
| type == REG_DWORD && value == 1) | |
| { | |
| RegCloseKey(hkeyApp); | |
| sizeOfSubKeyName = 255; | |
| continue; | |
| } | |
| if (!RegQueryValueExW(hkeyApp, DisplayNameW, NULL, NULL, NULL, &displen)) | |
| { | |
| WCHAR *command; | |
| size = sizeof(value); | |
| if (!RegQueryValueExW(hkeyApp, WindowsInstallerW, NULL, &type, (LPBYTE)&value, &size) && | |
| type == REG_DWORD && value == 1) | |
| { | |
| static const WCHAR fmtW[] = {'m','s','i','e','x','e','c',' ','/','x','%','s',0}; | |
| command = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(fmtW) + lstrlenW(subKeyName)) * sizeof(WCHAR)); | |
| wsprintfW(command, fmtW, subKeyName); | |
| } | |
| else if (!RegQueryValueExW(hkeyApp, UninstallCommandlineW, NULL, NULL, NULL, &uninstlen)) | |
| { | |
| command = HeapAlloc(GetProcessHeap(), 0, uninstlen); | |
| RegQueryValueExW(hkeyApp, UninstallCommandlineW, 0, 0, (LPBYTE)command, &uninstlen); | |
| } | |
| else | |
| { | |
| RegCloseKey(hkeyApp); | |
| sizeOfSubKeyName = 255; | |
| continue; | |
| } | |
| numentries++; | |
| entries = HeapReAlloc(GetProcessHeap(), 0, entries, numentries*sizeof(uninst_entry)); | |
| entries[numentries-1].root = root; | |
| entries[numentries-1].key = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(subKeyName)+1)*sizeof(WCHAR)); | |
| lstrcpyW(entries[numentries-1].key, subKeyName); | |
| entries[numentries-1].descr = HeapAlloc(GetProcessHeap(), 0, displen); | |
| RegQueryValueExW(hkeyApp, DisplayNameW, 0, 0, (LPBYTE)entries[numentries-1].descr, &displen); | |
| entries[numentries-1].command = command; | |
| entries[numentries-1].active = 0; | |
| WINE_TRACE("allocated entry #%d: %s (%s), %s\n", | |
| numentries, wine_dbgstr_w(entries[numentries-1].key), wine_dbgstr_w(entries[numentries-1].descr), wine_dbgstr_w(entries[numentries-1].command)); | |
| if(sFilter != NULL && StrStrIW(entries[numentries-1].descr,sFilter)==NULL) | |
| numentries--; | |
| } | |
| RegCloseKey(hkeyApp); | |
| sizeOfSubKeyName = 255; | |
| } | |
| return 1; | |
| } | |
| static int FetchUninstallInformation(void) | |
| { | |
| static const BOOL is_64bit = sizeof(void *) > sizeof(int); | |
| int rc = 0; | |
| HKEY root; | |
| numentries = 0; | |
| oldsel = -1; | |
| if (!entries) | |
| entries = HeapAlloc(GetProcessHeap(), 0, sizeof(uninst_entry)); | |
| if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE, PathUninstallW, 0, KEY_READ, &root)) | |
| { | |
| rc |= FetchFromRootKey(root); | |
| RegCloseKey(root); | |
| } | |
| if (is_64bit && | |
| !RegOpenKeyExW(HKEY_LOCAL_MACHINE, PathUninstallW, 0, KEY_READ|KEY_WOW64_32KEY, &root)) | |
| { | |
| rc |= FetchFromRootKey(root); | |
| RegCloseKey(root); | |
| } | |
| if (!RegOpenKeyExW(HKEY_CURRENT_USER, PathUninstallW, 0, KEY_READ, &root)) | |
| { | |
| rc |= FetchFromRootKey(root); | |
| RegCloseKey(root); | |
| } | |
| qsort(entries, numentries, sizeof(uninst_entry), cmp_by_name); | |
| return rc; | |
| } | |
| static void UninstallProgram(void) | |
| { | |
| unsigned int i; | |
| WCHAR errormsg[1024]; | |
| BOOL res; | |
| STARTUPINFOW si; | |
| PROCESS_INFORMATION info; | |
| DWORD exit_code; | |
| HKEY hkey; | |
| for (i=0; i < numentries; i++) | |
| { | |
| if (!(entries[i].active)) /* don't uninstall this one */ | |
| continue; | |
| WINE_TRACE("uninstalling %s\n", wine_dbgstr_w(entries[i].descr)); | |
| memset(&si, 0, sizeof(STARTUPINFOW)); | |
| si.cb = sizeof(STARTUPINFOW); | |
| si.wShowWindow = SW_NORMAL; | |
| res = CreateProcessW(NULL, entries[i].command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &info); | |
| if (res) | |
| { /* wait for the process to exit */ | |
| WaitForSingleObject(info.hProcess, INFINITE); | |
| res = GetExitCodeProcess(info.hProcess, &exit_code); | |
| WINE_TRACE("%d: %08x\n", res, exit_code); | |
| } | |
| else | |
| { | |
| WCHAR sAppName[MAX_STRING_LEN]; | |
| WCHAR sUninstallFailed[MAX_STRING_LEN]; | |
| HINSTANCE hInst = GetModuleHandleW(0); | |
| LoadStringW(hInst, IDS_APPNAME, sAppName, sizeof(sAppName)/sizeof(WCHAR)); | |
| LoadStringW(hInst, IDS_UNINSTALLFAILED, sUninstallFailed, sizeof(sUninstallFailed)/sizeof(WCHAR)); | |
| wsprintfW(errormsg, sUninstallFailed, entries[i].command); | |
| if(MessageBoxW(0, errormsg, sAppName, MB_YESNO | MB_ICONQUESTION)==IDYES) | |
| { | |
| /* delete the application's uninstall entry */ | |
| RegOpenKeyExW(entries[i].root, PathUninstallW, 0, KEY_READ, &hkey); | |
| RegDeleteKeyW(hkey, entries[i].key); | |
| RegCloseKey(hkey); | |
| } | |
| } | |
| } | |
| WINE_TRACE("finished uninstall phase.\n"); | |
| } |