From 57b775ef6ec27524a27784cf0cd6e8e4479c6782 Mon Sep 17 00:00:00 2001 From: Whindmar Saksit Date: Wed, 8 May 2024 23:58:54 +0200 Subject: [PATCH] [RAPPS] Automatically generate installer/uninstaller for downloaded zip/cab files (#6652) With a single database line added to applications distributed as zip/cab allows rapps.exe to act as an installer that automatically extracts the files and creates a startmenu shortcut. It can also uninstall the extracted files (and optionally other files and registry entries created by the application). --- base/applications/rapps/CMakeLists.txt | 7 +- base/applications/rapps/appdb.cpp | 205 +++-- base/applications/rapps/appinfo.cpp | 69 +- base/applications/rapps/cabinet.cpp | 32 +- base/applications/rapps/configparser.cpp | 53 ++ base/applications/rapps/geninst.cpp | 817 ++++++++++++++++++ base/applications/rapps/gui.cpp | 4 +- base/applications/rapps/include/appdb.h | 18 +- base/applications/rapps/include/appinfo.h | 52 +- .../applications/rapps/include/configparser.h | 6 + base/applications/rapps/include/misc.h | 65 +- base/applications/rapps/include/resource.h | 3 + base/applications/rapps/include/unattended.h | 3 + base/applications/rapps/lang/bg-BG.rc | 5 + base/applications/rapps/lang/cs-CZ.rc | 5 + base/applications/rapps/lang/de-DE.rc | 5 + base/applications/rapps/lang/en-US.rc | 5 + base/applications/rapps/lang/es-ES.rc | 5 + base/applications/rapps/lang/et-EE.rc | 5 + base/applications/rapps/lang/fr-FR.rc | 5 + base/applications/rapps/lang/he-IL.rc | 5 + base/applications/rapps/lang/hu-HU.rc | 5 + base/applications/rapps/lang/id-ID.rc | 5 + base/applications/rapps/lang/it-IT.rc | 5 + base/applications/rapps/lang/ja-JP.rc | 5 + base/applications/rapps/lang/no-NO.rc | 5 + base/applications/rapps/lang/pl-PL.rc | 5 + base/applications/rapps/lang/pt-BR.rc | 5 + base/applications/rapps/lang/pt-PT.rc | 5 + base/applications/rapps/lang/ro-RO.rc | 5 + base/applications/rapps/lang/ru-RU.rc | 5 + base/applications/rapps/lang/sk-SK.rc | 5 + base/applications/rapps/lang/sq-AL.rc | 5 + base/applications/rapps/lang/sv-SE.rc | 5 + base/applications/rapps/lang/tr-TR.rc | 5 + base/applications/rapps/lang/uk-UA.rc | 5 + base/applications/rapps/lang/zh-CN.rc | 5 + base/applications/rapps/lang/zh-HK.rc | 5 + base/applications/rapps/lang/zh-TW.rc | 5 + base/applications/rapps/loaddlg.cpp | 26 +- base/applications/rapps/misc.cpp | 170 +++- base/applications/rapps/unattended.cpp | 114 +++ base/applications/rapps/winmain.cpp | 1 + 43 files changed, 1638 insertions(+), 137 deletions(-) create mode 100644 base/applications/rapps/geninst.cpp diff --git a/base/applications/rapps/CMakeLists.txt b/base/applications/rapps/CMakeLists.txt index 5f3bbcc3cca..b53f91087af 100644 --- a/base/applications/rapps/CMakeLists.txt +++ b/base/applications/rapps/CMakeLists.txt @@ -2,6 +2,9 @@ project(rapps) include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/cryptlib) include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils) +include_directories( + ${REACTOS_SOURCE_DIR}/sdk/include/reactos/libs/zlib + ${REACTOS_SOURCE_DIR}/sdk/lib/3rdparty/zlib/contrib) include_directories(include) list(APPEND SOURCE @@ -11,6 +14,7 @@ list(APPEND SOURCE asyncinet.cpp cabinet.cpp configparser.cpp + geninst.cpp gui.cpp integrity.cpp loaddlg.cpp @@ -35,6 +39,7 @@ list(APPEND SOURCE include/settings.h include/unattended.h include/winmain.h + ${ZLIB_SOURCE} ) add_definitions( @@ -44,7 +49,7 @@ file(GLOB_RECURSE rapps_rc_deps res/*.*) add_rc_deps(rapps.rc ${rapps_rc_deps}) add_executable(rapps ${SOURCE} rapps.rc) set_module_type(rapps win32gui UNICODE) -target_link_libraries(rapps conutils ${PSEH_LIB} uuid wine cpprt atl_classes) +target_link_libraries(rapps conutils ${PSEH_LIB} uuid wine cpprt atl_classes minizip zlib) add_importlibs(rapps advapi32 comctl32 gdi32 wininet user32 shell32 shlwapi ole32 setupapi gdiplus msvcrt kernel32 ntdll) add_pch(rapps include/rapps.h SOURCE) add_dependencies(rapps rappsmsg) diff --git a/base/applications/rapps/appdb.cpp b/base/applications/rapps/appdb.cpp index 02a404f9f53..97ba9626a64 100644 --- a/base/applications/rapps/appdb.cpp +++ b/base/applications/rapps/appdb.cpp @@ -14,7 +14,7 @@ static HKEY g_RootKeyEnum[3] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_LOCAL_MACHINE}; -static REGSAM g_RegSamEnum[3] = {KEY_WOW64_32KEY, KEY_WOW64_32KEY, KEY_WOW64_64KEY}; +static REGSAM g_RegSamEnum[3] = {0, KEY_WOW64_32KEY, KEY_WOW64_64KEY}; #define UNINSTALL_SUBKEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" static VOID @@ -34,8 +34,8 @@ CAppDB::CAppDB(const CStringW &path) : m_BasePath(path) m_BasePath.Canonicalize(); } -CAppInfo * -CAppDB::FindByPackageName(const CStringW &name) +CAvailableApplicationInfo * +CAppDB::FindAvailableByPackageName(const CStringW &name) { POSITION CurrentListPosition = m_Available.GetHeadPosition(); while (CurrentListPosition) @@ -43,7 +43,7 @@ CAppDB::FindByPackageName(const CStringW &name) CAppInfo *Info = m_Available.GetNext(CurrentListPosition); if (Info->szIdentifier == name) { - return Info; + return static_cast(Info); } } return NULL; @@ -97,7 +97,7 @@ CAppDB::EnumerateFiles() { CConfigParser *Parser = new CConfigParser(CPathW(AppsPath) += FindFileData.cFileName); int Cat; - if (!Parser->GetInt(L"Category", Cat)) + if (!Parser->GetInt(DB_CATEGORY, Cat)) Cat = ENUM_INVALID; Info = new CAvailableApplicationInfo(Parser, szPkgName, static_cast(Cat), AppsPath); @@ -141,74 +141,123 @@ CAppDB::UpdateAvailable() EnumerateFiles(); } +static inline HKEY +GetRootKeyInfo(UINT Index, REGSAM &RegSam) +{ + C_ASSERT(_countof(g_RootKeyEnum) == _countof(g_RegSamEnum)); + if (Index < _countof(g_RootKeyEnum)) + { + RegSam = g_RegSamEnum[Index]; + return g_RootKeyEnum[Index]; + } + return NULL; +} + +HKEY +CAppDB::EnumInstalledRootKey(UINT Index, REGSAM &RegSam) +{ + // Loop for through all combinations. + // Note that HKEY_CURRENT_USER\Software does not have a redirect + // https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys#redirected-shared-and-reflected-keys-under-wow64 + if (Index < (IsSystem64Bit() ? 3 : 2)) + return GetRootKeyInfo(Index, RegSam); + else + return NULL; +} + +CInstalledApplicationInfo * +CAppDB::CreateInstalledAppByRegistryKey(LPCWSTR KeyName, HKEY hKeyParent, UINT KeyIndex) +{ + CRegKey hSubKey; + if (hSubKey.Open(hKeyParent, KeyName, KEY_READ) != ERROR_SUCCESS) + return NULL; + DWORD value, size; + + size = sizeof(DWORD); + if (!RegQueryValueExW(hSubKey, L"SystemComponent", NULL, NULL, (LPBYTE)&value, &size) && value == 1) + { + // Ignore system components + return NULL; + } + + size = 0; + BOOL bIsUpdate = !RegQueryValueExW(hSubKey, L"ParentKeyName", NULL, NULL, NULL, &size); + + AppsCategories cat = bIsUpdate ? ENUM_UPDATES : ENUM_INSTALLED_APPLICATIONS; + CInstalledApplicationInfo *pInfo; + pInfo = new CInstalledApplicationInfo(hSubKey.Detach(), KeyName, cat, KeyIndex); + if (pInfo && pInfo->Valid()) + { + return pInfo; + } + delete pInfo; + return NULL; +} + +CInstalledApplicationInfo * +CAppDB::EnumerateRegistry(CAtlList *List, LPCWSTR SearchOnly) +{ + ATLASSERT(List || SearchOnly); + REGSAM wowsam; + HKEY hRootKey; + for (UINT rki = 0; (hRootKey = EnumInstalledRootKey(rki, wowsam)); ++rki) + { + CRegKey hKey; + if (hKey.Open(hRootKey, UNINSTALL_SUBKEY, KEY_READ | wowsam) != ERROR_SUCCESS) + { + continue; + } + for (DWORD Index = 0;; ++Index) + { + WCHAR szKeyName[MAX_PATH]; + DWORD dwSize = _countof(szKeyName); + if (hKey.EnumKey(Index, szKeyName, &dwSize) != ERROR_SUCCESS) + { + break; + } + if (List || !StrCmpIW(SearchOnly, szKeyName)) + { + CInstalledApplicationInfo *Info; + Info = CreateInstalledAppByRegistryKey(szKeyName, hKey, rki); + if (Info) + { + if (List) + List->AddTail(Info); + else + return Info; + } + } + } + } + return NULL; +} + VOID CAppDB::UpdateInstalled() { // Remove all old entries ClearList(m_Installed); - int LoopKeys = 2; + EnumerateRegistry(&m_Installed, NULL); +} - if (IsSystem64Bit()) +CInstalledApplicationInfo * +CAppDB::CreateInstalledAppByRegistryKey(LPCWSTR Name) +{ + return EnumerateRegistry(NULL, Name); +} + +CInstalledApplicationInfo * +CAppDB::CreateInstalledAppInstance(LPCWSTR KeyName, BOOL User, REGSAM WowSam) +{ + HKEY hRootKey = User ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + UINT KeyIndex = User ? (0) : ((WowSam & KEY_WOW64_64KEY) ? 2 : 1); + CRegKey hKey; + if (hKey.Open(hRootKey, UNINSTALL_SUBKEY, KEY_READ | WowSam) == ERROR_SUCCESS) { - // loop for all 3 combination. - // note that HKEY_CURRENT_USER\Software don't have a redirect - // https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys#redirected-shared-and-reflected-keys-under-wow64 - LoopKeys = 3; - } - - for (int keyIndex = 0; keyIndex < LoopKeys; keyIndex++) - { - LONG ItemIndex = 0; - WCHAR szKeyName[MAX_PATH]; - - CRegKey hKey; - if (hKey.Open(g_RootKeyEnum[keyIndex], UNINSTALL_SUBKEY, KEY_READ | g_RegSamEnum[keyIndex]) != ERROR_SUCCESS) - { - continue; - } - - while (1) - { - DWORD dwSize = _countof(szKeyName); - if (hKey.EnumKey(ItemIndex, szKeyName, &dwSize) != ERROR_SUCCESS) - { - break; - } - - ItemIndex++; - - CRegKey hSubKey; - if (hSubKey.Open(hKey, szKeyName, KEY_READ) == ERROR_SUCCESS) - { - DWORD dwValue = 0; - - dwSize = sizeof(DWORD); - if (RegQueryValueExW(hSubKey, L"SystemComponent", NULL, NULL, (LPBYTE)&dwValue, &dwSize) == - ERROR_SUCCESS && - dwValue == 1) - { - // Ignore system components - continue; - } - - BOOL bIsUpdate = - (RegQueryValueExW(hSubKey, L"ParentKeyName", NULL, NULL, NULL, &dwSize) == ERROR_SUCCESS); - - CInstalledApplicationInfo *Info = new CInstalledApplicationInfo( - hSubKey.Detach(), szKeyName, bIsUpdate ? ENUM_UPDATES : ENUM_INSTALLED_APPLICATIONS, keyIndex); - - if (Info->Valid()) - { - m_Installed.AddTail(Info); - } - else - { - delete Info; - } - } - } + return CreateInstalledAppByRegistryKey(KeyName, hKey, KeyIndex); } + return NULL; } static void @@ -262,30 +311,28 @@ CAppDB::RemoveCached() RemoveDirectoryW(m_BasePath); } -BOOL +DWORD CAppDB::RemoveInstalledAppFromRegistry(const CAppInfo *Info) { // Validate that this is actually an installed app / update ATLASSERT(Info->iCategory == ENUM_INSTALLED_APPLICATIONS || Info->iCategory == ENUM_UPDATES); if (Info->iCategory != ENUM_INSTALLED_APPLICATIONS && Info->iCategory != ENUM_UPDATES) - return FALSE; + return ERROR_INVALID_PARAMETER; - // Grab the index in the registry keys const CInstalledApplicationInfo *InstalledInfo = static_cast(Info); - ATLASSERT(InstalledInfo->iKeyIndex >= 0 && InstalledInfo->iKeyIndex < (int)_countof(g_RootKeyEnum)); - if (InstalledInfo->iKeyIndex < 0 && InstalledInfo->iKeyIndex >= (int)_countof(g_RootKeyEnum)) - return FALSE; - int keyIndex = InstalledInfo->iKeyIndex; - - // Grab the registry key name CStringW Name = InstalledInfo->szIdentifier; + REGSAM wowsam; + HKEY hRoot = GetRootKeyInfo(InstalledInfo->m_KeyInfo, wowsam); + ATLASSERT(hRoot); + if (!hRoot) + return ERROR_OPEN_FAILED; - // Recursively delete this key CRegKey Uninstall; - if (Uninstall.Open(g_RootKeyEnum[keyIndex], UNINSTALL_SUBKEY, KEY_READ | KEY_WRITE | g_RegSamEnum[keyIndex]) != - ERROR_SUCCESS) - return FALSE; - - return Uninstall.RecurseDeleteKey(Name) == ERROR_SUCCESS; + LSTATUS err = Uninstall.Open(hRoot, UNINSTALL_SUBKEY, KEY_READ | KEY_WRITE | wowsam); + if (err == ERROR_SUCCESS) + { + err = Uninstall.RecurseDeleteKey(Name); + } + return err; } diff --git a/base/applications/rapps/appinfo.cpp b/base/applications/rapps/appinfo.cpp index 7e00c4224e8..eefbb91d2e5 100644 --- a/base/applications/rapps/appinfo.cpp +++ b/base/applications/rapps/appinfo.cpp @@ -116,7 +116,7 @@ VOID CAvailableApplicationInfo::InsertVersionInfo(CAppRichEdit *RichEdit) { CStringW szRegName; - m_Parser->GetString(L"RegName", szRegName); + m_Parser->GetString(DB_REGNAME, szRegName); BOOL bIsInstalled = ::GetInstalledVersion(NULL, szRegName) || ::GetInstalledVersion(NULL, szDisplayName); if (bIsInstalled) @@ -131,11 +131,14 @@ CAvailableApplicationInfo::InsertVersionInfo(CAppRichEdit *RichEdit) { BOOL bHasUpdate = CompareVersion(szInstalledVersion, szDisplayVersion) < 0; if (bHasUpdate) + { RichEdit->LoadAndInsertText(IDS_STATUS_UPDATE_AVAILABLE, CFE_ITALIC); + RichEdit->LoadAndInsertText(IDS_AINFO_VERSION, szInstalledVersion, 0); + } else + { RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC); - - RichEdit->LoadAndInsertText(IDS_AINFO_VERSION, szInstalledVersion, 0); + } } else { @@ -364,8 +367,19 @@ CAvailableApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CSt UrlDownload = m_szUrlDownload; } +InstallerType +CAvailableApplicationInfo::GetInstallerType() const +{ + CStringW str; + m_Parser->GetString(DB_INSTALLER, str); + if (str.CompareNoCase(DB_GENINSTSECTION) == 0) + return INSTALLER_GENERATE; + else + return INSTALLER_UNKNOWN; +} + BOOL -CAvailableApplicationInfo::UninstallApplication(BOOL bModify) +CAvailableApplicationInfo::UninstallApplication(UninstallCommandFlags Flags) { ATLASSERT(FALSE && "Should not be called"); return FALSE; @@ -374,9 +388,8 @@ CAvailableApplicationInfo::UninstallApplication(BOOL bModify) CInstalledApplicationInfo::CInstalledApplicationInfo( HKEY Key, const CStringW &KeyName, - AppsCategories Category, - int KeyIndex) - : CAppInfo(KeyName, Category), m_hKey(Key), iKeyIndex(KeyIndex) + AppsCategories Category, UINT KeyInfo) + : CAppInfo(KeyName, Category), m_hKey(Key), m_KeyInfo(KeyInfo) { if (GetApplicationRegString(L"DisplayName", szDisplayName)) { @@ -561,15 +574,51 @@ CInstalledApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CSt ATLASSERT(FALSE && "Should not be called"); } -BOOL -CInstalledApplicationInfo::UninstallApplication(BOOL bModify) +InstallerType +CInstalledApplicationInfo::GetInstallerType() const { + CRegKey reg; + if (reg.Open(m_hKey, GENERATE_ARPSUBKEY, KEY_READ) == ERROR_SUCCESS) + { + return INSTALLER_GENERATE; + } + return INSTALLER_UNKNOWN; +} + +BOOL +CInstalledApplicationInfo::UninstallApplication(UninstallCommandFlags Flags) +{ + if (GetInstallerType() == INSTALLER_GENERATE) + { + return UninstallGenerated(*this, Flags); + } + + BOOL bModify = Flags & UCF_MODIFY; if (m_szUninstallString.IsEmpty()) { RetrieveUninstallStrings(); } - BOOL bSuccess = StartProcess(bModify ? m_szModifyString : m_szUninstallString, TRUE); + CStringW cmd = bModify ? m_szModifyString : m_szUninstallString; + if ((Flags & (UCF_MODIFY | UCF_SILENT)) == UCF_SILENT) + { + DWORD msi = 0; + msi = GetApplicationRegDword(L"WindowsInstaller", &msi) && msi; + if (msi) + { + cmd += L" /qn"; + } + else + { + CStringW silentcmd; + if (GetApplicationRegString(L"QuietUninstallString", silentcmd) && !silentcmd.IsEmpty()) + { + cmd = silentcmd; + } + } + } + + BOOL bSuccess = StartProcess(cmd, TRUE); if (bSuccess && !bModify) WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_REMOVE, szDisplayName); diff --git a/base/applications/rapps/cabinet.cpp b/base/applications/rapps/cabinet.cpp index a86ad8cecc2..46b9484f161 100644 --- a/base/applications/rapps/cabinet.cpp +++ b/base/applications/rapps/cabinet.cpp @@ -53,6 +53,13 @@ MultiByteToWide(const CStringA &szSource, CStringW &szDest, UINT Codepage) return sz != 0; } +struct NotifyData +{ + EXTRACTCALLBACK Callback; + void *CallerCookie; + LPCSTR OutputDir; +}; + /* FDICreate callbacks */ FNALLOC(fnMemAlloc) @@ -143,6 +150,7 @@ FNSEEK(fnFileSeek) FNFDINOTIFY(fnNotify) { INT_PTR iResult = 0; + NotifyData *pND = (NotifyData *)pfdin->pv; switch (fdint) { @@ -151,9 +159,15 @@ FNFDINOTIFY(fnNotify) CStringW szExtractDir, szCabFileName; // Append the destination directory to the file name. - MultiByteToWide((LPCSTR)pfdin->pv, szExtractDir, CP_UTF8); + MultiByteToWide(pND->OutputDir, szExtractDir, CP_UTF8); MultiByteToWide(pfdin->psz1, szCabFileName, CP_ACP); + if (!NotifyFileExtractCallback(szCabFileName, pfdin->cb, pfdin->attribs, + pND->Callback, pND->CallerCookie)) + { + break; // Skip file + } + if (szCabFileName.Find('\\') >= 0) { CStringW szNewDirName = szExtractDir; @@ -230,10 +244,10 @@ typedef BOOL (*fnFDIDestroy)(HFDI); /* * Extraction function - * TODO: require only a full path to the cab as an argument */ BOOL -ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir) +ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir, + EXTRACTCALLBACK Callback, void *Cookie) { HINSTANCE hCabinetDll; HFDI ExtractHandler; @@ -243,6 +257,7 @@ ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const C fnFDICopy pfnFDICopy; fnFDIDestroy pfnFDIDestroy; BOOL bResult; + NotifyData nd = { Callback, Cookie }; // Load cabinet.dll and extract needed functions hCabinetDll = LoadLibraryW(L"cabinet.dll"); @@ -290,12 +305,21 @@ ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const C // Add a slash to cab name as required by the api szCabNameUTF8 = "\\" + szCabNameUTF8; + nd.OutputDir = szOutputDirUTF8.GetString(); bResult = pfnFDICopy( ExtractHandler, (LPSTR)szCabNameUTF8.GetString(), (LPSTR)szCabDirUTF8.GetString(), 0, fnNotify, NULL, - (void FAR *)szOutputDirUTF8.GetString()); + (void FAR *)&nd); } pfnFDIDestroy(ExtractHandler); FreeLibrary(hCabinetDll); return bResult; } + +BOOL +ExtractFilesFromCab(LPCWSTR FullCabPath, const CStringW &szOutputDir, + EXTRACTCALLBACK Callback, void *Cookie) +{ + CStringW dir, file = SplitFileAndDirectory(FullCabPath, &dir); + return ExtractFilesFromCab(file, dir, szOutputDir, Callback, Cookie); +} diff --git a/base/applications/rapps/configparser.cpp b/base/applications/rapps/configparser.cpp index d550fbaedd4..fbe2c917c01 100644 --- a/base/applications/rapps/configparser.cpp +++ b/base/applications/rapps/configparser.cpp @@ -25,6 +25,20 @@ struct CSectionNames }; static CSectionNames g_Names; +HRESULT +ReadIniValue(LPCWSTR File, LPCWSTR Section, LPCWSTR Name, CStringW &Output) +{ + for (DWORD len = 256, ret;; len *= 2) + { + ret = GetPrivateProfileString(Section, Name, L"\n", Output.GetBuffer(len), len, File); + if (ret + 1 != len) + { + Output.ReleaseBuffer(ret); + return ret && Output[0] != L'\n' ? ret : HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + } + } +} + CConfigParser::CConfigParser(const CStringW &FilePath) : szConfigPath(FilePath) { CacheINI(); @@ -166,3 +180,42 @@ CConfigParser::GetInt(const CStringW &KeyName, INT &iResult) // we only care about values > 0 return (iResult > 0); } + +UINT +CConfigParser::GetSectionString(LPCWSTR Section, LPCWSTR Name, CStringW &Result) +{ + HRESULT hr; // Return value; length of ini string or 0 on failure. + CStringW SecBuf; + WCHAR FullLoc[5], *NeutralLoc = FullLoc + 2; + GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_ILANGUAGE, FullLoc, _countof(FullLoc)); + + SecBuf.Format(L"%s.%s.%s", Section, FullLoc, CurrentArchitecture); + if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0) + return hr; + + if (*NeutralLoc) + { + SecBuf.Format(L"%s.%s.%s", Section, NeutralLoc, CurrentArchitecture); + if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0) + return hr; + } + + SecBuf.Format(L"%s.%s", Section, CurrentArchitecture); + if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0) + return hr; + + SecBuf.Format(L"%s.%s", Section, FullLoc); + if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0) + return hr; + + if (*NeutralLoc) + { + SecBuf.Format(L"%s.%s", Section, NeutralLoc); + if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0) + return hr; + } + + if ((hr = ReadIniValue(szConfigPath, Section, Name, Result)) > 0) + return hr; + return 0; +} diff --git a/base/applications/rapps/geninst.cpp b/base/applications/rapps/geninst.cpp new file mode 100644 index 00000000000..d304eb24eb7 --- /dev/null +++ b/base/applications/rapps/geninst.cpp @@ -0,0 +1,817 @@ +/* + * PROJECT: ReactOS Applications Manager + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Act as installer and uninstaller for applications distributed in archives + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#include "defines.h" +#include +#include +#include +#include +#include "resource.h" +#include "appdb.h" +#include "appinfo.h" +#include "misc.h" +#include "configparser.h" +#include "unattended.h" + +#include "minizip/ioapi.h" +#include "minizip/iowin32.h" +#include "minizip/unzip.h" +extern "C" { + #include "minizip/ioapi.c" + #include "minizip/iowin32.c" + #include "minizip/unzip.c" +} + +#define REGPATH_UNINSTALL L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" + +#define DB_GENINST_FILES L"Files" +#define DB_GENINST_DIR L"Dir" +#define DB_GENINST_ICON L"Icon" +#define DB_GENINST_LNK L"Lnk" +#define DB_GENINST_DELFILE L"DelFile" // Delete files generated by the application +#define DB_GENINST_DELDIR L"DelDir" +#define DB_GENINST_DELDIREMPTY L"DelDirEmpty" +#define DB_GENINST_DELREG L"DelReg" +#define DB_GENINST_DELREGEMPTY L"DelRegEmpty" + +enum { + UNOP_FILE = 'F', + UNOP_DIR = 'D', + UNOP_EMPTYDIR = 'd', + UNOP_REGKEY = 'K', + UNOP_EMPTYREGKEY = 'k', +}; + +static int +ExtractFilesFromZip(LPCWSTR Archive, const CStringW &OutputDir, + EXTRACTCALLBACK Callback, void *Cookie) +{ + const UINT pkzefsutf8 = 1 << 11; // APPNOTE; APPENDIX D + zlib_filefunc64_def zff; + fill_win32_filefunc64W(&zff); + unzFile hzf = unzOpen2_64(Archive, &zff); + if (!hzf) + return UNZ_BADZIPFILE; + CStringA narrow; + CStringW path, dir; + int zerr = unzGoToFirstFile(hzf); + for (; zerr == UNZ_OK; zerr = unzGoToNextFile(hzf)) + { + unz_file_info64 fi; + zerr = unzGetCurrentFileInfo64(hzf, &fi, NULL, 0, NULL, 0, NULL, 0); + if (zerr != UNZ_OK) + break; + LPSTR file = narrow.GetBuffer(fi.size_filename); + zerr = unzGetCurrentFileInfo64(hzf, &fi, file, narrow.GetAllocLength(), NULL, 0, NULL, 0); + if (zerr != UNZ_OK) + break; + narrow.ReleaseBuffer(fi.size_filename); + narrow.Replace('/', '\\'); + while (narrow[0] == '\\') + narrow = narrow.Mid(1); + UINT codepage = (fi.flag & pkzefsutf8) ? CP_UTF8 : 1252; + UINT cch = MultiByteToWideChar(codepage, 0, narrow, -1, NULL, 0); + cch = MultiByteToWideChar(codepage, 0, narrow, -1, path.GetBuffer(cch - 1), cch); + if (!cch) + break; + path.ReleaseBuffer(cch - 1); + DWORD fileatt = FILE_ATTRIBUTE_NORMAL, err; + BYTE attsys = HIBYTE(fi.version), dos = 0, ntfs = 10, vfat = 14; + if ((attsys == dos || attsys == ntfs || attsys == vfat) && LOBYTE(fi.external_fa)) + fileatt = LOBYTE(fi.external_fa); + + if (!NotifyFileExtractCallback(path, fi.uncompressed_size, fileatt, + Callback, Cookie)) + continue; // Skip file + + path = BuildPath(OutputDir, path); + SplitFileAndDirectory(path, &dir); + if (!dir.IsEmpty() && (err = CreateDirectoryTree(dir))) + { + zerr = UNZ_ERRNO; + SetLastError(err); + break; + } + + if ((zerr = unzOpenCurrentFile(hzf)) != UNZ_OK) + break; + zerr = UNZ_ERRNO; + HANDLE hFile = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_DELETE, + NULL, CREATE_ALWAYS, fileatt, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD len = 1024 * 4, cb; + LPSTR buf = narrow.GetBuffer(len); + for (zerr = UNZ_OK; zerr == UNZ_OK;) + { + len = zerr = unzReadCurrentFile(hzf, buf, len); + if (zerr <= 0) + break; + zerr = WriteFile(hFile, buf, len, &cb, NULL) && cb == len ? UNZ_OK : UNZ_ERRNO; + } + CloseHandle(hFile); + } + unzCloseCurrentFile(hzf); + } + unzClose(hzf); + return zerr == UNZ_END_OF_LIST_OF_FILE ? UNZ_OK : zerr; +} + +static UINT +ExtractZip(LPCWSTR Archive, const CStringW &OutputDir, + EXTRACTCALLBACK Callback, void *Cookie) +{ + int zerr = ExtractFilesFromZip(Archive, OutputDir, Callback, Cookie); + return zerr == UNZ_ERRNO ? GetLastError() : zerr ? ERROR_INTERNAL_ERROR : 0; +} + +static UINT +ExtractCab(LPCWSTR Archive, const CStringW &OutputDir, + EXTRACTCALLBACK Callback, void *Cookie) +{ + if (ExtractFilesFromCab(Archive, OutputDir, Callback, Cookie)) + return ERROR_SUCCESS; + UINT err = GetLastError(); + return err ? err : ERROR_INTERNAL_ERROR; +} + +enum { IM_STARTPROGRESS = WM_APP, IM_PROGRESS, IM_END }; + +static struct CommonInfo +{ + LPCWSTR AppName; + HWND hDlg; + DWORD Error, Count; + BOOL Silent; + CRegKey *ArpKey, Entries; + + CommonInfo(LPCWSTR DisplayName, BOOL IsSilent = FALSE) + : AppName(DisplayName), hDlg(NULL), Error(0), Count(0), Silent(IsSilent), ArpKey(NULL) + { + } + inline HWND GetGuiOwner() const { return Silent ? NULL : hDlg; } +} *g_pInfo; + +struct InstallInfo : CommonInfo +{ + CConfigParser &Parser; + LPCWSTR ArchivePath, InstallDir, ShortcutFile; + CStringW UninstFile, MainApp; + UINT InstallDirLen, EntryCount; + BOOL PerUser; + + InstallInfo(LPCWSTR AppName, CConfigParser &CP, LPCWSTR Archive) + : CommonInfo(AppName), Parser(CP), ArchivePath(Archive) + { + EntryCount = 0; + } +}; + +static UINT +ErrorBox(UINT Error = GetLastError()) +{ + if (!Error) + Error = ERROR_INTERNAL_ERROR; + WCHAR buf[400]; + UINT fmf = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM; + FormatMessageW(fmf, NULL, Error, 0, buf, _countof(buf), NULL); + MessageBoxW(g_pInfo->GetGuiOwner(), buf, 0, MB_OK | MB_ICONSTOP); + g_pInfo->Error = Error; + return Error; +} + +static LPCWSTR +GetCommonString(LPCWSTR Name, CStringW &Output, LPCWSTR Default = L"") +{ + InstallInfo &Info = *static_cast(g_pInfo); + BOOL found = Info.Parser.GetString(Name, Output); + return (found && !Output.IsEmpty() ? Output : Output = Default).GetString(); +} + +static LPCWSTR +GetGenerateString(LPCWSTR Name, CStringW &Output, LPCWSTR Default = L"") +{ + InstallInfo &Info = *static_cast(g_pInfo); + UINT r = Info.Parser.GetSectionString(DB_GENINSTSECTION, Name, Output); + return (r ? Output : Output = Default).GetString(); +} + +static void +WriteArpEntry(LPCWSTR Name, LPCWSTR Value, UINT Type = REG_SZ) +{ + // Write a "Add/Remove programs" value if we have a valid uninstaller key + if (g_pInfo->ArpKey) + { + UINT err = g_pInfo->ArpKey->SetStringValue(Name, Value, Type); + if (err) + ErrorBox(err); + } +} + +static BOOL +AddEntry(WCHAR Type, LPCWSTR Value) +{ + InstallInfo &Info = *static_cast(g_pInfo); + CStringW name; + name.Format(L"%c%u", Type, ++Info.EntryCount); + UINT err = Info.Entries.SetStringValue(name, Value); + if (err) + ErrorBox(err); + return !err; +} + +static HRESULT +GetCustomIconPath(InstallInfo &Info, CStringW &Path) +{ + if (*GetGenerateString(DB_GENINST_ICON, Path)) + { + Path = BuildPath(Info.InstallDir, Path); + int idx = PathParseIconLocation(Path.GetBuffer()); + Path.ReleaseBuffer(); + HICON hIco = NULL; + if (ExtractIconExW(Path, idx, &hIco, NULL, 1)) + DestroyIcon(hIco); + if (idx) + Path.AppendFormat(L",%d", idx); + return hIco ? S_OK : S_FALSE; + } + return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); +} + +static BOOL +GetLocalizedSMFolderName(LPCWSTR WinVal, LPCWSTR RosInf, LPCWSTR RosVal, CStringW &Output) +{ + CRegKey key; + if (key.Open(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion", KEY_READ) == ERROR_SUCCESS && + GetRegString(key, WinVal, Output) && !Output.IsEmpty()) + { + return TRUE; + } + WCHAR windir[MAX_PATH]; + GetWindowsDirectoryW(windir, _countof(windir)); + CStringW path = BuildPath(BuildPath(windir, L"inf"), RosInf), section; + DWORD lang = 0, lctype = LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER; + if (GetLocaleInfoW(GetUserDefaultLCID(), lctype, (LPWSTR) &lang, sizeof(lang) / sizeof(WCHAR))) + { + section.Format(L"Strings.%.4x", lang); + if (ReadIniValue(path, section, RosVal, Output) > 0) + return TRUE; + section.Format(L"Strings.%.2x", PRIMARYLANGID(lang)); + if (ReadIniValue(path, section, RosVal, Output) > 0) + return TRUE; + } + return ReadIniValue(path, L"Strings", RosVal, Output) > 0; +} + +static BOOL +CreateShortcut(const CStringW &Target) +{ + InstallInfo &Info = *static_cast(g_pInfo); + UINT csidl = Info.PerUser ? CSIDL_PROGRAMS : CSIDL_COMMON_PROGRAMS; + CStringW rel = Info.ShortcutFile, path, dir, tmp; + + if (FAILED(GetSpecialPath(csidl, path, Info.GetGuiOwner()))) + return TRUE; // Pretend everything is OK + + int cat; + if (Info.Parser.GetInt(DB_CATEGORY, cat) && cat == ENUM_CAT_GAMES) + { + // Try to find the name of the Games folder in the Start Menu + if (GetLocalizedSMFolderName(L"SM_GamesName", L"shortcuts.inf", L"Games", tmp)) + { + path = BuildPath(path, tmp); + } + } + + // SHPathPrepareForWrite will prepare the necessary directories. + // Windows and ReactOS SHPathPrepareForWrite do not support '/'. + rel.Replace('/', '\\'); + path = BuildPath(path, rel.GetString()); + UINT SHPPFW = SHPPFW_DIRCREATE | SHPPFW_IGNOREFILENAME; + HRESULT hr = SHPathPrepareForWriteW(Info.GetGuiOwner(), NULL, path, SHPPFW); + if ((Info.Error = ErrorFromHResult(hr)) != 0) + { + ErrorBox(Info.Error); + return FALSE; + } + + CComPtr link; + hr = link.CoCreateInstance(CLSID_ShellLink, IID_IShellLinkW); + if (SUCCEEDED(hr)) + { + if (SUCCEEDED(hr = link->SetPath(Target))) + { + if (SUCCEEDED(GetCustomIconPath(Info, tmp))) + { + LPWSTR p = tmp.GetBuffer(); + int idx = PathParseIconLocation(p); + link->SetIconLocation(p, idx); + } + CComPtr persist; + if (SUCCEEDED(hr = link->QueryInterface(IID_IPersistFile, (void**)&persist))) + { + hr = persist->Save(path, FALSE); + } + } + } + if (SUCCEEDED(hr)) + { + if (AddEntry(UNOP_FILE, path)) + { + SplitFileAndDirectory(path, &dir); + AddEntry(UNOP_EMPTYDIR, dir); + } + } + else + { + ErrorBox(ErrorFromHResult(hr)); + } + return !Info.Error; +} + +static BOOL +InstallFiles(const CStringW &SourceDirBase, const CStringW &Spec, + const CStringW &DestinationDir) +{ + InstallInfo &Info = *static_cast(g_pInfo); + CStringW sourcedir, filespec; + filespec = SplitFileAndDirectory(Spec, &sourcedir); // Split "[OptionalDir\]*.ext" + sourcedir = BuildPath(SourceDirBase, sourcedir); + BOOL success = TRUE; + WIN32_FIND_DATAW wfd; + HANDLE hFind = FindFirstFileW(BuildPath(sourcedir, filespec), &wfd); + if (hFind == INVALID_HANDLE_VALUE) + { + DWORD error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND) + return TRUE; + else + return !ErrorBox(error); + } + + for (;;) + { + CStringW from = BuildPath(sourcedir, wfd.cFileName); + CStringW to = BuildPath(DestinationDir, wfd.cFileName); + CStringW uninstpath = to.Mid(Info.InstallDirLen - 1); + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + LPWSTR p = wfd.cFileName; + BOOL dots = p[0] == '.' && (!p[1] || (p[1] == '.' && !p[2])); + if (!dots) + { + Info.Error = CreateDirectoryTree(to); + if (Info.Error) + { + success = !ErrorBox(Info.Error); + } + else + { + success = AddEntry(UNOP_EMPTYDIR, uninstpath); + } + + if (success) + { + success = InstallFiles(from, filespec, to); + } + } + } + else + { + success = MoveFileEx(from, to, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING); + if (success) + { + if (Info.MainApp.IsEmpty()) + { + Info.MainApp = to; + } + success = AddEntry(UNOP_FILE, uninstpath); + } + else + { + ErrorBox(); + } + SendMessage(g_pInfo->hDlg, IM_PROGRESS, 0, 0); + } + + if (!success || !FindNextFileW(hFind, &wfd)) + break; + } + FindClose(hFind); + return success; +} + +static void +AddUninstallOperationsFromDB(LPCWSTR Name, WCHAR UnOp, CStringW PathPrefix = CStringW(L"")) +{ + CStringW item, tmp; + if (*GetGenerateString(Name, tmp)) + { + for (int pos = 1; pos > 0;) + { + pos = tmp.Find(L'|'); + item = pos <= 0 ? tmp : tmp.Left(pos); + tmp = tmp.Mid(pos + 1); + AddEntry(UnOp, PathPrefix + item); + } + } +} + +static BOOL CALLBACK +ExtractCallback(const EXTRACTCALLBACKINFO &, void *Cookie) +{ + InstallInfo &Info = *(InstallInfo *) Cookie; + Info.Count += 1; + return TRUE; +} + +static DWORD CALLBACK +ExtractAndInstallThread(LPVOID Parameter) +{ + const BOOL PerUserModeDefault = TRUE; + InstallInfo &Info = *static_cast(g_pInfo); + LPCWSTR AppName = Info.AppName, Archive = Info.ArchivePath, None = L"!"; + CStringW installdir, tempdir, files, shortcut, tmp; + HRESULT hr; + CRegKey arpkey; + Info.ArpKey = &arpkey; + + if (!*GetGenerateString(DB_GENINST_FILES, files, L"*.exe|*.*")) + return ErrorBox(ERROR_BAD_FORMAT); + + GetCommonString(DB_SCOPE, tmp); + if (tmp.CompareNoCase(L"User") == 0) + Info.PerUser = TRUE; + else if (tmp.CompareNoCase(L"Machine") == 0) + Info.PerUser = FALSE; + else + Info.PerUser = PerUserModeDefault; + + hr = GetProgramFilesPath(installdir, Info.PerUser, Info.GetGuiOwner()); + if ((Info.Error = ErrorFromHResult(hr)) != 0) + return ErrorBox(Info.Error); + + GetGenerateString(DB_GENINST_DIR, tmp); + if (tmp.Find('%') == 0 && ExpandEnvStrings(tmp)) + installdir = tmp; + else if (tmp.Compare(None)) + installdir = BuildPath(installdir, tmp.IsEmpty() ? AppName : tmp.GetString()); + Info.InstallDir = installdir.GetString(); + Info.InstallDirLen = installdir.GetLength() + 1; + hr = SHPathPrepareForWriteW(Info.GetGuiOwner(), NULL, installdir, SHPPFW_DIRCREATE); + if ((Info.Error = ErrorFromHResult(hr)) != 0) + return ErrorBox(Info.Error); + + // Create the destination directory, and inside it, a temporary directory + // where we will extract the archive to before moving the files to their + // final location (adding uninstall entries as we go) + tempdir.Format(L"%s\\~RAM%u.tmp", installdir.GetString(), GetCurrentProcessId()); + Info.Error = CreateDirectoryTree(tempdir.GetString()); + if (Info.Error) + return ErrorBox(Info.Error); + + if (!*GetGenerateString(DB_GENINST_LNK, shortcut)) + shortcut.Format(L"%s.lnk", AppName); + Info.ShortcutFile = shortcut.Compare(None) ? shortcut.GetString() : NULL; + + // Create the uninstall registration key + LPCWSTR arpkeyname = AppName; + tmp = BuildPath(REGPATH_UNINSTALL, arpkeyname); + HKEY hRoot = Info.PerUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + REGSAM regsam = KEY_READ | KEY_WRITE | (IsSystem64Bit() ? KEY_WOW64_64KEY : KEY_WOW64_32KEY); + Info.Error = arpkey.Create(hRoot, tmp, NULL, REG_OPTION_NON_VOLATILE, regsam); + if (!Info.Error) + { + arpkey.RecurseDeleteKey(GENERATE_ARPSUBKEY); + Info.Error = Info.Entries.Create(arpkey, GENERATE_ARPSUBKEY); + } + if (Info.Error) + ErrorBox(Info.Error); + + if (!Info.Error) + { + BOOL isCab = SplitFileAndDirectory(Archive).Right(4).CompareNoCase(L".cab") == 0; + Info.Error = isCab ? ExtractCab(Archive, tempdir, ExtractCallback, &Info) + : ExtractZip(Archive, tempdir, ExtractCallback, &Info); + } + + if (!Info.Error) + { + // We now know how many files we extracted, change from marquee to normal progress bar. + SendMessage(Info.hDlg, IM_STARTPROGRESS, 0, 0); + + for (int pos = 1; pos > 0 && !Info.Error;) + { + pos = files.Find(L'|'); + CStringW item = pos <= 0 ? files : files.Left(pos); + files = files.Mid(pos + 1); + InstallFiles(tempdir, item, installdir); + } + + if (!Info.MainApp.IsEmpty()) + { + AddUninstallOperationsFromDB(DB_GENINST_DELREG, UNOP_REGKEY); + AddUninstallOperationsFromDB(DB_GENINST_DELREGEMPTY, UNOP_EMPTYREGKEY); + AddUninstallOperationsFromDB(DB_GENINST_DELFILE, UNOP_FILE, L"\\"); + AddUninstallOperationsFromDB(DB_GENINST_DELDIR, UNOP_DIR, L"\\"); + AddUninstallOperationsFromDB(DB_GENINST_DELDIREMPTY, UNOP_EMPTYDIR, L"\\"); + AddEntry(UNOP_EMPTYDIR, L"\\"); + + WriteArpEntry(L"DisplayName", AppName); + WriteArpEntry(L"InstallLocation", Info.InstallDir); // Note: This value is used by the uninstaller! + + LPWSTR p = tmp.GetBuffer(1 + MAX_PATH); + p[0] = L'\"'; + GetModuleFileName(NULL, p + 1, MAX_PATH); + tmp.ReleaseBuffer(); + UINT cch = tmp.GetLength(), bitness = IsSystem64Bit() ? 64 : 32; + WCHAR modechar = Info.PerUser ? 'U' : 'M'; + LPCWSTR unparamsfmt = L"\" /" CMD_KEY_UNINSTALL L" /K%s \"%c%d\\%s\""; + (tmp = tmp.Mid(0, cch)).AppendFormat(unparamsfmt, L"", modechar, bitness, arpkeyname); + WriteArpEntry(L"UninstallString", tmp); + (tmp = tmp.Mid(0, cch)).AppendFormat(unparamsfmt, L" /S", modechar, bitness, arpkeyname); + WriteArpEntry(L"QuietUninstallString", tmp); + + if (GetCustomIconPath(Info, tmp) != S_OK) + tmp = Info.MainApp; + WriteArpEntry(L"DisplayIcon", tmp); + + if (*GetCommonString(DB_VERSION, tmp)) + WriteArpEntry(L"DisplayVersion", tmp); + + SYSTEMTIME st; + GetSystemTime(&st); + tmp.Format(L"%.4u%.2u%.2u", st.wYear, st.wMonth, st.wDay); + WriteArpEntry(L"InstallDate", tmp); + + if (*GetCommonString(DB_PUBLISHER, tmp)) + WriteArpEntry(L"Publisher", tmp); + +#if DBG + tmp.Format(L"sys64=%d rapps%d", IsSystem64Bit(), sizeof(void*) * 8); + WriteArpEntry(L"_DEBUG", tmp); +#endif + } + + if (!Info.Error && Info.ShortcutFile) + { + CreateShortcut(Info.MainApp); + } + } + + DeleteDirectoryTree(tempdir.GetString()); + RemoveDirectory(installdir.GetString()); // This is harmless even if we installed something + return 0; +} + +static DWORD CALLBACK +WorkerThread(LPVOID Parameter) +{ + ((LPTHREAD_START_ROUTINE)Parameter)(NULL); + return SendMessage(g_pInfo->hDlg, IM_END, 0, 0); +} + +static INT_PTR CALLBACK +UIDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HWND hPB = GetDlgItem(hDlg, 1); + switch(uMsg) + { + case IM_STARTPROGRESS: + SetWindowLongPtr(hPB, GWL_STYLE, WS_CHILD | WS_VISIBLE); + SendMessageW(hPB, PBM_SETMARQUEE, FALSE, 0); + SendMessageW(hPB, PBM_SETRANGE32, 0, g_pInfo->Count); + SendMessageW(hPB, PBM_SETPOS, 0, 0); + break; + case IM_PROGRESS: + SendMessageW(hPB, PBM_DELTAPOS, 1, 0); + break; + case IM_END: + DestroyWindow(hDlg); + break; + case WM_INITDIALOG: + { + SendMessageW(hPB, PBM_SETMARQUEE, TRUE, 0); + g_pInfo->hDlg = hDlg; + HICON hIco = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN)); + SendMessageW(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIco); + SendMessageW(hDlg, WM_SETTEXT, 0, (LPARAM)g_pInfo->AppName); + if (!SHCreateThread(WorkerThread, (void*)lParam, CTF_COINIT, NULL)) + { + ErrorBox(); + SendMessageW(hDlg, IM_END, 0, 0); + } + break; + } + case WM_CLOSE: + return TRUE; + case WM_DESTROY: + PostMessage(NULL, WM_QUIT, 0, 0); + break; + } + return FALSE; +} + +static BOOL +CreateUI(BOOL Silent, LPTHREAD_START_ROUTINE ThreadProc) +{ + enum { DLGW = 150, DLGH = 20, PAD = 4, PADx2 = PAD * 2, CHILDCOUNT = 1 }; + const UINT DlgStyle = WS_CAPTION | DS_MODALFRAME | DS_NOFAILCREATE | DS_CENTER; + static const WORD DlgTmpl[] = + { + LOWORD(DlgStyle), HIWORD(DlgStyle), 0, 0, CHILDCOUNT, 0, 0, DLGW, DLGH, 0, 0, 0, + PBS_MARQUEE, HIWORD(WS_CHILD | WS_VISIBLE), 0, 0, PAD, PAD, DLGW - PADx2, DLGH - PADx2, 1, + 'm', 's', 'c', 't', 'l', 's', '_', 'p', 'r', 'o', 'g', 'r', 'e', 's', 's', '3', '2', 0, 0, + }; + HWND hWnd = CreateDialogIndirectParamW(NULL, (LPCDLGTEMPLATE)DlgTmpl, NULL, + UIDlgProc, (LPARAM)ThreadProc); + if (!hWnd) + { + ErrorBox(); + return FALSE; + } + else if (!Silent) + { + ShowWindow(hWnd, SW_SHOW); + } + MSG Msg; + while (GetMessageW(&Msg, NULL, 0, 0)) + { + TranslateMessage(&Msg); + DispatchMessageW(&Msg); + } + return TRUE; +} + +BOOL +ExtractAndRunGeneratedInstaller(const CAvailableApplicationInfo &AppInfo, LPCWSTR Archive) +{ + InstallInfo Info(AppInfo.szDisplayName, *AppInfo.GetConfigParser(), Archive); + g_pInfo = &Info; + return CreateUI(Info.Silent, ExtractAndInstallThread) ? !Info.Error : FALSE; +} + +struct UninstallInfo : CommonInfo +{ + CInstalledApplicationInfo &AppInfo; + UninstallInfo(CInstalledApplicationInfo &Info, BOOL IsSilent) + : CommonInfo(Info.szDisplayName, IsSilent), AppInfo(Info) + { + ArpKey = &Info.GetRegKey(); + } +}; + +enum UninstallStage +{ + US_ITEMS, + US_CONTAINERS, + UINSTALLSTAGECOUNT +}; + +static DWORD CALLBACK +UninstallThread(LPVOID Parameter) +{ + UninstallInfo &Info = *static_cast(g_pInfo); + + CStringW tmp, path, installdir; + path.LoadString(IDS_INSTGEN_CONFIRMUNINST); + tmp.Format(path.GetString(), Info.AppName); + if (!Info.Silent && + MessageBox(Info.GetGuiOwner(), tmp, Info.AppName, MB_YESNO | MB_ICONQUESTION) != IDYES) + { + Info.Error = ERROR_CANCELLED; + SendMessage(Info.hDlg, IM_END, 0, 0); + return 0; + } + + Info.Error = Info.Entries.Open(*Info.ArpKey, GENERATE_ARPSUBKEY, KEY_READ); + if (Info.Error) + return ErrorBox(Info.Error); + + RegQueryInfoKey(Info.Entries, NULL, NULL, NULL, NULL, NULL, NULL, &Info.Count, NULL, NULL, NULL, NULL); + Info.Count *= UINSTALLSTAGECOUNT; + SendMessage(Info.hDlg, IM_STARTPROGRESS, 0, 0); + + if (!GetRegString(*Info.ArpKey, L"InstallLocation", installdir) || installdir.IsEmpty()) + return ErrorBox(ERROR_INVALID_NAME); + + for (UINT stage = 0; stage < UINSTALLSTAGECOUNT; ++stage) + { + for (UINT vi = 0;; ++vi) + { + WCHAR value[MAX_PATH], data[MAX_PATH * 2]; + DWORD valsize = _countof(value), size = sizeof(data) - sizeof(WCHAR), rt; + data[_countof(data) - 1] = UNICODE_NULL; + UINT err = RegEnumValue(Info.Entries, vi, value, &valsize, NULL, &rt, (BYTE*)data, &size); + if (err) + { + if (err != ERROR_NO_MORE_ITEMS) + { + return ErrorBox(err); + } + break; + } + + LPCWSTR str = data; + WORD op = value[0]; + switch(*data ? MAKEWORD(stage, op) : 0) + { + case MAKEWORD(US_ITEMS, UNOP_REGKEY): + case MAKEWORD(US_CONTAINERS, UNOP_EMPTYREGKEY): + { + REGSAM wowsam = 0; + HKEY hKey = NULL; + path.Format(L"%.4s", data); + if (path.CompareNoCase(L"HKCR") == 0) + hKey = HKEY_CLASSES_ROOT; + else if (path.CompareNoCase(L"HKCU") == 0) + hKey = HKEY_CURRENT_USER; + else if (path.CompareNoCase(L"HKLM") == 0) + hKey = HKEY_LOCAL_MACHINE; + + if (data[4] == '6' && data[5] == '4') + wowsam = KEY_WOW64_64KEY; + else if (data[4] == '3' && data[5] == '2') + wowsam = KEY_WOW64_32KEY; + + str = &data[wowsam ? 6 : 4]; + if (!hKey || *str != L'\\') + break; + tmp = SplitFileAndDirectory(++str, &path); + if (!tmp.IsEmpty() && !path.IsEmpty()) + { + CRegKey key; + err = key.Open(hKey, path, DELETE | wowsam); + if (err == ERROR_SUCCESS) + { + if (op == UNOP_REGKEY) + err = key.RecurseDeleteKey(tmp); + else if (RegKeyHasValues(hKey, str, wowsam) == S_FALSE) + err = key.DeleteSubKey(tmp); + } + switch(err) + { + case ERROR_SUCCESS: + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + break; + default: + return ErrorBox(err); + } + } + break; + } + + case MAKEWORD(US_ITEMS, UNOP_FILE): + { + if (*data == L'\\') + str = (path = BuildPath(installdir, data)); + + if (!DeleteFile(str)) + { + err = GetLastError(); + if (err != ERROR_FILE_NOT_FOUND) + { + return ErrorBox(err); + } + } + break; + } + + case MAKEWORD(US_CONTAINERS, UNOP_EMPTYDIR): + case MAKEWORD(US_CONTAINERS, UNOP_DIR): + { + if (*data == L'\\') + str = (path = BuildPath(installdir, data)); + + if (op == UNOP_DIR) + DeleteDirectoryTree(str, Info.GetGuiOwner()); + else + RemoveDirectory(str); + break; + } + } + SendMessage(Info.hDlg, IM_PROGRESS, 0, 0); + } + } + if (!Info.Error) + { + Info.Error = CAppDB::RemoveInstalledAppFromRegistry(&Info.AppInfo); + if (Info.Error) + return ErrorBox(Info.Error); + } + return 0; +} + +BOOL +UninstallGenerated(CInstalledApplicationInfo &AppInfo, UninstallCommandFlags Flags) +{ + UninstallInfo Info(AppInfo, Flags & UCF_SILENT); + g_pInfo = &Info; + return CreateUI(Info.Silent, UninstallThread) ? !Info.Error : FALSE; +} diff --git a/base/applications/rapps/gui.cpp b/base/applications/rapps/gui.cpp index d0b518967df..a1075686314 100644 --- a/base/applications/rapps/gui.cpp +++ b/base/applications/rapps/gui.cpp @@ -275,7 +275,7 @@ CMainWindow::RemoveSelectedAppFromRegistry() return FALSE; if (MessageBoxW(szMsgText, szMsgTitle, MB_YESNO | MB_ICONQUESTION) == IDYES) - return m_Db->RemoveInstalledAppFromRegistry(InstalledApp); + return m_Db->RemoveInstalledAppFromRegistry(InstalledApp) == ERROR_SUCCESS; return FALSE; } @@ -290,7 +290,7 @@ CMainWindow::UninstallSelectedApp(BOOL bModify) if (!InstalledApp) return FALSE; - return InstalledApp->UninstallApplication(bModify); + return InstalledApp->UninstallApplication(bModify ? UCF_MODIFY : UCF_NONE); } VOID diff --git a/base/applications/rapps/include/appdb.h b/base/applications/rapps/include/appdb.h index 1f6bf531a35..8beae3986ec 100644 --- a/base/applications/rapps/include/appdb.h +++ b/base/applications/rapps/include/appdb.h @@ -15,13 +15,20 @@ class CAppDB BOOL EnumerateFiles(); + static CInstalledApplicationInfo * + EnumerateRegistry(CAtlList *List, LPCWSTR Name); + static CInstalledApplicationInfo * + CreateInstalledAppByRegistryKey(LPCWSTR KeyName, HKEY hKeyParent, UINT KeyIndex); + public: CAppDB(const CStringW &path); VOID GetApps(CAtlList &List, AppsCategories Type) const; + CAvailableApplicationInfo * + FindAvailableByPackageName(const CStringW &name); CAppInfo * - FindByPackageName(const CStringW &name); + FindByPackageName(const CStringW &name) { return FindAvailableByPackageName(name); } VOID UpdateAvailable(); @@ -30,9 +37,16 @@ class CAppDB VOID RemoveCached(); - BOOL + static DWORD RemoveInstalledAppFromRegistry(const CAppInfo *Info); + static CInstalledApplicationInfo * + CreateInstalledAppByRegistryKey(LPCWSTR Name); + static CInstalledApplicationInfo * + CreateInstalledAppInstance(LPCWSTR KeyName, BOOL User, REGSAM WowSam); + static HKEY + EnumInstalledRootKey(UINT Index, REGSAM &RegSam); + size_t GetAvailableCount() const { return m_Available.GetCount(); diff --git a/base/applications/rapps/include/appinfo.h b/base/applications/rapps/include/appinfo.h index 639d3fc8182..175048b6008 100644 --- a/base/applications/rapps/include/appinfo.h +++ b/base/applications/rapps/include/appinfo.h @@ -63,7 +63,31 @@ IsInstalledEnum(INT x) return (x >= ENUM_INSTALLED_MIN && x <= ENUM_INSTALLED_MAX); } +enum UninstallCommandFlags +{ + UCF_NONE = 0x00, + UCF_MODIFY = 0x01, + UCF_SILENT = 0x02, +}; + +enum InstallerType +{ + INSTALLER_UNKNOWN, + INSTALLER_GENERATE, // .zip file automatically converted to installer by rapps +}; + +#define DB_VERSION L"Version" +#define DB_CATEGORY L"Category" +#define DB_PUBLISHER L"Publisher" +#define DB_REGNAME L"RegName" +#define DB_INSTALLER L"Installer" +#define DB_SCOPE L"Scope" // User or Machine + +#define DB_GENINSTSECTION L"Generate" +#define GENERATE_ARPSUBKEY L"RApps" // Our uninstall data is stored here + class CAppRichEdit; +class CConfigParser; class CAppInfo { @@ -93,13 +117,15 @@ class CAppInfo GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const = 0; virtual VOID GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload) = 0; + virtual InstallerType + GetInstallerType() const { return INSTALLER_UNKNOWN; } virtual BOOL - UninstallApplication(BOOL bModify) = 0; + UninstallApplication(UninstallCommandFlags Flags) = 0; }; class CAvailableApplicationInfo : public CAppInfo { - class CConfigParser *m_Parser; + CConfigParser *m_Parser; CSimpleArray m_szScrnshotLocation; bool m_ScrnshotRetrieved; CStringW m_szUrlDownload; @@ -125,6 +151,9 @@ class CAvailableApplicationInfo : public CAppInfo const CPathW &BasePath); ~CAvailableApplicationInfo(); + CConfigParser * + GetConfigParser() const { return m_Parser; } + virtual BOOL Valid() const override; virtual BOOL @@ -139,8 +168,10 @@ class CAvailableApplicationInfo : public CAppInfo GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const override; virtual VOID GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload) override; + virtual InstallerType + GetInstallerType() const override; virtual BOOL - UninstallApplication(BOOL bModify) override; + UninstallApplication(UninstallCommandFlags Flags) override; }; class CInstalledApplicationInfo : public CAppInfo @@ -163,10 +194,12 @@ class CInstalledApplicationInfo : public CAppInfo RetrieveUninstallStrings(); public: - const int iKeyIndex; - CInstalledApplicationInfo(HKEY Key, const CStringW &KeyName, AppsCategories Category, int KeyIndex); + UINT m_KeyInfo; + CInstalledApplicationInfo(HKEY Key, const CStringW &KeyName, AppsCategories Category, UINT KeyInfo); ~CInstalledApplicationInfo(); + CRegKey& GetRegKey() { return m_hKey; } + virtual BOOL Valid() const override; virtual BOOL @@ -181,6 +214,13 @@ class CInstalledApplicationInfo : public CAppInfo GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const override; virtual VOID GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload) override; + virtual InstallerType + GetInstallerType() const override; virtual BOOL - UninstallApplication(BOOL bModify) override; + UninstallApplication(UninstallCommandFlags Flags) override; }; + +BOOL +UninstallGenerated(CInstalledApplicationInfo &AppInfo, UninstallCommandFlags Flags); +BOOL +ExtractAndRunGeneratedInstaller(const CAvailableApplicationInfo &AppInfo, LPCWSTR Archive); diff --git a/base/applications/rapps/include/configparser.h b/base/applications/rapps/include/configparser.h index 4573e90f195..792d58f604c 100644 --- a/base/applications/rapps/include/configparser.h +++ b/base/applications/rapps/include/configparser.h @@ -19,4 +19,10 @@ class CConfigParser GetString(const CStringW &KeyName, CStringW &ResultString); BOOL GetInt(const CStringW &KeyName, INT &iResult); + + UINT + GetSectionString(LPCWSTR Section, LPCWSTR Name, CStringW &Result); }; + +HRESULT +ReadIniValue(LPCWSTR File, LPCWSTR Section, LPCWSTR Name, CStringW &Output); diff --git a/base/applications/rapps/include/misc.h b/base/applications/rapps/include/misc.h index 593ef5a82b0..b43585d748a 100644 --- a/base/applications/rapps/include/misc.h +++ b/base/applications/rapps/include/misc.h @@ -16,6 +16,16 @@ #define CurrentArchitecture L"ppc" #endif +static inline UINT +ErrorFromHResult(HRESULT hr) +{ + // Attempt to extract the original Win32 error code from the HRESULT + if (HIWORD(hr) == HIWORD(HRESULT_FROM_WIN32(!0))) + return LOWORD(hr); + else + return hr >= 0 ? ERROR_SUCCESS : hr; +} + VOID CopyTextToClipboard(LPCWSTR lpszText); VOID @@ -34,8 +44,28 @@ WriteLogMessage(WORD wType, DWORD dwEventID, LPCWSTR lpMsg); BOOL GetInstalledVersion(CStringW *pszVersion, const CStringW &szRegName); +typedef struct +{ + const CStringW &ItemPath; + UINT64 UncompressedSize; + UINT FileAttributes; +} EXTRACTCALLBACKINFO; +typedef BOOL (CALLBACK*EXTRACTCALLBACK)(const EXTRACTCALLBACKINFO &Info, void *Cookie); + +static inline BOOL +NotifyFileExtractCallback(const CStringW &ItemPath, UINT64 UncompressedSize, UINT FileAttributes, + EXTRACTCALLBACK Callback, void *Cookie) +{ + EXTRACTCALLBACKINFO eci = { ItemPath, UncompressedSize, FileAttributes }; + return Callback ? Callback(eci, Cookie) : TRUE; +} + BOOL -ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir); +ExtractFilesFromCab(const CStringW &szCabName, const CStringW &szCabDir, const CStringW &szOutputDir, + EXTRACTCALLBACK Callback = NULL, void *Cookie = NULL); +BOOL +ExtractFilesFromCab(LPCWSTR FullCabPath, const CStringW &szOutputDir, + EXTRACTCALLBACK Callback = NULL, void *Cookie = NULL); BOOL IsSystem64Bit(); @@ -49,6 +79,39 @@ UnixTimeToFileTime(DWORD dwUnixTime, LPFILETIME pFileTime); BOOL SearchPatternMatch(LPCWSTR szHaystack, LPCWSTR szNeedle); +HRESULT +RegKeyHasValues(HKEY hKey, LPCWSTR Path, REGSAM wowsam = 0); +LPCWSTR +GetRegString(CRegKey &Key, LPCWSTR Name, CStringW &Value); + +bool +ExpandEnvStrings(CStringW &Str); + +template static CStringW +BuildPath(const T &Base, LPCWSTR Append) +{ + CStringW path = Base; + SIZE_T len = path.GetLength(); + if (len && path[len - 1] != L'\\' && path[len - 1] != L'/') + path += L'\\'; + while (*Append == L'\\' || *Append == L'/') + ++Append; + return path + Append; +} + +CStringW +SplitFileAndDirectory(LPCWSTR FullPath, CStringW *pDir = NULL); +BOOL +DeleteDirectoryTree(LPCWSTR Dir, HWND hwnd = NULL); +UINT +CreateDirectoryTree(LPCWSTR Dir); +HRESULT +GetSpecialPath(UINT csidl, CStringW &Path, HWND hwnd = NULL); +HRESULT +GetKnownPath(REFKNOWNFOLDERID kfid, CStringW &Path, DWORD Flags = KF_FLAG_CREATE); +HRESULT +GetProgramFilesPath(CStringW &Path, BOOL PerUser, HWND hwnd = NULL); + template class CLocalPtr : public CHeapPtr { }; diff --git a/base/applications/rapps/include/resource.h b/base/applications/rapps/include/resource.h index f9fa07a2df3..2d846f8661e 100644 --- a/base/applications/rapps/include/resource.h +++ b/base/applications/rapps/include/resource.h @@ -228,6 +228,9 @@ #define IDS_CMD_PACKAGE_NOT_FOUND 959 #define IDS_CMD_PACKAGE_INFO 960 +/* Generated installer */ +#define IDS_INSTGEN_CONFIRMUNINST 1000 + /* Accelerators */ #define HOTKEYS 715 diff --git a/base/applications/rapps/include/unattended.h b/base/applications/rapps/include/unattended.h index 8eaaf93c244..dd6ea07b595 100644 --- a/base/applications/rapps/include/unattended.h +++ b/base/applications/rapps/include/unattended.h @@ -1,6 +1,8 @@ #pragma once #define CMD_KEY_APPWIZ L"APPWIZ" +#define CMD_KEY_GENINST L"GENINST" +#define CMD_KEY_UNINSTALL L"UNINSTALL" #define CMD_KEY_INSTALL L"INSTALL" #define CMD_KEY_SETUP L"SETUP" #define CMD_KEY_FIND L"FIND" @@ -12,6 +14,7 @@ const WCHAR UsageString[] = L"RAPPS \ [/" CMD_KEY_HELP L"] \ [/" CMD_KEY_INSTALL L" packagename] \ +[/" CMD_KEY_UNINSTALL L" packagename|displayname] \ [/" CMD_KEY_SETUP L" filename] \ [/" CMD_KEY_FIND L" string] \ [/" CMD_KEY_INFO L" packagename]"; diff --git a/base/applications/rapps/lang/bg-BG.rc b/base/applications/rapps/lang/bg-BG.rc index f20960d1b67..aef15975ccc 100644 --- a/base/applications/rapps/lang/bg-BG.rc +++ b/base/applications/rapps/lang/bg-BG.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/cs-CZ.rc b/base/applications/rapps/lang/cs-CZ.rc index 3c5767119d2..b971b23708d 100644 --- a/base/applications/rapps/lang/cs-CZ.rc +++ b/base/applications/rapps/lang/cs-CZ.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/de-DE.rc b/base/applications/rapps/lang/de-DE.rc index d04479e368a..9871571b78e 100644 --- a/base/applications/rapps/lang/de-DE.rc +++ b/base/applications/rapps/lang/de-DE.rc @@ -271,3 +271,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Paket %1 konnte nicht gefunden werden.\n" IDS_CMD_PACKAGE_INFO "Informationen über das Paket %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/en-US.rc b/base/applications/rapps/lang/en-US.rc index 4a096aa8d24..9999a22fd5d 100644 --- a/base/applications/rapps/lang/en-US.rc +++ b/base/applications/rapps/lang/en-US.rc @@ -271,3 +271,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/es-ES.rc b/base/applications/rapps/lang/es-ES.rc index a95ab092791..c03b12c94e6 100644 --- a/base/applications/rapps/lang/es-ES.rc +++ b/base/applications/rapps/lang/es-ES.rc @@ -270,3 +270,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "No se pudo encontrar el paquete %1.\n" IDS_CMD_PACKAGE_INFO "Información del paquete %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/et-EE.rc b/base/applications/rapps/lang/et-EE.rc index 984365fda8d..f32cdba120c 100644 --- a/base/applications/rapps/lang/et-EE.rc +++ b/base/applications/rapps/lang/et-EE.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/fr-FR.rc b/base/applications/rapps/lang/fr-FR.rc index 7fdf4aac47c..e80b47f4823 100644 --- a/base/applications/rapps/lang/fr-FR.rc +++ b/base/applications/rapps/lang/fr-FR.rc @@ -271,3 +271,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Impossible de trouver le paquet %1.\n" IDS_CMD_PACKAGE_INFO "Information sur le paquet %1 :\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/he-IL.rc b/base/applications/rapps/lang/he-IL.rc index 79fcce6bff4..a980e20c36d 100644 --- a/base/applications/rapps/lang/he-IL.rc +++ b/base/applications/rapps/lang/he-IL.rc @@ -273,3 +273,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "החבילה %1 לא נמצאה.\n" IDS_CMD_PACKAGE_INFO "מידע על החבילה %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/hu-HU.rc b/base/applications/rapps/lang/hu-HU.rc index d41af559df7..305d31e6877 100644 --- a/base/applications/rapps/lang/hu-HU.rc +++ b/base/applications/rapps/lang/hu-HU.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Nem sikerült a(z) %1 csomagot megtalálni.\n" IDS_CMD_PACKAGE_INFO "Információ a(z) %1 csomagról:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/id-ID.rc b/base/applications/rapps/lang/id-ID.rc index b199cebb4a3..8142ed8a060 100644 --- a/base/applications/rapps/lang/id-ID.rc +++ b/base/applications/rapps/lang/id-ID.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/it-IT.rc b/base/applications/rapps/lang/it-IT.rc index 0ba9c27524c..46877f8bd12 100644 --- a/base/applications/rapps/lang/it-IT.rc +++ b/base/applications/rapps/lang/it-IT.rc @@ -270,3 +270,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/ja-JP.rc b/base/applications/rapps/lang/ja-JP.rc index c078771012e..51fe5e18440 100644 --- a/base/applications/rapps/lang/ja-JP.rc +++ b/base/applications/rapps/lang/ja-JP.rc @@ -270,3 +270,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "パッケージ %1 を探すのに失敗しました。\n" IDS_CMD_PACKAGE_INFO "パッケージ %1 に関する情報:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/no-NO.rc b/base/applications/rapps/lang/no-NO.rc index ff292224fd4..2fa4b5d3094 100644 --- a/base/applications/rapps/lang/no-NO.rc +++ b/base/applications/rapps/lang/no-NO.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/pl-PL.rc b/base/applications/rapps/lang/pl-PL.rc index cb0fbfd3f51..92968c724b6 100644 --- a/base/applications/rapps/lang/pl-PL.rc +++ b/base/applications/rapps/lang/pl-PL.rc @@ -270,3 +270,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Nie można odnaleźć pakietu %1.\n" IDS_CMD_PACKAGE_INFO "Informacje o pakiecie %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/pt-BR.rc b/base/applications/rapps/lang/pt-BR.rc index d6a3d5e2425..8d978ee9d0a 100644 --- a/base/applications/rapps/lang/pt-BR.rc +++ b/base/applications/rapps/lang/pt-BR.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/pt-PT.rc b/base/applications/rapps/lang/pt-PT.rc index e8f9af18d5d..3cad5e57a59 100644 --- a/base/applications/rapps/lang/pt-PT.rc +++ b/base/applications/rapps/lang/pt-PT.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Falhou a procura do pacote %1.\n" IDS_CMD_PACKAGE_INFO "Informações acerca do pacote %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/ro-RO.rc b/base/applications/rapps/lang/ro-RO.rc index 9ad955cd815..ff1b21ca697 100644 --- a/base/applications/rapps/lang/ro-RO.rc +++ b/base/applications/rapps/lang/ro-RO.rc @@ -270,3 +270,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Nu se poate găsi pachetul %1.\n" IDS_CMD_PACKAGE_INFO "Informații despre pachet %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/ru-RU.rc b/base/applications/rapps/lang/ru-RU.rc index c043e438b20..438c00a3035 100644 --- a/base/applications/rapps/lang/ru-RU.rc +++ b/base/applications/rapps/lang/ru-RU.rc @@ -273,3 +273,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Не удалось найти пакет %1.\n" IDS_CMD_PACKAGE_INFO "Информация о пакете %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/sk-SK.rc b/base/applications/rapps/lang/sk-SK.rc index 1ec3921d9b9..efc5521f6ad 100644 --- a/base/applications/rapps/lang/sk-SK.rc +++ b/base/applications/rapps/lang/sk-SK.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/sq-AL.rc b/base/applications/rapps/lang/sq-AL.rc index 3ed2d62d1c6..adf42542cc5 100644 --- a/base/applications/rapps/lang/sq-AL.rc +++ b/base/applications/rapps/lang/sq-AL.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/sv-SE.rc b/base/applications/rapps/lang/sv-SE.rc index 74b6e635bc1..c1db45e92e6 100644 --- a/base/applications/rapps/lang/sv-SE.rc +++ b/base/applications/rapps/lang/sv-SE.rc @@ -268,3 +268,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/tr-TR.rc b/base/applications/rapps/lang/tr-TR.rc index ee1213cdf73..8a785398775 100644 --- a/base/applications/rapps/lang/tr-TR.rc +++ b/base/applications/rapps/lang/tr-TR.rc @@ -270,3 +270,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "%1 paketi bulunmadı.\n" IDS_CMD_PACKAGE_INFO "%1 paketi hakkındaki bilgi:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/uk-UA.rc b/base/applications/rapps/lang/uk-UA.rc index 4bdca75b294..0d3b91f39a2 100644 --- a/base/applications/rapps/lang/uk-UA.rc +++ b/base/applications/rapps/lang/uk-UA.rc @@ -271,3 +271,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "Failed to find package %1.\n" IDS_CMD_PACKAGE_INFO "Information about package %1:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/zh-CN.rc b/base/applications/rapps/lang/zh-CN.rc index 9df8e29ac8d..04278ff8c22 100644 --- a/base/applications/rapps/lang/zh-CN.rc +++ b/base/applications/rapps/lang/zh-CN.rc @@ -272,3 +272,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "未能找到软件包 %1。\n" IDS_CMD_PACKAGE_INFO "有关软件包 %1 的信息:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/zh-HK.rc b/base/applications/rapps/lang/zh-HK.rc index 684fbc0ed35..22292befa08 100644 --- a/base/applications/rapps/lang/zh-HK.rc +++ b/base/applications/rapps/lang/zh-HK.rc @@ -269,3 +269,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "無法找到軟件套件 %1。\n" IDS_CMD_PACKAGE_INFO "有關軟件套件 %1 的資訊:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/lang/zh-TW.rc b/base/applications/rapps/lang/zh-TW.rc index 4dac28d7193..4144d8f3193 100644 --- a/base/applications/rapps/lang/zh-TW.rc +++ b/base/applications/rapps/lang/zh-TW.rc @@ -269,3 +269,8 @@ BEGIN IDS_CMD_PACKAGE_NOT_FOUND "無法找到軟體套件 %1。\n" IDS_CMD_PACKAGE_INFO "有關軟體套件 %1 的資訊:\n" END + +STRINGTABLE +BEGIN + IDS_INSTGEN_CONFIRMUNINST "Are you sure you want to uninstall %s?" +END diff --git a/base/applications/rapps/loaddlg.cpp b/base/applications/rapps/loaddlg.cpp index 0433463a8f5..f05bfdd5095 100644 --- a/base/applications/rapps/loaddlg.cpp +++ b/base/applications/rapps/loaddlg.cpp @@ -49,6 +49,7 @@ #include "rosui.h" #include "dialogs.h" #include "misc.h" +#include "unattended.h" #ifdef USE_CERT_PINNING #define CERT_ISSUER_INFO_OLD "US\r\nLet's Encrypt\r\nLet's Encrypt Authority X3" @@ -107,19 +108,26 @@ UrlUnescapeAndMakeFileNameValid(CStringW& str) struct DownloadInfo { - DownloadInfo() + DownloadInfo() : DLType(DLTYPE_APPLICATION), IType(INSTALLER_UNKNOWN), SizeInBytes(0) { } DownloadInfo(const CAppInfo &AppInfo) : DLType(DLTYPE_APPLICATION) { AppInfo.GetDownloadInfo(szUrl, szSHA1, SizeInBytes); szName = AppInfo.szDisplayName; + IType = AppInfo.GetInstallerType(); + if (IType == INSTALLER_GENERATE) + { + szPackageName = AppInfo.szIdentifier; + } } DownloadType DLType; + InstallerType IType; CStringW szUrl; CStringW szName; CStringW szSHA1; + CStringW szPackageName; ULONG SizeInBytes; }; @@ -400,12 +408,12 @@ CertGetSubjectAndIssuer(HINTERNET hFile, CLocalPtr &subjectInfo, CLocalPtr #endif inline VOID -MessageBox_LoadString(HWND hMainWnd, INT StringID) +MessageBox_LoadString(HWND hOwnerWnd, INT StringID) { CStringW szMsgText; if (szMsgText.LoadStringW(StringID)) { - MessageBoxW(hMainWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR); + MessageBoxW(hOwnerWnd, szMsgText.GetString(), NULL, MB_OK | MB_ICONERROR); } } @@ -1011,6 +1019,7 @@ CDownloadManager::ThreadFunc(LPVOID param) // run it if (InfoArray[iAppId].DLType == DLTYPE_APPLICATION) { + CStringW app, params; SHELLEXECUTEINFOW shExInfo = {0}; shExInfo.cbSize = sizeof(shExInfo); shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS; @@ -1019,6 +1028,17 @@ CDownloadManager::ThreadFunc(LPVOID param) shExInfo.lpParameters = L""; shExInfo.nShow = SW_SHOW; + if (InfoArray[iAppId].IType == INSTALLER_GENERATE) + { + params = L"/" + CStringW(CMD_KEY_GENINST) + L" \"" + + InfoArray[iAppId].szPackageName + L"\" \"" + + CStringW(shExInfo.lpFile) + L"\""; + shExInfo.lpParameters = params; + shExInfo.lpFile = app.GetBuffer(MAX_PATH); + GetModuleFileNameW(NULL, const_cast(shExInfo.lpFile), MAX_PATH); + app.ReleaseBuffer(); + } + /* FIXME: Do we want to log installer status? */ WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_INSTALL, InfoArray[iAppId].szName); diff --git a/base/applications/rapps/misc.cpp b/base/applications/rapps/misc.cpp index bf3b1ddc6b5..f2b15d5c645 100644 --- a/base/applications/rapps/misc.cpp +++ b/base/applications/rapps/misc.cpp @@ -12,9 +12,6 @@ static HANDLE hLog = NULL; -static BOOL bIsSys64ResultCached = FALSE; -static BOOL bIsSys64Result = FALSE; - VOID CopyTextToClipboard(LPCWSTR lpszText) { @@ -289,32 +286,14 @@ GetInstalledVersion(CStringW *pszVersion, const CStringW &szRegName) BOOL IsSystem64Bit() { - if (bIsSys64ResultCached) - { - // just return cached result - return bIsSys64Result; - } - - SYSTEM_INFO si; - typedef void(WINAPI * LPFN_PGNSI)(LPSYSTEM_INFO); - LPFN_PGNSI pGetNativeSystemInfo = - (LPFN_PGNSI)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "GetNativeSystemInfo"); - if (pGetNativeSystemInfo) - { - pGetNativeSystemInfo(&si); - if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || - si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) - { - bIsSys64Result = TRUE; - } - } - else - { - bIsSys64Result = FALSE; - } - - bIsSys64ResultCached = TRUE; // next time calling this function, it will directly return bIsSys64Result - return bIsSys64Result; +#ifdef _WIN64 + return TRUE; +#else + static UINT cache = 0; + if (!cache) + cache = 1 + (IsOS(OS_WOW6432) != FALSE); + return cache - 1; +#endif } INT @@ -368,6 +347,64 @@ UnixTimeToFileTime(DWORD dwUnixTime, LPFILETIME pFileTime) pFileTime->dwHighDateTime = ll >> 32; } +HRESULT +RegKeyHasValues(HKEY hKey, LPCWSTR Path, REGSAM wowsam) +{ + CRegKey key; + LONG err = key.Open(hKey, Path, KEY_QUERY_VALUE | wowsam); + if (err == ERROR_SUCCESS) + { + WCHAR name[1]; + DWORD cchname = _countof(name), cbsize = 0; + err = RegEnumValueW(key, 0, name, &cchname, NULL, NULL, NULL, &cbsize); + if (err == ERROR_NO_MORE_ITEMS) + return S_FALSE; + if (err == ERROR_MORE_DATA) + err = ERROR_SUCCESS; + } + return HRESULT_FROM_WIN32(err); +} + +LPCWSTR +GetRegString(CRegKey &Key, LPCWSTR Name, CStringW &Value) +{ + for (;;) + { + ULONG cb = 0, cch; + ULONG err = Key.QueryValue(Name, NULL, NULL, &cb); + if (err) + break; + cch = cb / sizeof(WCHAR); + LPWSTR p = Value.GetBuffer(cch + 1); + p[cch] = UNICODE_NULL; + err = Key.QueryValue(Name, NULL, (BYTE*)p, &cb); + if (err == ERROR_MORE_DATA) + continue; + if (err) + break; + Value.ReleaseBuffer(); + return Value.GetString(); + } + return NULL; +} + +bool +ExpandEnvStrings(CStringW &Str) +{ + CStringW buf; + DWORD cch = ExpandEnvironmentStringsW(Str, NULL, 0); + if (cch) + { + if (ExpandEnvironmentStringsW(Str, buf.GetBuffer(cch), cch) == cch) + { + buf.ReleaseBuffer(cch - 1); + Str = buf; + return true; + } + } + return false; +} + BOOL SearchPatternMatch(LPCWSTR szHaystack, LPCWSTR szNeedle) { @@ -376,3 +413,78 @@ SearchPatternMatch(LPCWSTR szHaystack, LPCWSTR szNeedle) /* TODO: Improve pattern search beyond a simple case-insensitive substring search. */ return StrStrIW(szHaystack, szNeedle) != NULL; } + +BOOL +DeleteDirectoryTree(LPCWSTR Dir, HWND hwnd) +{ + CStringW from(Dir); + UINT cch = from.GetLength(); + from.Append(L"00"); + LPWSTR p = from.GetBuffer(); + p[cch] = p[cch + 1] = L'\0'; // Double null-terminate + UINT fof = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; + SHFILEOPSTRUCT shfos = { hwnd, FO_DELETE, p, NULL, (FILEOP_FLAGS)fof }; + return SHFileOperationW(&shfos); +} + +UINT +CreateDirectoryTree(LPCWSTR Dir) +{ + UINT err = SHCreateDirectory(NULL, Dir); + return err == ERROR_ALREADY_EXISTS ? 0 : err; +} + +CStringW +SplitFileAndDirectory(LPCWSTR FullPath, CStringW *pDir) +{ + CPathW dir = FullPath; + //int win = dir.ReverseFind(L'\\'), nix = dir.ReverseFind(L'/'), sep = max(win, nix); + int sep = dir.FindFileName(); + CStringW file = dir.m_strPath.Mid(sep); + if (pDir) + *pDir = sep == -1 ? L"" : dir.m_strPath.Left(sep - 1); + return file; +} + +HRESULT +GetSpecialPath(UINT csidl, CStringW &Path, HWND hwnd) +{ + if (!SHGetSpecialFolderPathW(hwnd, Path.GetBuffer(MAX_PATH), csidl, TRUE)) + return E_FAIL; + Path.ReleaseBuffer(); + return S_OK; +} + +HRESULT +GetKnownPath(REFKNOWNFOLDERID kfid, CStringW &Path, DWORD Flags) +{ + PWSTR p; + FARPROC f = GetProcAddress(LoadLibraryW(L"SHELL32"), "SHGetKnownFolderPath"); + if (!f) + return HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION); + HRESULT hr = ((HRESULT(WINAPI*)(REFKNOWNFOLDERID,UINT,HANDLE,PWSTR*))f)(kfid, Flags, NULL, &p); + if (FAILED(hr)) + return hr; + Path = p; + CoTaskMemFree(p); + return hr; +} + +HRESULT +GetProgramFilesPath(CStringW &Path, BOOL PerUser, HWND hwnd) +{ + if (!PerUser) + return GetSpecialPath(CSIDL_PROGRAM_FILES, Path, hwnd); + + HRESULT hr = GetKnownPath(FOLDERID_UserProgramFiles, Path); + if (FAILED(hr)) + { + hr = GetSpecialPath(CSIDL_LOCAL_APPDATA, Path, hwnd); + // Use the correct path on NT6 (on NT5 the path becomes a bit long) + if (SUCCEEDED(hr) && LOBYTE(GetVersion()) >= 6) + { + Path = BuildPath(Path, L"Programs"); // Should not be localized + } + } + return hr; +} diff --git a/base/applications/rapps/unattended.cpp b/base/applications/rapps/unattended.cpp index ab6aaec676e..61224f58a31 100644 --- a/base/applications/rapps/unattended.cpp +++ b/base/applications/rapps/unattended.cpp @@ -8,6 +8,7 @@ #include "gui.h" #include "unattended.h" +#include "configparser.h" #include #include @@ -42,6 +43,22 @@ InitRappsConsole() ConInitStdStreams(); // Initialize the Console Standard Streams } +static CAppInfo * +SearchForAppWithDisplayName(CAppDB &db, AppsCategories Type, LPCWSTR Name) +{ + CAtlList List; + db.GetApps(List, Type); + for (POSITION it = List.GetHeadPosition(); it;) + { + CAppInfo *Info = List.GetNext(it); + if (SearchPatternMatch(Info->szDisplayName, Name)) + { + return Info; + } + } + return NULL; +} + static BOOL HandleInstallCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft) { @@ -104,6 +121,95 @@ HandleSetupCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft) return DownloadListOfApplications(Applications, TRUE); } +static BOOL +HandleUninstallCommand(CAppDB &db, UINT argcLeft, LPWSTR *argvLeft) +{ + UINT argi = 0, silent = FALSE, byregkeyname = FALSE; + for (; argcLeft; ++argi, --argcLeft) + { + if (!StrCmpIW(argvLeft[argi], L"/S")) + ++silent; + else if (!StrCmpIW(argvLeft[argi], L"/K")) + ++byregkeyname; + else + break; + } + if (argcLeft != 1) + return FALSE; + + CStringW buf; + LPCWSTR name = argvLeft[argi]; + BOOL retval = FALSE; + CAppInfo *pInfo = NULL, *pDelete = NULL; + + if (!byregkeyname) + { + for (UINT i = ENUM_INSTALLED_MIN; !pInfo && i <= ENUM_INSTALLED_MAX; ++i) + { + pInfo = SearchForAppWithDisplayName(db, AppsCategories(i), name); + } + + if (!pInfo) + { + CAvailableApplicationInfo *p = db.FindAvailableByPackageName(name); + if (p) + { + CConfigParser *cp = p->GetConfigParser(); + if (cp && cp->GetString(DB_REGNAME, buf) && !buf.IsEmpty()) + { + name = buf.GetString(); + byregkeyname = TRUE; + } + } + } + } + + if (byregkeyname) + { + // Force a specific key type if requested ([32|64]<\\KeyName>) + if (name[0]) + { + REGSAM wow = 0; + UINT i = 1; + if (name[i] == '3' && name[i + 1]) + wow = KEY_WOW64_32KEY, i += 2; + else if (name[i] == '6' && name[i + 1]) + wow = KEY_WOW64_64KEY, i += 2; + + if (name[i++] == '\\') + { + pInfo = CAppDB::CreateInstalledAppInstance(name + i, name[0] == 'U', wow); + } + } + + if (!pInfo) + { + pInfo = CAppDB::CreateInstalledAppByRegistryKey(name); + } + pDelete = pInfo; + } + + if (pInfo) + { + retval = pInfo->UninstallApplication(silent ? UCF_SILENT : UCF_NONE); + } + delete pDelete; + return retval; +} + +static BOOL +HandleGenerateInstallerCommand(CAppDB &db, UINT argcLeft, LPWSTR *argvLeft) +{ + if (argcLeft != 2) + return FALSE; + + CAvailableApplicationInfo *pAI = db.FindAvailableByPackageName(argvLeft[0]); + if (!pAI) + return FALSE; + + return ExtractAndRunGeneratedInstaller(*pAI, argvLeft[1]); +} + static BOOL HandleFindCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft) { @@ -276,6 +382,14 @@ ParseCmdAndExecute(LPWSTR lpCmdLine, BOOL bIsFirstLaunch, int nCmdShow) { return HandleSetupCommand(&db, argv[1], argc - 2, argv + 2); } + else if (MatchCmdOption(argv[1], CMD_KEY_UNINSTALL)) + { + return HandleUninstallCommand(db, argc - 2, argv + 2); + } + else if (MatchCmdOption(argv[1], CMD_KEY_GENINST)) + { + return HandleGenerateInstallerCommand(db, argc - 2, argv + 2); + } InitRappsConsole(); diff --git a/base/applications/rapps/winmain.cpp b/base/applications/rapps/winmain.cpp index 3fb8e23d4b1..be2294c7504 100644 --- a/base/applications/rapps/winmain.cpp +++ b/base/applications/rapps/winmain.cpp @@ -49,6 +49,7 @@ wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nSh InitLogs(); InitCommonControls(); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); // Give UI higher priority than background threads // parse cmd-line and perform the corresponding operation BOOL bSuccess = ParseCmdAndExecute(GetCommandLineW(), bIsFirstLaunch, SW_SHOWNORMAL);