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
+
+
+