Skip to content

Commit 9a3a90b

Browse files
fix(msi): switch custom actions to deferred + SetProperty CustomActionData pattern
Immediate CAs were running during MSI's sequence-building phase, before InstallFiles physically writes the .exe to disk. msiexec then failed with error 1721 ("program required could not be run") when trying to invoke the binary. Switching to deferred CAs ensures they run during the script execution phase, after the binary is on disk. CustomActionData pattern relays property values across the immediate/deferred boundary.
1 parent 1235415 commit 9a3a90b

2 files changed

Lines changed: 60 additions & 26 deletions

File tree

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-test2"
6+
Version = "1.11.2-msi-test3"
77
AgentURL = "https://github.com/step-security/dev-machine-guard"
88
)
99

packaging/windows/Product.wxs

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -85,50 +85,84 @@
8585
================================================================
8686
Custom Actions
8787
88-
All three CAs are immediate (run in msiexec's own context, which is
89-
elevated/SYSTEM under SCCM) so we avoid the CustomActionData dance
90-
that deferred CAs require. ExeCommand interpolates [PROPERTY] tokens
91-
at sequence time. Return="check" makes a non-zero exit roll back
92-
the install transaction.
88+
All four CAs run DEFERRED with Impersonate=no — they execute
89+
during the install script phase (after InstallFiles physically
90+
writes the .exe to disk) in SYSTEM context. Immediate CAs were
91+
tried first but failed with MSI error 1721 ("program required
92+
could not be run") because immediate CAs run during sequence
93+
*building*, before the binary is on disk.
94+
95+
Deferred CAs can't read MSI properties directly. The standard
96+
workaround is paired SetProperty elements that populate the
97+
CustomActionData property which the deferred CA then reads as
98+
its command line.
9399
================================================================
94100
-->
95101

96-
<!-- Configure with inline credentials: runs when APIKEY is set and
97-
BOOTSTRAPFILE is not. Tenant config (customer ID, endpoint, key,
98-
frequency) comes from msiexec command line. -->
102+
<!-- Deferred command-runners. ExeCommand="[CustomActionData]" pulls
103+
the actual command line from the CustomActionData property,
104+
which the SetProperty elements below populate at sequence
105+
time (when [INSTALLFOLDER], [CUSTOMERID], etc. ARE expandable). -->
106+
99107
<CustomAction Id="RunConfigureInline"
100108
Directory="INSTALLFOLDER"
101-
ExeCommand='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" configure --non-interactive --customer-id "[CUSTOMERID]" --api-endpoint "[APIENDPOINT]" --api-key "[APIKEY]" --scan-frequency "[SCANFREQUENCY]"'
102-
Execute="immediate"
109+
ExeCommand="[CustomActionData]"
110+
Execute="deferred"
111+
Impersonate="no"
103112
Return="check"/>
104113

105-
<!-- Configure from a pre-staged JSON: runs when BOOTSTRAPFILE is set.
106-
Recommended for SCCM since the API key never appears on the
107-
msiexec command line (and therefore never in AppEnforce.log). -->
108114
<CustomAction Id="RunConfigureFromFile"
109115
Directory="INSTALLFOLDER"
110-
ExeCommand='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" configure --non-interactive --from-file "[BOOTSTRAPFILE]"'
111-
Execute="immediate"
116+
ExeCommand="[CustomActionData]"
117+
Execute="deferred"
118+
Impersonate="no"
112119
Return="check"/>
113120

114-
<!-- Register the Windows Task Scheduler entry via the binary's own
115-
`install` subcommand. Internally that shells out to schtasks.exe;
116-
no PowerShell, no .NET, no WMI. -->
117121
<CustomAction Id="RunInstallScheduledTask"
118122
Directory="INSTALLFOLDER"
119-
ExeCommand='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" install'
120-
Execute="immediate"
123+
ExeCommand="[CustomActionData]"
124+
Execute="deferred"
125+
Impersonate="no"
121126
Return="check"/>
122127

123-
<!-- Uninstall path: remove the scheduled task before MSI removes the
124-
.exe. Return="ignore" so a missing task (already removed manually)
125-
doesn't block uninstall. -->
126128
<CustomAction Id="RunUninstallScheduledTask"
127129
Directory="INSTALLFOLDER"
128-
ExeCommand='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" uninstall'
129-
Execute="immediate"
130+
ExeCommand="[CustomActionData]"
131+
Execute="deferred"
132+
Impersonate="no"
130133
Return="ignore"/>
131134

135+
<!-- SetProperty elements: WiX shorthand for an immediate Type 51 CA.
136+
Each one sets a property whose name matches a deferred CA Id;
137+
MSI auto-populates that CA's CustomActionData from this value
138+
at execution time. Property tokens like [INSTALLFOLDER] and
139+
[APIKEY] expand here (in the immediate phase) — so by the time
140+
the deferred CA runs, the command line is fully resolved. -->
141+
142+
<SetProperty Id="RunConfigureInline"
143+
Value='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" configure --non-interactive --customer-id "[CUSTOMERID]" --api-endpoint "[APIENDPOINT]" --api-key "[APIKEY]" --scan-frequency "[SCANFREQUENCY]"'
144+
Sequence="execute"
145+
Before="RunConfigureInline"
146+
Condition="APIKEY AND NOT BOOTSTRAPFILE AND NOT Installed"/>
147+
148+
<SetProperty Id="RunConfigureFromFile"
149+
Value='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" configure --non-interactive --from-file "[BOOTSTRAPFILE]"'
150+
Sequence="execute"
151+
Before="RunConfigureFromFile"
152+
Condition="BOOTSTRAPFILE AND NOT Installed"/>
153+
154+
<SetProperty Id="RunInstallScheduledTask"
155+
Value='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" install'
156+
Sequence="execute"
157+
Before="RunInstallScheduledTask"
158+
Condition="NOT Installed"/>
159+
160+
<SetProperty Id="RunUninstallScheduledTask"
161+
Value='"[INSTALLFOLDER]stepsecurity-dev-machine-guard.exe" uninstall'
162+
Sequence="execute"
163+
Before="RunUninstallScheduledTask"
164+
Condition="REMOVE=&quot;ALL&quot;"/>
165+
132166
<!--
133167
InstallExecuteSequence: where each CA fires in the MSI sequence.
134168

0 commit comments

Comments
 (0)