Skip to content

Commit 900e7af

Browse files
fix(msi): use WixQuietExec from WixToolset.Util.wixext
WiX 4 has a documented bug (wixtoolset discussion #9143) where [CustomActionData] is silently stripped from ExeCommand at compile time, leaving deferred CAs with no command line. The bare Property attribute is also invalid — WiX requires one of DllEntry/ExeCommand/Value/etc. The WiX-blessed pattern for this exact use case (run an exe with property- substituted args from a deferred CA) is WixQuietExec from the Util extension. It's a DLL custom action that reads its command line from the property whose Id matches the CA Id, which is auto-populated as CustomActionData by MSI. Adds the util: namespace to Product.wxs, wires the Util extension install into CI (workflow + Makefile), uses Wix4UtilCA_$(sys.BUILDARCHSHORT) so the binary auto-selects per arch.
1 parent c5143c8 commit 900e7af

4 files changed

Lines changed: 35 additions & 19 deletions

File tree

.github/workflows/release.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,15 +208,16 @@ jobs:
208208
- name: Checkout repository
209209
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
210210

211-
- name: Install WiX 4
212-
# WiX 4 ships as a .NET global tool. windows-latest has the .NET SDK
213-
# preinstalled, so this resolves quickly. We pin to a known-working
214-
# version. Linux/macOS hosts have WiX 4 issues with Directory/@Name
215-
# path validation — we always build the MSI on Windows.
211+
- name: Install WiX 4 + Util extension
212+
# WiX 4 ships as a .NET global tool. The Util extension (WixQuietExec
213+
# and friends) is a separate NuGet package that must be added to the
214+
# global wix tool before referencing util: namespace types.
216215
shell: pwsh
217216
run: |
218217
dotnet tool install --global wix --version 4.0.5
219218
wix --version
219+
wix extension add --global WixToolset.Util.wixext/4.0.5
220+
wix extension list --global
220221
221222
- name: Download Windows .exe assets from draft release
222223
env:
@@ -248,13 +249,15 @@ jobs:
248249
249250
wix build packaging/windows/Product.wxs `
250251
-arch x64 `
252+
-ext WixToolset.Util.wixext `
251253
-d Arch=x64 `
252254
-d "Version=$version" `
253255
-d "BinaryPath=$($amd64.FullName)" `
254256
-out "dist/stepsecurity-dev-machine-guard-$version-x64.msi"
255257
256258
wix build packaging/windows/Product.wxs `
257259
-arch arm64 `
260+
-ext WixToolset.Util.wixext `
258261
-d Arch=arm64 `
259262
-d "Version=$version" `
260263
-d "BinaryPath=$($arm64.FullName)" `

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,21 @@ build-linux:
2929
# with whatever the binary reports as `--version`.
3030
build-msi-amd64: build-windows
3131
mkdir -p dist
32+
wix extension add --global WixToolset.Util.wixext/4.0.5 || true
3233
wix build packaging/windows/Product.wxs \
3334
-arch x64 \
35+
-ext WixToolset.Util.wixext \
3436
-d Arch=x64 \
3537
-d Version=$(VERSION) \
3638
-d BinaryPath=$(CURDIR)/$(BINARY).exe \
3739
-out dist/stepsecurity-dev-machine-guard-$(VERSION)-x64.msi
3840

3941
build-msi-arm64: build-windows-arm64
4042
mkdir -p dist
43+
wix extension add --global WixToolset.Util.wixext/4.0.5 || true
4144
wix build packaging/windows/Product.wxs \
4245
-arch arm64 \
46+
-ext WixToolset.Util.wixext \
4347
-d Arch=arm64 \
4448
-d Version=$(VERSION) \
4549
-d BinaryPath=$(CURDIR)/$(BINARY)-arm64.exe \

internal/buildinfo/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package buildinfo
33
import "fmt"
44

55
const (
6-
Version = "1.11.2-msi-test5"
6+
Version = "1.11.2-msi-test6"
77
AgentURL = "https://github.com/step-security/dev-machine-guard"
88
)
99

packaging/windows/Product.wxs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
msiexec — powershell.exe is never spawned anywhere in install/upgrade/
1515
uninstall flows.
1616
-->
17-
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
17+
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
18+
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
1819

1920
<?if $(var.Arch) = "x64" ?>
2021
<?define UpgradeCode = "65AE0FC0-2070-4F40-B0CA-413637F94121" ?>
@@ -99,35 +100,43 @@
99100
================================================================
100101
-->
101102

102-
<!-- Use the Property-attribute pattern (MSI Type 50: exe-from-property).
103-
Discovered the hard way that WiX 4 silently strips [CustomActionData]
104-
tokens out of ExeCommand at compile time, so Directory+ExeCommand
105-
CAs end up with empty (or args-less) Target columns. The Property
106-
pattern bypasses ExeCommand entirely: MSI reads the FULL command
107-
line (path + args) directly from a property whose name matches the
108-
CA's Id, which gets auto-populated as CustomActionData for the
109-
deferred execution. -->
103+
<!-- Use WixQuietExec from WixToolset.Util.wixext. This is the WiX-blessed
104+
pattern for running an exe with property-substituted args from a
105+
deferred custom action. WiX 4 has a known bug where [CustomActionData]
106+
in ExeCommand is silently stripped at compile time
107+
(https://github.com/orgs/wixtoolset/discussions/9143), and the bare
108+
Property attribute is invalid without ExeCommand. WixQuietExec is a
109+
DLL CA that reads its command line from a property whose Id matches
110+
the CA Id — solving the relay problem cleanly.
111+
112+
BinaryRef "Wix4UtilCA_$(sys.BUILDARCHSHORT)" auto-selects the right
113+
arch (X64 for our x64 MSI, A64 for arm64).
114+
DllEntry "WixQuietExec64" runs in 64-bit context. -->
110115

111116
<CustomAction Id="RunConfigureInline"
112-
Property="RunConfigureInline"
117+
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
118+
DllEntry="WixQuietExec64"
113119
Execute="deferred"
114120
Impersonate="no"
115121
Return="check"/>
116122

117123
<CustomAction Id="RunConfigureFromFile"
118-
Property="RunConfigureFromFile"
124+
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
125+
DllEntry="WixQuietExec64"
119126
Execute="deferred"
120127
Impersonate="no"
121128
Return="check"/>
122129

123130
<CustomAction Id="RunInstallScheduledTask"
124-
Property="RunInstallScheduledTask"
131+
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
132+
DllEntry="WixQuietExec64"
125133
Execute="deferred"
126134
Impersonate="no"
127135
Return="check"/>
128136

129137
<CustomAction Id="RunUninstallScheduledTask"
130-
Property="RunUninstallScheduledTask"
138+
BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
139+
DllEntry="WixQuietExec64"
131140
Execute="deferred"
132141
Impersonate="no"
133142
Return="ignore"/>

0 commit comments

Comments
 (0)