diff --git a/res/msi/CustomActions/CustomActions.cpp b/res/msi/CustomActions/CustomActions.cpp index f836edb3b..85ea27fe8 100644 --- a/res/msi/CustomActions/CustomActions.cpp +++ b/res/msi/CustomActions/CustomActions.cpp @@ -1,11 +1,13 @@ // CustomAction.cpp : Defines the entry point for the custom action. #include "pch.h" + #include #include +#include +#include UINT __stdcall CustomActionHello( - __in MSIHANDLE hInstall -) + __in MSIHANDLE hInstall) { HRESULT hr = S_OK; DWORD er = ERROR_SUCCESS; @@ -24,8 +26,7 @@ LExit: } UINT __stdcall RemoveInstallFolder( - __in MSIHANDLE hInstall -) + __in MSIHANDLE hInstall) { HRESULT hr = S_OK; DWORD er = ERROR_SUCCESS; @@ -46,7 +47,7 @@ UINT __stdcall RemoveInstallFolder( ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz); SHFILEOPSTRUCTW fileOp; - ZeroMemory(&fileOp, sizeof(SHFILEOPSTRUCT)); + ZeroMemory(&fileOp, sizeof(SHFILEOPSTRUCT)); fileOp.wFunc = FO_DELETE; fileOp.pFrom = installFolder; @@ -68,3 +69,160 @@ LExit: er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); } + +#include "../../../src/platform/windows_delete_test_cert.cc"; +UINT __stdcall DeleteTestCerts( + __in MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "DeleteTestCerts"); + ExitOnFailure(hr, "Failed to initialize"); + + WcaLog(LOGMSG_STANDARD, "Initialized."); + + DeleteRustDeskTestCertsW(); + WcaLog(LOGMSG_STANDARD, "DeleteRustDeskTestCertsW finished."); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess +// **NtQueryInformationProcess** may be altered or unavailable in future versions of Windows. +// Applications should use the alternate functions listed in this topic. +// But I do not find the alternate functions. +// https://github.com/heim-rs/heim/issues/105#issuecomment-683647573 +typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); +bool TerminateProcessIfNotContainsParam(pfnNtQueryInformationProcess NtQueryInformationProcess, HANDLE process, LPCWSTR excludeParam) +{ + bool processClosed = false; + PROCESS_BASIC_INFORMATION processInfo; + NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &processInfo, sizeof(processInfo), NULL); + if (status == 0 && processInfo.PebBaseAddress != NULL) + { + PEB peb; + SIZE_T dwBytesRead; + if (ReadProcessMemory(process, processInfo.PebBaseAddress, &peb, sizeof(peb), &dwBytesRead)) + { + RTL_USER_PROCESS_PARAMETERS pebUpp; + if (ReadProcessMemory(process, + peb.ProcessParameters, + &pebUpp, + sizeof(RTL_USER_PROCESS_PARAMETERS), + &dwBytesRead)) + { + if (pebUpp.CommandLine.Length > 0) + { + WCHAR *commandLine = (WCHAR *)malloc(pebUpp.CommandLine.Length); + if (commandLine != NULL) + { + if (ReadProcessMemory(process, pebUpp.CommandLine.Buffer, + commandLine, pebUpp.CommandLine.Length, &dwBytesRead)) + { + if (wcsstr(commandLine, excludeParam) == NULL) + { + WcaLog(LOGMSG_STANDARD, "Terminate process : %ls", commandLine); + TerminateProcess(process, 0); + processClosed = true; + } + } + free(commandLine); + } + } + } + } + } + return processClosed; +} + +// Terminate processes that do not have parameter [excludeParam] +// Note. This function relies on "NtQueryInformationProcess", +// which may not be found. +// Then all processes of [processName] will be terminated. +bool TerminateProcessesByNameW(LPCWSTR processName, LPCWSTR excludeParam) +{ + HMODULE hntdll = GetModuleHandleW(L"ntdll.dll"); + if (hntdll == NULL) + { + WcaLog(LOGMSG_STANDARD, "Failed to load ntdll."); + } + + pfnNtQueryInformationProcess NtQueryInformationProcess = NULL; + if (hntdll != NULL) + { + NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress( + hntdll, "NtQueryInformationProcess"); + } + if (NtQueryInformationProcess == NULL) + { + WcaLog(LOGMSG_STANDARD, "Failed to get address of NtQueryInformationProcess."); + } + + bool processClosed = false; + // Create a snapshot of the current system processes + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot != INVALID_HANDLE_VALUE) + { + PROCESSENTRY32W processEntry; + processEntry.dwSize = sizeof(PROCESSENTRY32W); + if (Process32FirstW(snapshot, &processEntry)) + { + do + { + if (lstrcmpW(processName, processEntry.szExeFile) == 0) + { + HANDLE process = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processEntry.th32ProcessID); + if (process != NULL) + { + if (NtQueryInformationProcess == NULL) + { + WcaLog(LOGMSG_STANDARD, "Terminate process : %ls, while NtQueryInformationProcess is NULL", processName); + TerminateProcess(process, 0); + processClosed = true; + } + else + { + processClosed = TerminateProcessIfNotContainsParam( + NtQueryInformationProcess, + process, + excludeParam); + } + CloseHandle(process); + } + } + } while (Process32Next(snapshot, &processEntry)); + } + CloseHandle(snapshot); + } + if (hntdll != NULL) + { + CloseHandle(hntdll); + } + return processClosed; +} + +UINT __stdcall TerminateProcesses( + __in MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + DWORD er = ERROR_SUCCESS; + + int nResult = 0; + wchar_t szProcess[256] = {0}; + DWORD cchProcess = sizeof(szProcess) / sizeof(szProcess[0]); + + hr = WcaInitialize(hInstall, "TerminateProcesses"); + ExitOnFailure(hr, "Failed to initialize"); + + MsiGetPropertyW(hInstall, L"TerminateProcesses", szProcess, &cchProcess); + + WcaLog(LOGMSG_STANDARD, "Try terminate processes : %ls", szProcess); + TerminateProcessesByNameW(szProcess, L"--install"); + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} diff --git a/res/msi/CustomActions/CustomActions.def b/res/msi/CustomActions/CustomActions.def index 8eb50ce0d..08a539cb7 100644 --- a/res/msi/CustomActions/CustomActions.def +++ b/res/msi/CustomActions/CustomActions.def @@ -3,3 +3,5 @@ LIBRARY "CustomActions" EXPORTS CustomActionHello RemoveInstallFolder + DeleteTestCerts + TerminateProcesses diff --git a/res/msi/CustomActions/CustomActions.vcxproj b/res/msi/CustomActions/CustomActions.vcxproj index 3667b3bc8..b9491b73b 100644 --- a/res/msi/CustomActions/CustomActions.vcxproj +++ b/res/msi/CustomActions/CustomActions.vcxproj @@ -12,7 +12,7 @@ Win32Proj {6b3647e0-b4a3-46ae-8757-a22ee51c1dac} CustomActions - v143 + v142 10.0 diff --git a/res/msi/Package/Components/Regs.wxs b/res/msi/Package/Components/Regs.wxs index 4aec900df..ee14da48d 100644 --- a/res/msi/Package/Components/Regs.wxs +++ b/res/msi/Package/Components/Regs.wxs @@ -39,7 +39,17 @@ - + + + + + + + + + + + diff --git a/res/msi/Package/Components/RustDesk.wxs b/res/msi/Package/Components/RustDesk.wxs index 297f35a77..cf41ba01a 100644 --- a/res/msi/Package/Components/RustDesk.wxs +++ b/res/msi/Package/Components/RustDesk.wxs @@ -7,10 +7,7 @@ - - - - + @@ -33,51 +30,47 @@ + - + - - + Fix ICE 38 by adding a dummy registry key that is the key for this shortcut. + https://learn.microsoft.com/en-us/windows/win32/msi/ice38 + --> + - + - - + + + + + + - - - + - + @@ -87,6 +80,7 @@ + diff --git a/res/msi/Package/Fragments/AddRemoveProperties.wxs b/res/msi/Package/Fragments/AddRemoveProperties.wxs index 39d0775da..56dfb1854 100644 --- a/res/msi/Package/Fragments/AddRemoveProperties.wxs +++ b/res/msi/Package/Fragments/AddRemoveProperties.wxs @@ -8,16 +8,19 @@ - + + + + diff --git a/res/msi/Package/Fragments/CustomActions.wxs b/res/msi/Package/Fragments/CustomActions.wxs index 09d9b5698..500b5da40 100644 --- a/res/msi/Package/Fragments/CustomActions.wxs +++ b/res/msi/Package/Fragments/CustomActions.wxs @@ -6,18 +6,7 @@ - - - - - - - - - - - + + diff --git a/res/msi/Package/Package.wxs b/res/msi/Package/Package.wxs index d9f7d125a..cd6b29936 100644 --- a/res/msi/Package/Package.wxs +++ b/res/msi/Package/Package.wxs @@ -4,7 +4,7 @@ - + @@ -23,38 +23,26 @@ - - + - - - - - - - - - - - - - - + + - - - + + + + @@ -64,8 +52,13 @@ + + + + + diff --git a/res/msi/README.md b/res/msi/README.md index 80a2490bf..2650ad69b 100644 --- a/res/msi/README.md +++ b/res/msi/README.md @@ -6,19 +6,38 @@ This project is mainly derived from \n', + f'{indent}\n', f'{indent}\n', f'{indent}\n', f'{indent}\n', @@ -111,6 +154,7 @@ def gen_pre_vars(args, build_dir): f'{indent}\n', f'{indent}\n', f'{indent}\n', + f'{indent}\n', "\n", f"{indent}\n" f'{indent}\n', @@ -118,6 +162,7 @@ def gen_pre_vars(args, build_dir): for i, line in enumerate(to_insert_lines): lines.insert(index_start + i + 1, line) + return lines return gen_content_between_tags( "Package/Includes.wxi", "", "", func @@ -135,15 +180,15 @@ def replace_app_name_in_lans(app_name): f.writelines(lines) -def gen_upgrade_info(version): +def gen_upgrade_info(): def func(lines, index_start): indent = g_indent_unit * 3 - major, _, _ = version.split(".") + major, _, _ = g_version.split(".") upgrade_id = uuid.uuid4() to_insert_lines = [ f'{indent}\n', - f'{indent}{g_indent_unit}\n', + f'{indent}{g_indent_unit}\n', f"{indent}\n", ] @@ -159,6 +204,175 @@ def gen_upgrade_info(version): ) +def gen_custom_dialog_bitmaps(): + def func(lines, index_start): + indent = g_indent_unit * 2 + + # https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set + vars = [ + "WixUIBannerBmp", + "WixUIDialogBmp", + "WixUIExclamationIco", + "WixUIInfoIco", + "WixUINewIco", + "WixUIUpIco", + ] + to_insert_lines = [] + for var in vars: + if Path(f"Package/Resources/{var}.bmp").exists(): + to_insert_lines.append( + f'{indent}\n' + ) + + for i, line in enumerate(to_insert_lines): + lines.insert(index_start + i + 1, line) + return lines + + return gen_content_between_tags( + "Package/Package.wxs", + "", + "", + func, + ) + + +def gen_custom_ARPSYSTEMCOMPONENT_False(args): + def func(lines, index_start): + indent = g_indent_unit * 2 + + lines_new = [] + lines_new.append( + f"{indent}\n" + ) + lines_new.append( + f'{indent}\n\n' + ) + + lines_new.append( + f"{indent}\n" + ) + for _, v in g_arpsystemcomponent.items(): + if "msi" in v and "v" in v: + lines_new.append(f'{indent}\n') + + for i, line in enumerate(lines_new): + lines.insert(index_start + i + 1, line) + return lines + + return gen_content_between_tags( + "Package/Fragments/AddRemoveProperties.wxs", + "", + "", + func, + ) + + +def get_folder_size(folder_path): + total_size = 0 + + folder = Path(folder_path) + for file in folder.glob("**/*"): + if file.is_file(): + total_size += file.stat().st_size + + return total_size + + +def gen_custom_ARPSYSTEMCOMPONENT_True(args, build_dir): + def func(lines, index_start): + indent = g_indent_unit * 5 + + lines_new = [] + lines_new.append( + f"{indent}\n" + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + installDate = datetime.datetime.now().strftime("%Y%m%d") + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + + estimated_size = get_folder_size(build_dir) + lines_new.append( + f'{indent}\n' + ) + + lines_new.append( + f'{indent}\n' + ) + lines_new.append(f'{indent}\n') + lines_new.append( + f'{indent}\n' + ) + + major, minor, build = g_version.split(".") + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + lines_new.append( + f'{indent}\n' + ) + + lines_new.append( + f'{indent}\n' + ) + for k, v in g_arpsystemcomponent.items(): + if 'v' in v: + t = v['t'] if 't' in v is None else 'string' + lines_new.append(f'{indent}\n') + + for i, line in enumerate(lines_new): + lines.insert(index_start + i + 1, line) + return lines + + return gen_content_between_tags( + "Package/Components/Regs.wxs", + "", + "", + func, + ) + + +def gen_custom_ARPSYSTEMCOMPONENT(args, build_dir): + try: + custom_arp = json.loads(args.custom_arp) + g_arpsystemcomponent.update(custom_arp) + except json.JSONDecodeError as e: + print(f"Failed to decode custom arp: {e}") + return False + + if args.arp: + return gen_custom_ARPSYSTEMCOMPONENT_True(args, build_dir) + else: + return gen_custom_ARPSYSTEMCOMPONENT_False(args) + + def gen_content_between_tags(filename, tag_start, tag_end, func): target_file = Path(sys.argv[0]).parent.joinpath(filename) lines, index_start = read_lines_and_start_index(target_file, tag_start, tag_end) @@ -173,26 +387,69 @@ def gen_content_between_tags(filename, tag_start, tag_end, func): return True +def init_global_vars(args): + var_file = "../../src/version.rs" + if not Path(var_file).exists(): + print(f"Error: {var_file} not found") + return False + + with open(var_file, "r") as f: + content = f.readlines() + + global g_version + global g_build_date + g_version = args.version.replace("-", ".") + if g_version == "": + # pub const VERSION: &str = "1.2.4"; + version_pattern = re.compile(r'.*VERSION: &str = "(.*)";.*') + for line in content: + match = version_pattern.match(line) + if match: + g_version = match.group(1) + break + if g_version == "": + print(f"Error: version not found in {var_file}") + return False + + # pub const BUILD_DATE: &str = "2024-04-08 23:11"; + build_date_pattern = re.compile(r'BUILD_DATE: &str = "(.*)";') + for line in content: + match = build_date_pattern.match(line) + if match: + g_build_date = match.group(1) + break + + return True + + if __name__ == "__main__": parser = make_parser() args = parser.parse_args() app_name = args.app_name build_dir = ( - Path(sys.argv[0]) - .parent.joinpath( - f'../../flutter/build/windows/x64/runner/{"Debug" if args.debug else "Release"}' - ) - .resolve() + f'../../flutter/build/windows/x64/runner/{"Debug" if args.debug else "Release"}' ) + if args.github_ci: + build_dir = "../../rustdesk" + build_dir = Path(sys.argv[0]).parent.joinpath(build_dir).resolve() + + if not init_global_vars(args): + sys.exit(-1) if not gen_pre_vars(args, build_dir): sys.exit(-1) - if not gen_upgrade_info(args.version): + if not gen_upgrade_info(): + sys.exit(-1) + + if not gen_custom_ARPSYSTEMCOMPONENT(args, build_dir): sys.exit(-1) if not gen_auto_component(app_name, build_dir): sys.exit(-1) + if not gen_custom_dialog_bitmaps(): + sys.exit(-1) + replace_app_name_in_lans(args.app_name) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 66317e127..baa932c40 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1279,6 +1279,11 @@ fn get_before_uninstall(kill_self: bool) -> String { } fn get_uninstall(kill_self: bool) -> String { + let reg_uninstall_string = get_reg("UninstallString"); + if reg_uninstall_string.to_lowercase().contains("msiexec.exe") { + return reg_uninstall_string; + } + let mut uninstall_cert_cmd = "".to_string(); if let Ok(exe) = std::env::current_exe() { if let Some(exe_path) = exe.to_str() {