diff --git a/ReleaseBuilder/Build/Command.CreatePackage.cs b/ReleaseBuilder/Build/Command.CreatePackage.cs index 38654b566..debdcf111 100644 --- a/ReleaseBuilder/Build/Command.CreatePackage.cs +++ b/ReleaseBuilder/Build/Command.CreatePackage.cs @@ -560,19 +560,26 @@ public static partial class Command await ProcessHelper.Execute(wixArgs, workingDirectory: buildRoot); - // Apply any installer data tables present - // On Windows, the wxs files include the tables, but on Linux/MacOS, we need to apply them separately - // because wixl does not support the tables directly + // Apply post-wixl MSI table customizations on Linux/macOS. On Windows the + // real WiX toolchain handles all of these natively via WXS elements if (!OperatingSystem.IsWindows()) { if (string.IsNullOrWhiteSpace(rtcfg.Configuration.Commands.MsiBuild)) { - Console.WriteLine("WARNING: msiBuild not found, skipping IDT file injection."); + Console.WriteLine("WARNING: msiBuild not found, skipping SQL file injection."); } else { - foreach (var idtFile in Directory.GetFiles(resourcesSubDir, "*.idt")) - await ProcessHelper.Execute([rtcfg.Configuration.Commands.MsiBuild, msiFile, "-i", idtFile]); + foreach (var sqlFile in Directory.GetFiles(resourcesSubDir, "*.sql")) + { + var queries = (await File.ReadAllLinesAsync(sqlFile)) + .Select(line => line.Trim()) + .Where(line => line.Length > 0 && !line.StartsWith("--")) + .ToArray(); + + foreach (var query in queries) + await ProcessHelper.Execute([rtcfg.Configuration.Commands.MsiBuild, msiFile, "-q", query]); + } } } diff --git a/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs b/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs index 096cf2cfe..7e47d598b 100644 --- a/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs +++ b/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs @@ -35,6 +35,12 @@ + + + + + + @@ -87,6 +93,9 @@ SERVICE_WAS_STOPPED=1 AND NOT UPGRADINGPRODUCTCODE SERVICE_WAS_STOPPED=1 AND NOT REMOVE~="ALL" AND NOT UPGRADINGPRODUCTCODE + + + INSTALL_PRELOAD="true" AND NOT Installed @@ -119,5 +128,12 @@ JScriptCall="CustomAction" Execute="deferred" Return="check" /> + + + diff --git a/ReleaseBuilder/Resources/Windows/Agent/InstallTables.sql b/ReleaseBuilder/Resources/Windows/Agent/InstallTables.sql new file mode 100644 index 000000000..6fa48fc56 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/Agent/InstallTables.sql @@ -0,0 +1,60 @@ +-- Post-wixl MSI table customizations for the Agent installer. +-- +-- The file is processed by Command.CreatePackage.cs after wixl produces the +-- MSI: each non-empty, non-comment line is run as a single `msibuild -q` +-- query. Statements are executed in order; lines beginning with `--` and +-- blank lines are ignored. +-- +-- Why this exists: wixl 0.106 does not understand WiX elements such as +-- or (on a Component) - it silently drops +-- them or fails to build. On Windows the real WiX toolchain emits the +-- corresponding tables natively from the WXS sources; on Linux/macOS we +-- have to author the rows directly. Statements that touch tables wixl +-- already emits (RemoveFile, InstallExecuteSequence) must use INSERT only, +-- never CREATE, because `msibuild -q "CREATE TABLE foo"` against an +-- existing table fails. + +-- --------------------------------------------------------------------------- +-- MsiLockPermissionsEx: ACLs for the install dir. Replaces the WiX +-- element on SecureInstallDirComp that wixl cannot parse. +-- --------------------------------------------------------------------------- +CREATE TABLE `MsiLockPermissionsEx` (`MsiLockPermissionsEx` CHAR(72) NOT NULL, `LockObject` CHAR(72) NOT NULL, `Table` CHAR(32) NOT NULL, `SDDLText` CHAR(255) NOT NULL, `Condition` CHAR(255) PRIMARY KEY `MsiLockPermissionsEx`, `LockObject`, `Table`) +INSERT INTO `MsiLockPermissionsEx` (`MsiLockPermissionsEx`, `LockObject`, `Table`, `SDDLText`) VALUES ('SecureFolderACLs', 'INSTALLLOCATION', 'CreateFolder', 'D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU)') + +-- --------------------------------------------------------------------------- +-- MoveFile: copies preload.json from the directory containing the MSI +-- (resolved into PRELOAD_SOURCE by SetPreloadSource) into INSTALLLOCATION +-- when the user passes INSTALL_PRELOAD=true on the msiexec command line. +-- The MoveFile row is keyed to the always-installed Duplicati.Agent.exe +-- component so it ships with the rest of the binaries and follows their +-- install/uninstall lifecycle. Options=0 means copy (not move). Replaces +-- the WiX element that wixl cannot parse on a Component without +-- a child. +-- +-- The standard MoveFiles action that processes this table is scheduled +-- further below in this file. +-- --------------------------------------------------------------------------- +CREATE TABLE `MoveFile` (`FileKey` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `SourceName` CHAR(255) LOCALIZABLE, `DestName` CHAR(255) LOCALIZABLE, `SourceFolder` CHAR(72), `DestFolder` CHAR(72) NOT NULL, `Options` SHORT NOT NULL PRIMARY KEY `FileKey`) +INSERT INTO `MoveFile` (`FileKey`, `Component_`, `SourceName`, `DestName`, `SourceFolder`, `DestFolder`, `Options`) VALUES ('CopyPreloadJson', 'Duplicati.Agent.exe', 'preload.json', 'preload.json', 'PRELOAD_SOURCE', 'INSTALLLOCATION', 0) + +-- --------------------------------------------------------------------------- +-- RemoveFile: removes preload.json from INSTALLLOCATION on uninstall +-- (InstallMode=2 = msidbRemoveFileInstallModeOnRemove). The RemoveFile +-- table is already created by wixl so we only INSERT here. +-- --------------------------------------------------------------------------- +INSERT INTO `RemoveFile` (`FileKey`, `Component_`, `FileName`, `DirProperty`, `InstallMode`) VALUES ('RemovePreloadJson', 'Duplicati.Agent.exe', 'preload.json', 'INSTALLLOCATION', 2) + +-- --------------------------------------------------------------------------- +-- InstallExecuteSequence: schedule the standard ResolveSource and MoveFiles +-- actions so the MoveFile row above is actually processed. wixl does not +-- auto-insert these standard actions because their respective tables are +-- empty at wixl build time. Both are gated by INSTALL_PRELOAD="true" so they +-- only fire when the user explicitly opts in. +-- ResolveSource @ 850 - populates [SourceDir] before SetPreloadSource +-- consumes it (SetPreloadSource itself is declared in the WXS at ~1001 +-- via ). +-- MoveFiles @ 3700 - canonical position between RemoveFiles (3500) +-- and InstallFiles (4000). +-- --------------------------------------------------------------------------- +INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('ResolveSource', 'INSTALL_PRELOAD="true" AND NOT Installed', 850) +INSERT INTO `InstallExecuteSequence` (`Action`, `Condition`, `Sequence`) VALUES ('MoveFiles', 'INSTALL_PRELOAD="true"', 3700) diff --git a/ReleaseBuilder/Resources/Windows/Agent/MsiLockPermissionsEx.idt b/ReleaseBuilder/Resources/Windows/Agent/MsiLockPermissionsEx.idt deleted file mode 100644 index df09ccd8a..000000000 --- a/ReleaseBuilder/Resources/Windows/Agent/MsiLockPermissionsEx.idt +++ /dev/null @@ -1,4 +0,0 @@ -MsiLockPermissionsEx LockObject Table SDDLText Condition -s72 s72 s32 s255 S255 -MsiLockPermissionsEx MsiLockPermissionsEx LockObject Table -SecureFolderACLs INSTALLLOCATION CreateFolder D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRFX;;;BU) diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs b/ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs index 85bdb6ff1..bf7bbc7a2 100644 --- a/ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs +++ b/ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs @@ -54,6 +54,12 @@ elevated execution sequence. --> + + + + + + + @@ -105,8 +111,9 @@ Two registry keys are managed here: * Service - holds the Installed=1 keypath value AND the runtime-generated InitPassword value. - The key's ACL is hardened by - MsiLockPermissionsEx.idt. + The key's ACL is hardened by the + MsiLockPermissionsEx row in + InstallTables.sql. * ServiceState - holds the BootstrapApplied sentinel that the service writes once it has applied @@ -126,8 +133,8 @@ first start. --> + MsiLockPermissionsEx row in InstallTables.sql that hardens + this key's ACL can reference a stable row primary key. --> @@ -141,7 +148,8 @@ @@ -238,6 +246,9 @@ &DuplicatiServiceFeature=3 NOT (&DuplicatiServiceFeature=3) + + INSTALL_PRELOAD="true" AND NOT Installed + + +