From abf3ee09ed3b295f6abd1d2a27794b9120b2ced8 Mon Sep 17 00:00:00 2001 From: Birunthan Mohanathas Date: Sun, 3 Nov 2013 16:20:12 +0200 Subject: [PATCH] New installer: Implement basic install and elevation handling --- Installer/Application.cpp | 63 ++++++------ Installer/DialogInstall.cpp | 131 +++++++++++++++++++++++-- Installer/DialogInstall.h | 11 ++- Installer/Install.cpp | 147 ++++++++++++++++++++++++++++ Installer/Install.h | 45 +++++++++ Installer/Installer.rc | 5 +- Installer/Installer.vcxproj | 14 ++- Installer/Installer.vcxproj.filters | 36 +++---- Installer/Resource.h | 4 + Installer/Util.cpp | 2 +- 10 files changed, 390 insertions(+), 68 deletions(-) create mode 100644 Installer/Install.cpp create mode 100644 Installer/Install.h diff --git a/Installer/Application.cpp b/Installer/Application.cpp index 380ef49b..1c696aca 100644 --- a/Installer/Application.cpp +++ b/Installer/Application.cpp @@ -17,52 +17,53 @@ */ #include "StdAfx.h" -#include "DialogInstall.h" -#include "Resource.h" #include "Application.h" +#include "DialogInstall.h" +#include "Install.h" +#include "Resource.h" bool IsSupportedPlatform(); bool IsSupportedCPU(); -int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPWSTR cmdLine, int) { - int argCount = 0; - LPWSTR* args = CommandLineToArgvW(GetCommandLine(), &argCount); - if (args) - { - for (int i = 1; i < argCount; ++i) - { - WCHAR* name = &args[i][(args[i][0] == L'/') ? 1 : 0]; - WCHAR* value = wcschr(name, L':'); - if (value) - { - *value = L'\0'; - ++value; - } - - if (wcscmp(name, L"Silent") == 0) - { - } - } - - if (argCount >= 2 && wcscmp(args[1], L"/ElevatedInstall") == 0) - { - } - - LocalFree(args); - } - + CoInitialize(nullptr); InitCommonControls(); + if (*cmdLine) + { + InstallOptions options; + const int scans = swscanf( + cmdLine, L"OPT:%259[^|]|%ld|%hd|%hd|%d", + options.targetPath, + &options.language, + &options.type, + &options.arch, + &options.launchOnLogin); + if (scans == 5) + { + DoInstall(options); + return 0; + } + + return 1; + } + if (!IsSupportedPlatform()) { - MessageBox(nullptr, L"Windows XP SP2 or higher is required to install Rainmeter.", nullptr, MB_OK | MB_ICONERROR); + MessageBox( + nullptr, + L"Windows XP SP2 or higher is required to install Rainmeter.", + L"Rainmeter Setup", MB_OK | MB_ICONERROR); return (int)InstallStatus::UnsupportedPlatform; } if (!IsSupportedCPU()) { - MessageBox(nullptr, L"A Pentium III or later processor is required to install Rainmeter.", nullptr, MB_OK | MB_ICONERROR); + MessageBox( + nullptr, + L"A Pentium III or later processor is required to install Rainmeter.", + L"Rainmeter Setup", MB_OK | MB_ICONERROR); return (int)InstallStatus::UnsupportedPlatform; } diff --git a/Installer/DialogInstall.cpp b/Installer/DialogInstall.cpp index 5247b7eb..f79bcc27 100644 --- a/Installer/DialogInstall.cpp +++ b/Installer/DialogInstall.cpp @@ -18,8 +18,11 @@ #include "StdAfx.h" #include "DialogInstall.h" +#include "Install.h" #include "Resource.h" +#include "Util.h" #include "../Common/ControlTemplate.h" +#include "../Common/Platform.h" CDialogInstall* CDialogInstall::c_Dialog = nullptr; @@ -74,7 +77,9 @@ WCHAR* GetString(UINT id) return L""; } -CDialogInstall::CDialogInstall() : Dialog() +CDialogInstall::CDialogInstall() : Dialog(), + m_InstallProcess(), + m_InstallProcessWaitThread() { } @@ -87,7 +92,7 @@ CDialogInstall* CDialogInstall::Create() c_Dialog = new CDialogInstall(); c_Dialog->ShowDialogWindow( - L"Installer", + L"Rainmeter Setup", 0, 0, 350, 210, DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU, WS_EX_APPWINDOW | WS_EX_CONTROLPARENT, @@ -128,7 +133,7 @@ INT_PTR CDialogInstall::OnInitDialog(WPARAM wParam, LPARAM lParam) static const ControlTemplate::Control s_Controls[] = { CT_ICON(Id_HeaderIcon, 0, - 8, 10, 24, 24, + 10, 10, 24, 24, WS_VISIBLE, 0), CT_LABEL(Id_HeaderTitleLabel, 2, @@ -139,7 +144,6 @@ INT_PTR CDialogInstall::OnInitDialog(WPARAM wParam, LPARAM lParam) 40, 20, 250, 9, WS_VISIBLE | SS_ENDELLIPSIS | SS_NOPREFIX, 0), - CT_BUTTON(Id_InstallButton, 1, 199, 191, 70, 14, WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 0), @@ -149,7 +153,7 @@ INT_PTR CDialogInstall::OnInitDialog(WPARAM wParam, LPARAM lParam) WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 0), CT_TAB(Id_Tab, 0, - -2, 38, 400, 148, + -2, 36, 400, 150, WS_VISIBLE | WS_TABSTOP | TCS_FIXEDWIDTH, 0) // Last for correct tab order. }; @@ -168,7 +172,11 @@ INT_PTR CDialogInstall::OnInitDialog(WPARAM wParam, LPARAM lParam) item = GetControl(Id_InstallButton); SendMessage(m_Window, WM_NEXTDLGCTL, (WPARAM)item, TRUE); - + if (Platform::IsAtLeastWinVista() && !Util::IsProcessUserAdmin()) + { + Button_SetElevationRequiredState(item, TRUE); + } + return TRUE; } @@ -180,6 +188,10 @@ INT_PTR CDialogInstall::OnCommand(WPARAM wParam, LPARAM lParam) PostMessage(m_Window, WM_CLOSE, 0, 0); break; + case Id_InstallButton: + LaunchInstallProcess(); + break; + default: return FALSE; } @@ -192,6 +204,84 @@ INT_PTR CDialogInstall::OnNotify(WPARAM wParam, LPARAM lParam) return 0; } +void CDialogInstall::LaunchInstallProcess() +{ + const bool isProcsesUserAdmin = Util::IsProcessUserAdmin(); + if (!isProcsesUserAdmin && (Platform::IsAtLeastWinVista() && !Util::CanProcessUserElevate())) + { + MessageBox( + m_Window, + L"Adminstrative privileges are required to install Rainmeter.\n\nClick OK to close setup.", + L"Rainmeter Setup", MB_OK | MB_ICONERROR); + PostMessage(m_Window, WM_CLOSE, 0, 0); + return; + } + + m_InstallProcessWaitThread = CreateThread( + nullptr, 0, ElevatedProcessWaitThreadProc, nullptr, CREATE_SUSPENDED, nullptr); + if (!m_InstallProcessWaitThread) + { + // TODO. + } + + WCHAR exePath[MAX_PATH]; + GetModuleFileName(nullptr, exePath, _countof(exePath)); + + HWND item = m_TabContents.GetControl(TabContents::Id_LanguageComboBox); + const LCID lcid = (LCID)ComboBox_GetItemData(item, ComboBox_GetCurSel(item)); + + item = m_TabContents.GetControl(TabContents::Id_InstallationTypeComboBox); + const LPARAM typeData = ComboBox_GetItemData(item, ComboBox_GetCurSel(item)); + + WCHAR targetPath[MAX_PATH]; + item = m_TabContents.GetControl(TabContents::Id_DestinationEdit); + Edit_GetText(item, targetPath, _countof(targetPath)); + + item = m_TabContents.GetControl(TabContents::Id_LaunchOnLoginCheckBox); + const int launchOnLogin = Button_GetCheck(item) == BST_CHECKED ? 1 : 0; + + WCHAR params[512]; + wsprintf( + params, L"OPT:%s|%ld|%hd|%hd|%d", + targetPath, lcid, LOWORD(typeData), HIWORD(typeData), launchOnLogin); + + // Launch the installer process and, if needed, request elevation. + SHELLEXECUTEINFO sei = {sizeof(sei)}; + sei.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS; + sei.lpVerb = isProcsesUserAdmin ? L"open" : L"runas"; + sei.lpFile = exePath; + sei.lpParameters = params; + sei.hwnd = m_Window; + sei.nShow = SW_NORMAL; + + if (!ShellExecuteEx(&sei) || !sei.hProcess) + { + MessageBox(m_Window, + L"Adminstrative privileges are required to install Rainmeter.\n\nClick OK to close setup.", + L"Rainmeter Setup", MB_OK | MB_ICONERROR); + PostMessage(m_Window, WM_CLOSE, 0, 0); + return; + } + + m_InstallProcess = sei.hProcess; + ResumeThread(m_InstallProcessWaitThread); +} + +DWORD WINAPI CDialogInstall::ElevatedProcessWaitThreadProc(void* param) +{ + WaitForSingleObject(c_Dialog->m_InstallProcess, INFINITE); + + CloseHandle(c_Dialog->m_InstallProcess); + c_Dialog->m_InstallProcess = nullptr; + + CloseHandle(c_Dialog->m_InstallProcessWaitThread); + c_Dialog->m_InstallProcessWaitThread = nullptr; + + PostMessage(c_Dialog->m_Window, WM_CLOSE, 0, 0); + + return 0; +} + /* ** Constructor. ** @@ -247,9 +337,13 @@ void CDialogInstall::TabContents::Create(HWND owner) item = GetControl(Id_InstallationTypeComboBox); ComboBox_AddString(item, L"Standard 64-bit installation (reccomended)"); + ComboBox_SetItemData(item, 0, MAKELPARAM(InstallType::Standard, InstallArch::X64)); ComboBox_AddString(item, L"Standard 32-bit installation"); + ComboBox_SetItemData(item, 1, MAKELPARAM(InstallType::Standard, InstallArch::X32)); ComboBox_AddString(item, L"Portable 64-bit installation"); + ComboBox_SetItemData(item, 2, MAKELPARAM(InstallType::Portable, InstallArch::X64)); ComboBox_AddString(item, L"Portable 32-bit installation"); + ComboBox_SetItemData(item, 3, MAKELPARAM(InstallType::Portable, InstallArch::X32)); ComboBox_SetCurSel(item, 0); } @@ -270,5 +364,28 @@ INT_PTR CDialogInstall::TabContents::HandleMessage(UINT uMsg, WPARAM wParam, LPA INT_PTR CDialogInstall::TabContents::OnCommand(WPARAM wParam, LPARAM lParam) { - return 0; + switch (LOWORD(wParam)) + { + case Id_DestinationBrowseButton: + { + WCHAR buffer[MAX_PATH]; + BROWSEINFO bi = {0}; + bi.hwndOwner = c_Dialog->GetWindow(); + bi.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; + + PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi); + if (pidl && SHGetPathFromIDList(pidl, buffer)) + { + HWND item = GetControl(Id_DestinationEdit); + Static_SetText(item, buffer); + CoTaskMemFree(pidl); + } + } + break; + + default: + return FALSE; + } + + return TRUE; } diff --git a/Installer/DialogInstall.h b/Installer/DialogInstall.h index 6d8bfbbc..88584030 100644 --- a/Installer/DialogInstall.h +++ b/Installer/DialogInstall.h @@ -65,8 +65,6 @@ protected: INT_PTR OnCommand(WPARAM wParam, LPARAM lParam); private: - TabContents m_TabContents; - enum Id { Id_CancelButton = IDCANCEL, @@ -76,6 +74,15 @@ private: Id_InstallButton, Id_Tab }; + + TabContents m_TabContents; + + HANDLE m_InstallProcess; + HANDLE m_InstallProcessWaitThread; + + void LaunchInstallProcess(); + + static DWORD WINAPI ElevatedProcessWaitThreadProc(void* param); }; #endif diff --git a/Installer/Install.cpp b/Installer/Install.cpp new file mode 100644 index 00000000..64d48d8c --- /dev/null +++ b/Installer/Install.cpp @@ -0,0 +1,147 @@ +/* + Copyright (C) 2013 Birunthan Mohanathas + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "StdAfx.h" +#include "Install.h" +#include "Resource.h" + +extern "C" { +#include "lzma/Alloc.h" +#include "lzma/7zFile.h" +#include "lzma/7zVersion.h" +#include "lzma/LzmaDec.h" +#include "lzma/7zCrc.h" +#include "lzma/7z.h" +#include "lzma/7zMemInStream.h" +#include "lzma/7zAlloc.h" +} // extern "C" + +void ExtractPayload(const void* payload, size_t payloadSize, const WCHAR* prefix) +{ + CMemInStream memStream; + MemInStream_Init(&memStream, payload, payloadSize); + + CrcGenerateTable(); + + ISzAlloc alloc = { SzAlloc, SzFree }; + CSzArEx db; + SzArEx_Init(&db); + SRes res = SzArEx_Open(&db, &memStream.s, &alloc, &alloc); + if (res != SZ_OK) + { + SzArEx_Free(&db, &alloc); + return; + } + + WCHAR buffer[MAX_PATH]; + UInt32 blockIndex = 0xFFFFFFFF; + Byte* outBuffer = 0; + size_t outBufferSize = 0; + + for (UInt32 i = 0; i < db.db.NumFiles; i++) + { + size_t offset = 0; + size_t outSizeProcessed = 0; + const CSzFileItem* f = db.db.Files + i; + + SzArEx_GetFileNameUtf16(&db, i, (UInt16*)buffer); + WCHAR* destPath = buffer; + if (wcsncmp(destPath, prefix, 3) == 0 || + wcsncmp(destPath, L"ALL", 3) == 0) + { + // Skip the prefix (X64/X32/ALL) and the path separater. + destPath += 4; + } + else + { + // This file isn't for this arch. + continue; + } + + if (!f->IsDir) + { + res = SzArEx_Extract( + &db, &memStream.s, i, &blockIndex, &outBuffer, &outBufferSize, &offset, + &outSizeProcessed, &alloc, &alloc); + if (res != SZ_OK) + { + break; + } + } + + for (size_t j = 0; destPath[j] != L'\0'; ++j) + { + if (destPath[j] == L'/') + { + destPath[j] = L'\0'; + CreateDirectory(destPath, NULL); + destPath[j] = CHAR_PATH_SEPARATOR; + } + } + + if (f->IsDir) + { + CreateDirectory(destPath, NULL); + continue; + } + + CSzFile outFile; + if (OutFile_OpenW(&outFile, destPath)) + { + res = SZ_ERROR_FAIL; + break; + } + size_t processedSize = outSizeProcessed; + if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || + processedSize != outSizeProcessed) + { + res = SZ_ERROR_FAIL; + break; + } + if (File_Close(&outFile)) + { + res = SZ_ERROR_FAIL; + break; + } + + if (f->AttribDefined) SetFileAttributesW(destPath, f->Attrib); + } + + IAlloc_Free(&alloc, outBuffer); + SzArEx_Free(&db, &alloc); + + if (res == SZ_OK) + { + // Success. + } +} + +void DoInstall(InstallOptions& options) +{ +#ifdef INSTALLER_INCLUDE_PAYLOAD + const auto module = GetModuleHandle(nullptr); + HRSRC payload = FindResource( + module, MAKEINTRESOURCE(IDR_PAYLOAD), MAKEINTRESOURCE(PAYLOAD_RESOURCE_TYPEID)); + const size_t payloadSize = SizeofResource(module, payload); + const void* payloadData = LockResource(LoadResource(module, payload)); + + SetCurrentDirectory(options.targetPath); + ExtractPayload( + payloadData, payloadSize, options.arch == InstallArch::X32 ? L"X32" : L"X64"); +#endif +} diff --git a/Installer/Install.h b/Installer/Install.h new file mode 100644 index 00000000..47861d63 --- /dev/null +++ b/Installer/Install.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2013 Birunthan Mohanathas + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef RM_INSTALLER_INSTALL_H_ +#define RM_INSTALLER_INSTALL_H_ + +enum InstallType : char +{ + Standard, + Portable +}; + +enum InstallArch : char +{ + X32, + X64 +}; + +struct InstallOptions +{ + WCHAR targetPath[MAX_PATH]; + InstallType type; + InstallArch arch; + LCID language; + BOOL launchOnLogin; +}; + +void DoInstall(InstallOptions& options); + +#endif \ No newline at end of file diff --git a/Installer/Installer.rc b/Installer/Installer.rc index 0ddbc1df..3a0d1b61 100644 --- a/Installer/Installer.rc +++ b/Installer/Installer.rc @@ -47,5 +47,8 @@ VS_VERSION_INFO VERSIONINFO // Icon // -IDI_APPICON ICON "Installer.ico" +IDI_APPICON ICON "Installer.ico" +#ifdef INSTALLER_INCLUDE_PAYLOAD +IDR_PAYLOAD PAYLOAD_RESOURCE_TYPEID payload.7z +#endif diff --git a/Installer/Installer.vcxproj b/Installer/Installer.vcxproj index a69a3212..223c938d 100644 --- a/Installer/Installer.vcxproj +++ b/Installer/Installer.vcxproj @@ -18,7 +18,7 @@ Windows Imagehlp.lib;Wininet.lib;Comctl32.lib;Version.lib;UxTheme.lib;shlwapi.lib;%(AdditionalDependencies) - $(WinDDK71Dir)\lib\Crt\i386\msvcrt.lib;%(AdditionalDependencies) + D:\Biru\Projects\WinDDK\7600.16385.1\lib\Crt\i386\msvcrt.lib;%(AdditionalDependencies) true @@ -29,10 +29,9 @@ - - + @@ -52,10 +51,9 @@ - - + @@ -69,12 +67,18 @@ + + + + {19312085-aa51-4bd6-be92-4b6098cca539} + + diff --git a/Installer/Installer.vcxproj.filters b/Installer/Installer.vcxproj.filters index 26409b41..cb86fd7f 100644 --- a/Installer/Installer.vcxproj.filters +++ b/Installer/Installer.vcxproj.filters @@ -16,14 +16,14 @@ {9588f54f-9188-40b7-b750-260f5f514ee5} - - {f19567fb-ab8f-4193-a3b3-826685cf8db3} - Source Files + + Source Files + Source Files @@ -78,14 +78,20 @@ Source Files - - Common - - - Common - + + Header Files + + + Header Files + + + Header Files + + + Header Files + Header Files @@ -131,18 +137,6 @@ lzma - - Header Files - - - Common - - - Common - - - Header Files - diff --git a/Installer/Resource.h b/Installer/Resource.h index b8a7d1ad..41578d03 100644 --- a/Installer/Resource.h +++ b/Installer/Resource.h @@ -1,2 +1,6 @@ #define IDI_APPICON 101 +#define IDR_PAYLOAD 102 + +#define PAYLOAD_RESOURCE_TYPEID 1000 + #define IDC_STATIC -1 diff --git a/Installer/Util.cpp b/Installer/Util.cpp index d8254526..d6beb441 100644 --- a/Installer/Util.cpp +++ b/Installer/Util.cpp @@ -99,7 +99,7 @@ bool CanProcessUserElevate() // Try checking registry. const WCHAR* subKey = L"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; DWORD data; - if (GetRegistryDword(HKEY_LOCAL_MACHINE, subKey, L"EnableLUA", &data) == ERROR_SUCCESS && + if (GetRegistryDword(HKEY_LOCAL_MACHINE, subKey, L"EnableLUA", &data) && data != 0) { return true;