Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2024-04-11 11:51:35 +08:00 committed by GitHub
parent a4f357fd80
commit 64020758d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 514 additions and 84 deletions

View File

@ -1,11 +1,13 @@
// CustomAction.cpp : Defines the entry point for the custom action.
#include "pch.h"
#include <strutil.h>
#include <shellapi.h>
#include <tlhelp32.h>
#include <winternl.h>
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);
}

View File

@ -3,3 +3,5 @@ LIBRARY "CustomActions"
EXPORTS
CustomActionHello
RemoveInstallFolder
DeleteTestCerts
TerminateProcesses

View File

@ -12,7 +12,7 @@
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{6b3647e0-b4a3-46ae-8757-a22ee51c1dac}</ProjectGuid>
<RootNamespace>CustomActions</RootNamespace>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />

View File

@ -39,7 +39,17 @@
<RegistryValue Type="string" Value='"[INSTALLFOLDER]$(var.Product)" "%1"' />
</RegistryKey>
</Component>
<!--For compatibility with registry values from previous versions-->
<Component Id="Product.Registry.UninstallRustDesk" Guid="FC1A3D2E-5642-FBD8-CFA6-5ECAC6DE69A8">
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\$(var.Product)" >
<RegistryValue Type="string" Name="BuildDate" Value="$(var.BuildDate)" />
<RegistryValue Type="string" Name="share_rdp" Value="" />
<!--$ArpStart$-->
<!--$ArpEnd$-->
</RegistryKey>
</Component>
</DirectoryRef>
</Fragment>

View File

@ -7,10 +7,7 @@
<DirectoryRef Id="INSTALLFOLDER" FileSource="$(var.BuildDir)">
<Component Id="RustDesk.exe" Guid="620F0F69-4C17-4320-A619-495E329712A4">
<File Id="$(var.Product).exe" Name="$(var.Product).exe" KeyPath="yes" Checksum="yes">
<fire:FirewallException Id="RustDeskExTCPDom" Name="$(var.Product) TCP Domain" Profile="domain" Protocol="tcp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskExTCPPriv" Name="$(var.Product) TCP Private" Profile="private" Protocol="tcp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskExUDPDom" Name="$(var.Product) UDP Domain" Profile="domain" Protocol="udp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskExUDPPriv" Name="$(var.Product) UDP Private" Profile="private" Protocol="udp" Scope="any" IgnoreFailure="yes" />
<fire:FirewallException Id="RustDeskEx" Name="$(var.Product) Service" Scope="any" IgnoreFailure="yes" />
</File>
<ServiceInstall Id="ServiceInstaller" Type="ownProcess" Vital="yes" Name="$(var.Product)" DisplayName="!(loc.Service_DisplayName)" Description="!(loc.Service_Description)" Start="auto" Account="LocalSystem" ErrorControl="ignore" Interactive="no" Arguments="--service" />
<ServiceControl Id="StartService" Start="install" Stop="both" Remove="uninstall" Name="$(var.Product)" Wait="yes" />
@ -33,51 +30,47 @@
<Custom Action="RemoveInstallFolder.SetParam" After="RemoveFiles"/>
<Custom Action="RemoveInstallFolder" After="RemoveInstallFolder.SetParam"/>
<Custom Action="DeleteTestCerts" After="RemoveFiles"/>
</InstallExecuteSequence>
<!-- Shortcuts -->
<DirectoryRef Id="App.StartMenu">
<Component Id="App.StartMenu" Guid="30F6D57A-B805-4DA4-A071-05A3B22400CA">
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu" Type="string" Value="1" KeyPath="yes" />
<RemoveFolder Id="Remove.App.StartMenu" On="uninstall" />
</Component>
</DirectoryRef>
<DirectoryRef Id="App.StartMenu">
<Component Id="App.StartMenu.Shortcut" Guid="43ABCAC7-E47D-42D8-A408-25EC70DBB993" Condition="STARTMENUSHORTCUTS = 1">
<Shortcut Id="App.StartMenu.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<!--
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
-->
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
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
-->
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
</Component>
<Component Id="App.StartMenu.ShortcutTray" Guid="9362C316-40BB-41C1-859C-08182AA47E8D" Condition="STARTMENUSHORTCUTS = 1">
<Component Id="App.StartMenu.ShortcutTray" Guid="9362C316-40BB-41C1-859C-08182AA47E8D" Condition="STARTMENUSHORTCUTS = 1">
<Shortcut Id="App.StartMenu.ShortcutTray" Name="!(loc.SC_Client_Tray)" Description="!(loc.SC_Client_Tray_Desc)" Target="[!RustDesk.exe]" Arguments="--tray" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<!--
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
-->
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.StartMenu.Shortcut" Type="string" Value="1" KeyPath="yes" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.ShortcutTray" Type="string" Value="1" KeyPath="yes" />
</Component>
<Component Id="App.StartMenu.ShortcutUninstall" Guid="E100D7F8-D607-4513-28DA-2C95E5EA698E" Condition="STARTMENUSHORTCUTS = 1">
<Shortcut Id="App.StartMenu.ShortcutUninstall" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" Icon="AppIcon" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.StartMenu.ShortcutUninstall" Type="string" Value="1" KeyPath="yes" />
</Component>
</DirectoryRef>
<StandardDirectory Id="DesktopFolder">
<Component Id="App.Desktop.Shortcut" Guid="CA8FB7AA-17F7-4E36-A58A-5A016A303709" Condition="DESKTOPSHORTCUTS = 1">
<Shortcut Id="App.Desktop.Shortcut" Name="!(loc.SC_Client)" Description="!(loc.SC_Client_Desc)" Target="[!RustDesk.exe]" Icon="AppIcon" WorkingDirectory="INSTALLFOLDER" />
<!--
Fix ICE 38 by adding a dummy registry key that is the key for this shortcut.
http://msdn.microsoft.com/library/en-us/msi/setup/ice38.asp
-->
<RegistryValue Root="HKCU" Key="$(var.RegKeyInstall)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
<RegistryValue Root="HKCU" Key="Software\$(var.Product)" Name="App.Desktop.Shortcut" Type="string" Value="1" KeyPath="yes" />
</Component>
</StandardDirectory>
<DirectoryRef Id="INSTALLFOLDER">
<Component Id="App.UninstallShortcut" Guid="FB0F2AC7-2AE5-4C54-B860-5E472620B6B1">
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]" IconIndex="0" />
<Shortcut Id="App.UninstallShortcut" Name="!(loc.SC_Uninstall)" Description="!(loc.SC_Uninstall_Desc)" Target="[System6432Folder]msiexec.exe" Arguments="/x [ProductCode]" IconIndex="0" />
</Component>
</DirectoryRef>
@ -87,6 +80,7 @@
<ComponentRef Id="App.UninstallShortcut" />
<ComponentRef Id="App.StartMenu.Shortcut" />
<ComponentRef Id="App.StartMenu.ShortcutTray" />
<ComponentRef Id="App.StartMenu.ShortcutUninstall" />
<!--$AutoComonentStart$-->
<!--$AutoComponentEnd$-->

View File

@ -8,16 +8,19 @@
<!--
Support entries shown when clicking "Click here for support information"
in Control Panel's Add/Remove Programs http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/configuration_properties.asp
in Control Panel's Add/Remove Programs https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
-->
<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
<!--<Property Id="ARPCOMMENTS" Value="!(loc.AR_Comment)" />
<Property Id="ARPCONTACT" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPHELPLINK" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPREADME" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPURLINFOABOUT" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/rustdesk/rustdesk" />-->
<Property Id="ARPPRODUCTICON" Value="AppIcon" />
<!--$ArpStart$-->
<!--$ArpEnd$-->
<Property Id="RUSTDESKNATIVEINSTALL">
<RegistrySearch Id="RustDeskNativeInstallSearch" Root="HKCR" Key="$(var.RegKeyRoot)\DefaultIcon" Type="raw" />

View File

@ -6,18 +6,7 @@
<CustomAction Id="CustomActionHello" DllEntry="CustomActionHello" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="RemoveInstallFolder" DllEntry="RemoveInstallFolder" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<!-- Use WixQuietExec to run the commands to avoid the command window popping up. The command line to run needs to be stored
in a property with the same id as the WixQuietExec custom action and the path to the exe needs to be quoted.
A SetProperty action is used to allow the [SystemFolder] reference to be resolved and needs to be scheduled to run before the action.-->
<SetProperty Id="FirewallPortOutAdd" Value="&quot;[SystemFolder]netsh.exe&quot; advfirewall firewall add rule name=&quot;$(var.Product) Service&quot; dir=out action=allow program=$(var.ProductLower) enable=yes" Before="FirewallPortOutAdd" Sequence="execute" />
<CustomAction Id="FirewallPortOutAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<SetProperty Id="FirewallPortInAdd" Value="&quot;[SystemFolder]netsh.exe&quot; advfirewall firewall add rule name=&quot;$(var.Product) Service&quot; dir=in action=allow program=$(var.ProductLower) enable=yes" Before="FirewallPortInAdd" Sequence="execute" />
<CustomAction Id="FirewallPortInAdd" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<SetProperty Id="FirewallPortRemove" Value="&quot;[SystemFolder]netsh.exe&quot; advfirewall firewall delete rule name=&quot;$(var.Product) Service&quot;" Before="FirewallPortRemove" Sequence="execute" />
<CustomAction Id="FirewallPortRemove" DllEntry="WixQuietExec" Execute="deferred" Return="asyncWait" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)" />
<CustomAction Id="DeleteTestCerts" DllEntry="DeleteTestCerts" Impersonate="no" Execute="deferred" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
<CustomAction Id="TerminateProcesses" DllEntry="TerminateProcesses" Impersonate="yes" Execute="immediate" Return="ignore" BinaryRef="Custom_Actions_Dll"/>
</Fragment>
</Wix>

View File

@ -4,7 +4,7 @@
<?include Includes.wxi?>
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" Codepage="0" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
<Package Name="$(var.Product)" Version="$(var.Version)" Manufacturer="$(var.Manufacturer)" Language="!(loc.ProductLanguage)" UpgradeCode="$(var.UpgradeCode)" Scope="perMachine">
<SummaryInformation Keywords="Installer" Description="$(var.Description)" Codepage="!(loc.SummaryCodepage)" />
@ -23,38 +23,26 @@
<InstallUISequence>
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="CostFinalize" Condition="NOT Installed" />-->
<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND RUSTDESKNATIVEINSTALL AND Not RUSTDESKNATIVEINSTALLFOLDER"/>
<!--<Show Dialog="AnotherAppDialog" Before="WelcomeDlg" Condition="Not installed AND RUSTDESKNATIVEINSTALL AND Not RUSTDESKNATIVEINSTALLFOLDER"/>-->
</InstallUISequence>
<InstallExecuteSequence>
<!-- Stop all MP2 processes -->
<!--<Custom Action="StopProcesses" Before="ReadCustomPathsFromExistingPathsFile" />-->
<!-- Reads custom paths which maybe have been changed by the user in a former installation -->
<!--<Custom Action="ReadCustomPathsFromExistingPathsFile" Before="PrepareXmlPathVariables" Condition="(NOT Installed) AND (INSTALLTYPE_CUSTOM = 0)" />-->
<!--<Custom Action="PrepareXmlPathVariables" Before="FileCost" Condition="NOT Installed" />-->
<!--<Custom Action="AttachClientToServer" After="InstallFinalize" Condition="NOT Installed" />-->
<Custom Action="FirewallPortRemove" Before="InstallFinalize" Condition="REMOVE~=&quot;ALL&quot;" />
<Custom Action="FirewallPortOutAdd" Before="InstallFinalize" Condition="NOT Installed" />
<Custom Action="FirewallPortInAdd" Before="InstallFinalize" Condition="NOT Installed" />
<!-- Launch ClientLauncher if installing or already installed and not uninstalling -->
<Custom Action="LaunchApp" After="InstallFinalize" />
<Custom Action="LaunchAppTray" After="InstallFinalize" />
<InstallExecute After="RemoveExistingProducts" />
<Custom Action="CloseProcesses" Before="RemoveFiles" />
<Custom Action="TerminateProcesses" Before="RemoveFiles"/>
<Custom Action="TerminateProcesses.SetParam" Before="TerminateProcesses"/>
</InstallExecuteSequence>
<CustomAction Id="LaunchApp" ExeCommand="" Return="asyncNoWait" FileRef="RustDesk.exe" />
<CustomAction Id="LaunchAppTray" ExeCommand=" --tray" Return="asyncNoWait" FileRef="RustDesk.exe" />
<CustomAction Id="CloseProcesses" ExeCommand='taskkill /F /IM "$(var.Product).exe"' Return="asyncNoWait" Directory="INSTALLFOLDER" />
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" />
<Property Id="TerminateProcesses" Value="RustDeskTest.exe" />
<CustomAction Id="TerminateProcesses.SetParam" Return="check" Property="TerminateProcesses" Value="$(var.Product).exe" />
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" Schedule="afterInstallInitialize" AllowSameVersionUpgrades="yes" />
<Feature Id="App" Level="1" AllowAdvertise="no" Display="expand" Title="!(loc.F_App)" Description="!(loc.F_App_Desc)" AllowAbsent="no">
<ComponentGroupRef Id="Components" />
@ -64,8 +52,13 @@
<ComponentRef Id="Product.Registry.CommandPlay" />
<ComponentRef Id="Product.Registry.URLProtocol" />
<ComponentRef Id="Product.Registry.Command" />
<ComponentRef Id="Product.Registry.UninstallRustDesk" />
<ComponentRef Id="App.StartMenu" />
<ComponentRef Id="Product.Registry.PersistedShortcutProperties" />
</Feature>
<!--https://wixtoolset.org/docs/tools/wixext/wixui/#customizing-a-dialog-set-->
<!--$CustomBitmapsStart$-->
<!--$CustomBitmapsEnd$-->
</Package>
</Wix>

View File

@ -6,19 +6,38 @@ This project is mainly derived from <https://github.com/MediaPortal/MediaPortal-
## Steps
1. `python preprocess.py`
1. `python preprocess.py`, see `python preprocess.py -h` for help.
2. Build the .sln solution.
Run `msiexec /i package.msi /l*v install.log` to record the log.
## Usage
1. Put the custom dialog bitmaps in "Resources" directory. The supported bitmaps are `['WixUIBannerBmp', 'WixUIDialogBmp', 'WixUIExclamationIco', 'WixUIInfoIco', 'WixUINewIco', 'WixUIUpIco']`.
## Knowledge
### properties
[wix-toolset-set-custom-action-run-only-on-uninstall](https://www.advancedinstaller.com/versus/wix-toolset/wix-toolset-set-custom-action-run-only-on-uninstall.html)
| Property Name | Install | Uninstall | Change | Repair | Upgrade |
| ------ | ------ | ------ | ------ | ------ | ------ |
| Installed | False | True | True | True | True |
| REINSTALL | False | False | False | True | False |
| UPGRADINGPRODUCTCODE | False | False | False | False | True |
| REMOVE | False | True | False | False | True |
## TODOs
1. tray, uninstall shortcut
1. launch client after installation
1. github ci
1. options
1. Start menu. Uninstall
1. custom options
1. Custom client.
1. firewall and tcp allow. Outgoing
1. Custom icon. Current `Resources/icon.ico`.
1. Show license ?
1. Do create service. Outgoing.
## Refs
1. [wxs](https://wixtoolset.org/docs/schema/wxs/)
1. [wxs github](https://github.com/wixtoolset/wix)

View File

@ -1,12 +1,38 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import sys
import uuid
import argparse
import datetime
import re
from pathlib import Path
g_indent_unit = "\t"
g_version = ""
g_build_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
# https://learn.microsoft.com/en-us/windows/win32/msi/property-reference
g_arpsystemcomponent = {
"Comments": {
"msi": "ARPCOMMENTS",
"t": "string",
"v": "!(loc.AR_Comment)",
},
"Contact": {
"msi": "ARPCONTACT",
"v": "https://github.com/rustdesk/rustdesk",
},
"HelpLink": {
"msi": "ARPHELPLINK",
"v": "https://github.com/rustdesk/rustdesk/issues/",
},
"ReadMe": {
"msi": "ARPREADME",
"v": "https://github.com/fufesou/rustdesk",
},
}
def make_parser():
@ -14,6 +40,23 @@ def make_parser():
parser.add_argument(
"-d", "--debug", action="store_true", help="Is debug", default=False
)
parser.add_argument(
"-ci", "--github-ci", action="store_true", help="Is github ci", default=False
)
parser.add_argument(
"-arp",
"--arp",
action="store_true",
help="Is ARPSYSTEMCOMPONENT",
default=False,
)
parser.add_argument(
"-custom-arp",
"--custom-arp",
type=str,
default="{}",
help='Custom arp properties, e.g. \'["Comments": {"msi": "ARPCOMMENTS", "v": "Remote control application."}]\'',
)
parser.add_argument(
"-c", "--custom", action="store_true", help="Is custom client", default=False
)
@ -21,13 +64,13 @@ def make_parser():
"-an", "--app-name", type=str, default="RustDesk", help="The app name."
)
parser.add_argument(
"-v", "--version", type=str, default="1.2.4", help="The app version."
"-v", "--version", type=str, default="", help="The app version."
)
parser.add_argument(
"-m",
"--manufacturer",
type=str,
default="Purslane Ltd",
default="RustDesk",
help="The app manufacturer.",
)
return parser
@ -103,7 +146,7 @@ def gen_pre_vars(args, build_dir):
indent = g_indent_unit * 1
to_insert_lines = [
f'{indent}<?define Version="{args.version}" ?>\n',
f'{indent}<?define Version="{g_version}" ?>\n',
f'{indent}<?define Manufacturer="{args.manufacturer}" ?>\n',
f'{indent}<?define Product="{args.app_name}" ?>\n',
f'{indent}<?define Description="{args.app_name} Installer" ?>\n',
@ -111,6 +154,7 @@ def gen_pre_vars(args, build_dir):
f'{indent}<?define RegKeyRoot=".$(var.ProductLower)" ?>\n',
f'{indent}<?define RegKeyInstall="$(var.RegKeyRoot)\Install" ?>\n',
f'{indent}<?define BuildDir="{build_dir}" ?>\n',
f'{indent}<?define BuildDate="{g_build_date}" ?>\n',
"\n",
f"{indent}<!-- The UpgradeCode must be consistent for each product. ! -->\n"
f'{indent}<?define UpgradeCode = "{upgrade_code}" ?>\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", "<!--$PreVarsStart$-->", "<!--$PreVarsEnd$-->", 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}<Upgrade Id="{upgrade_id}">\n',
f'{indent}{g_indent_unit}<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="{major}.0.0.0" Maximum="{major}.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />\n',
f'{indent}{g_indent_unit}<UpgradeVersion Property="OLD_VERSION_FOUND" Minimum="{major}.0.0" Maximum="{major}.99.99" IncludeMinimum="yes" IncludeMaximum="yes" OnlyDetect="no" IgnoreRemoveFailure="yes" MigrateFeatures="yes" />\n',
f"{indent}</Upgrade>\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}<WixVariable Id="{var}" Value="Resources\\{var}.bmp" />\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",
"<!--$CustomBitmapsStart$-->",
"<!--$CustomBitmapsEnd$-->",
func,
)
def gen_custom_ARPSYSTEMCOMPONENT_False(args):
def func(lines, index_start):
indent = g_indent_unit * 2
lines_new = []
lines_new.append(
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/arpsystemcomponent?redirectedfrom=MSDN-->\n"
)
lines_new.append(
f'{indent}<!--<Property Id="ARPSYSTEMCOMPONENT" Value="1" />-->\n\n'
)
lines_new.append(
f"{indent}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
)
for _, v in g_arpsystemcomponent.items():
if "msi" in v and "v" in v:
lines_new.append(f'{indent}<Property Id="{v["msi"]}" Value="{v["v"]}" />\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",
"<!--$ArpStart$-->",
"<!--$ArpEnd$-->",
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}<!--https://learn.microsoft.com/en-us/windows/win32/msi/property-reference-->\n"
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="DisplayName" Value="{args.app_name}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="DisplayIcon" Value="[INSTALLFOLDER]{args.app_name}.exe" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="DisplayVersion" Value="{g_version}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="Publisher" Value="{args.manufacturer}" />\n'
)
installDate = datetime.datetime.now().strftime("%Y%m%d")
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="InstallDate" Value="{installDate}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="InstallLocation" Value="[INSTALLFOLDER]" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="InstallSource" Value="[InstallSource]" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="Language" Value="[ProductLanguage]" />\n'
)
estimated_size = get_folder_size(build_dir)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="EstimatedSize" Value="{estimated_size}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="expandable" Name="ModifyPath" Value="MsiExec.exe /X [ProductCode]" />\n'
)
lines_new.append(f'{indent}<RegistryValue Type="integer" Id="NoModify" Value="1" />\n')
lines_new.append(
f'{indent}<RegistryValue Type="expandable" Name="UninstallString" Value="MsiExec.exe /X [ProductCode]" />\n'
)
major, minor, build = g_version.split(".")
lines_new.append(
f'{indent}<RegistryValue Type="string" Name="Version" Value="{g_version}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="VersionMajor" Value="{major}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="VersionMinor" Value="{minor}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="VersionBuild" Value="{build}" />\n'
)
lines_new.append(
f'{indent}<RegistryValue Type="integer" Name="WindowsInstaller" Value="1" />\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}<RegistryValue Type="{t}" Name="{k}" Value="{v["v"]}" />\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",
"<!--$ArpStart$-->",
"<!--$ArpEnd$-->",
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)

View File

@ -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() {