Skip to content

Commit a7bc0e5

Browse files
chore(ai-skills): add skills to build local MSI (#1733)
Co-authored-by: Vladyslav Nikonov <mail@pacmancoder.xyz>
1 parent b5de812 commit a7bc0e5

2 files changed

Lines changed: 363 additions & 0 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
---
2+
name: build-agent-msi
3+
description: Build the Devolutions Agent Windows MSI installer locally. Use when asked to build, compile, or rebuild the agent MSI.
4+
---
5+
6+
# Build Devolutions Agent MSI Locally
7+
8+
## Required Tools
9+
10+
- **MSBuild** — VS 2022: `C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe`
11+
- **MakeAppx.exe** — Windows SDK: `C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\MakeAppx.exe`
12+
- **Rust/cargo**
13+
14+
## Important Notes
15+
16+
- **Static CRT is required.** Local builds do NOT link the CRT statically by default, but CI does.
17+
Without `+crt-static`, the binary fails at runtime with exit code `-1073741515` (`0xC0000135`, DLL not found)
18+
on any machine that doesn't have the MSVC redistributable installed — including fresh VMs.
19+
Always set `$Env:RUSTFLAGS = "-C target-feature=+crt-static"` before `cargo build`.
20+
21+
- **`devolutions-agent-updater` is NOT a separate package.** It is a `[[bin]]` target inside the
22+
`devolutions-agent` package (see `devolutions-agent/Cargo.toml`). Building `-p devolutions-agent`
23+
produces both `devolutions-agent.exe` and `devolutions-agent-updater.exe`. Do NOT use
24+
`-p devolutions-agent-updater` — it will fail with "package ID specification did not match any packages".
25+
26+
- **PEDM shell extension MSIX only builds in debug profile.** The MakeAppx step is not wired into the
27+
release build. Always point `DAGENT_PEDM_SHELL_EXT_MSIX` at `target\debug\DevolutionsPedmShellExt.msix`
28+
even when building a release MSI.
29+
30+
- **MSI version encoding.** The MSI product version must have a major component < 256, so the year prefix
31+
`20` must be stripped: `2026.1.0``26.1.0`. The registry decoder adds 2000 back.
32+
33+
- **MSBuild output filename.** MSBuild writes `Release\DevolutionsAgent.msi`. Rename it to the versioned
34+
form (`DevolutionsAgent-x86_64-<version>.msi`) after the build if needed for distribution.
35+
36+
## Step 1 — Build Rust Binaries
37+
38+
```powershell
39+
cd D:\devolutions-gateway
40+
$Env:PATH += ";C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64" # needed for PEDM MSIX
41+
$Env:RUSTFLAGS = "-C target-feature=+crt-static" # required — matches CI
42+
cargo build --release -p devolutions-agent -p devolutions-session
43+
cargo build -p devolutions-pedm-shell-ext # debug only — MSIX step not wired into release profile
44+
# devolutions-agent-updater.exe is produced automatically alongside devolutions-agent.exe
45+
```
46+
47+
## Step 2 — Download tun2socks and wintun
48+
49+
```powershell
50+
cd D:\devolutions-gateway
51+
pwsh ci/download-tun2socks.ps1
52+
# Produces: tun2socks.exe and wintun.dll in the repo root
53+
```
54+
55+
## Step 3 — Build the .NET DesktopAgent
56+
57+
```powershell
58+
cd D:\devolutions-gateway\dotnet\DesktopAgent
59+
dotnet build
60+
# Output: bin\Debug\net48\DevolutionsDesktopAgent.exe
61+
```
62+
63+
## Step 4 — Build the MSI
64+
65+
```powershell
66+
cd D:\devolutions-gateway\package\AgentWindowsManaged
67+
68+
$base = "D:\devolutions-gateway"
69+
$Env:DAGENT_EXECUTABLE = "$base\target\release\devolutions-agent.exe"
70+
$Env:DAGENT_UPDATER_EXECUTABLE = "$base\target\release\devolutions-agent-updater.exe"
71+
$Env:DAGENT_PEDM_SHELL_EXT_DLL = "$base\target\release\devolutions_pedm_shell_ext.dll"
72+
$Env:DAGENT_PEDM_SHELL_EXT_MSIX = "$base\target\debug\DevolutionsPedmShellExt.msix" # always debug
73+
$Env:DAGENT_SESSION_EXECUTABLE = "$base\target\release\devolutions-session.exe"
74+
$Env:DAGENT_TUN2SOCKS_EXE = "$base\tun2socks.exe"
75+
$Env:DAGENT_WINTUN_DLL = "$base\wintun.dll"
76+
$Env:DAGENT_DESKTOP_AGENT_PATH = "$base\dotnet\DesktopAgent\bin\Debug\net48"
77+
$version = (Get-Content "$base\VERSION" -Raw).Trim()
78+
if ($version.StartsWith("20")) { $version = $version.Substring(2) } # strip century: 2026.1.0 → 26.1.0
79+
$Env:DAGENT_VERSION = $version
80+
$Env:DAGENT_PLATFORM = "x64"
81+
82+
& "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe" DevolutionsAgent.sln /t:clean,restore,build /p:Configuration=Release /verbosity:minimal
83+
```
84+
85+
**Output:** `package/AgentWindowsManaged/Release/DevolutionsAgent.msi`
86+
87+
Optionally rename to a versioned filename and compute the SHA-256 (needed for `productinfo.json`):
88+
89+
```powershell
90+
$msi = "D:\devolutions-gateway\package\AgentWindowsManaged\Release\DevolutionsAgent.msi"
91+
$versioned = "D:\devolutions-gateway\package\AgentWindowsManaged\Release\DevolutionsAgent-x86_64-20$($Env:DAGENT_VERSION).0.msi"
92+
Copy-Item $msi $versioned -Force
93+
$hash = (Get-FileHash $versioned -Algorithm SHA256).Hash
94+
$hash | Set-Content "$([System.IO.Path]::ChangeExtension($versioned, 'sha'))"
95+
```
96+
97+
## Installing the MSI
98+
99+
**Always install with administrator rights.** Double-clicking the MSI from Explorer runs the installer
100+
under an impersonated (non-elevated) token, which causes `Error code 5` or silent failures in custom
101+
actions that require elevation.
102+
103+
Install from an **already-elevated** PowerShell prompt:
104+
105+
```powershell
106+
# Option 1 — msiexec with log (recommended for debugging)
107+
msiexec /i "C:\path\to\DevolutionsAgent-x86_64-2026.1.0.0.msi" /l*v "C:\path\to\log.log"
108+
109+
# Option 2 — silent install
110+
msiexec /i "C:\path\to\DevolutionsAgent-x86_64-2026.1.0.0.msi" /quiet
111+
```
112+
113+
## Common Build Failures
114+
115+
### Missing `WixSharp.wix.bin` NuGet Package
116+
117+
```powershell
118+
cd D:\devolutions-gateway\package\AgentWindowsManaged
119+
dotnet add package WixSharp.wix.bin --prerelease
120+
# Must match the WixSharp version (3.14.1)
121+
```
122+
123+
### MakeAppx Not Found During PEDM Shell Ext Build
124+
125+
```powershell
126+
$Env:PATH += ";C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64"
127+
```
128+
129+
### `0xC0000135` / Exit Code `-1073741515` at Runtime
130+
131+
The binary was built without static CRT. Rebuild with `$Env:RUSTFLAGS = "-C target-feature=+crt-static"`
132+
(see Step 1). This matches the CI build and eliminates the dependency on the MSVC redistributable.
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
---
2+
name: build-gateway-msi
3+
description: Build the Devolutions Gateway Windows MSI installer locally. Use when asked to build, compile, or rebuild the gateway MSI.
4+
---
5+
6+
# Build Devolutions Gateway MSI Locally
7+
8+
## Required Tools
9+
10+
- **MSBuild** — VS 2022: `C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe`
11+
- **Rust/cargo**
12+
- **pnpm** — for building the web app
13+
- **PowerShell 7+** (`pwsh`)
14+
- **dotnet** — for the PowerShell module build
15+
16+
## Important Notes
17+
18+
- **Static CRT is required for CI-matching builds.** Without `+crt-static`, the binary fails at runtime
19+
with exit code `-1073741515` (`0xC0000135`, DLL not found) on machines without the MSVC redistributable.
20+
Always set `$Env:RUSTFLAGS = "-C target-feature=+crt-static"` before `cargo build --release`.
21+
For a local debug build this is optional, but release builds must use it.
22+
23+
- **Version stripping.** The MSI product version must have a major component < 256, so the century
24+
prefix `20` must be stripped: `2026.1.0``26.1.0`. This matches what `ci/Build/Build.psm1` does
25+
via `$version.Substring(2)`.
26+
27+
- **MSBuild output filename.** MSBuild writes `Release\DevolutionsGateway.msi`. Rename it to the
28+
versioned form if needed for distribution.
29+
30+
- **Web app build is slow** (~5-10 min). Skip it on repeated builds by reusing the existing
31+
`webapp\dist\` output; only rebuild if webapp source changed.
32+
33+
- **`download-cadeau.ps1` runs from `ci/`** and always writes to `../native-libs/`, which resolves to
34+
the repo root's `native-libs\` directory. The file you need is `native-libs\xmf.dll`.
35+
36+
## Step 1 — Build Rust Binary
37+
38+
```powershell
39+
cd D:\devolutions-gateway
40+
$Env:RUSTFLAGS = "-C target-feature=+crt-static" # required for CI-matching release build
41+
cargo build --release -p devolutions-gateway
42+
# Output: target\release\devolutions-gateway.exe
43+
```
44+
45+
For a quick local debug build (no static CRT required, faster):
46+
47+
```powershell
48+
cd D:\devolutions-gateway
49+
cargo build -p devolutions-gateway
50+
# Output: target\debug\devolutions-gateway.exe
51+
```
52+
53+
## Step 2 — Download Cadeau (xmf.dll)
54+
55+
```powershell
56+
cd D:\devolutions-gateway
57+
pwsh ci/download-cadeau.ps1 -Platform win -Architecture x64
58+
# Output: native-libs\xmf.dll
59+
```
60+
61+
## Step 3 — Build PowerShell Module
62+
63+
```powershell
64+
cd D:\devolutions-gateway
65+
pwsh powershell/build.ps1
66+
# Output: powershell\package\DevolutionsGateway\
67+
```
68+
69+
## Step 4 — Build Web App
70+
71+
`@devolutions/*` packages are hosted on a private JFrog Artifactory registry.
72+
Before running `pnpm install` for the first time, configure authentication:
73+
74+
1. Log in: `npm login --registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/`
75+
2. Add these lines to `%USERPROFILE%\.npmrc` (they won't be added by `npm login` automatically):
76+
```
77+
@devolutions:registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/
78+
registry=https://registry.npmjs.org
79+
//devolutions.jfrog.io/artifactory/api/npm/npm/:_authToken=<same token as above>
80+
```
81+
The third line is needed because the lockfile contains some package tarballs
82+
at `/artifactory/` paths (different from `/devolutions/`), so a second token entry is required.
83+
84+
Then build:
85+
86+
```powershell
87+
cd D:\devolutions-gateway\webapp
88+
pnpm install
89+
90+
# Build in dependency order: shadow-player → multi-video-player → apps
91+
pnpm --filter '@devolutions/shadow-player' build # packages/shadow-player
92+
pnpm --filter '@devolutions/multi-video-player' build # packages/multi-video-player (depends on shadow-player)
93+
pnpm --filter './apps/gateway-ui' build
94+
pnpm --filter './apps/recording-player' build # depends on multi-video-player and shadow-player
95+
# Outputs:
96+
# webapp\dist\gateway-ui\
97+
# webapp\dist\recording-player\
98+
```
99+
100+
> **Note:** `pnpm build:libs` and `pnpm build:apps` may report "No projects matched" depending
101+
> on pnpm version/workspace state. Use the explicit `--filter` commands above instead.
102+
103+
## Step 5 — Build the MSI
104+
105+
```powershell
106+
cd D:\devolutions-gateway\package\WindowsManaged
107+
108+
$msbuild = "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe"
109+
$base = "D:\devolutions-gateway"
110+
111+
# Point at release binary (or replace with target\debug\devolutions-gateway.exe for debug build)
112+
$Env:DGATEWAY_EXECUTABLE = "$base\target\release\devolutions-gateway.exe"
113+
$Env:DGATEWAY_LIB_XMF_PATH = "$base\native-libs\xmf.dll"
114+
$Env:DGATEWAY_PSMODULE_PATH = "$base\powershell\package\DevolutionsGateway"
115+
$Env:DGATEWAY_WEBCLIENT_PATH = "$base\webapp\dist\gateway-ui"
116+
$Env:DGATEWAY_WEBPLAYER_PATH = "$base\webapp\dist\recording-player"
117+
$version = (Get-Content "$base\VERSION" -Raw).Trim()
118+
if ($version.StartsWith("20")) { $version = $version.Substring(2) } # strip century: 2026.1.0 → 26.1.0
119+
$Env:DGATEWAY_VERSION = $version
120+
121+
& $msbuild DevolutionsGateway.sln /t:clean,restore,build /p:Configuration=Release /verbosity:minimal
122+
```
123+
124+
**Output:** `package/WindowsManaged/Release/DevolutionsGateway.msi`
125+
126+
Optionally rename to a versioned filename and compute the SHA-256:
127+
128+
```powershell
129+
$msi = "D:\devolutions-gateway\package\WindowsManaged\Release\DevolutionsGateway.msi"
130+
$versioned = "D:\devolutions-gateway\package\WindowsManaged\Release\DevolutionsGateway-x86_64-20$($Env:DGATEWAY_VERSION).0.msi"
131+
Copy-Item $msi $versioned -Force
132+
$hash = (Get-FileHash $versioned -Algorithm SHA256).Hash
133+
$hash | Set-Content "$([System.IO.Path]::ChangeExtension($versioned, 'sha'))"
134+
```
135+
136+
## Alternative — Use the Packaging Script
137+
138+
The `ci/package-gateway-windows.ps1` script sets env vars and runs MSBuild for you:
139+
140+
```powershell
141+
$base = "D:\devolutions-gateway"
142+
New-Item -ItemType Directory -Force -Path "$base\output\msi" | Out-Null
143+
144+
pwsh "$base\ci\package-gateway-windows.ps1" `
145+
-Exe "$base\target\release\devolutions-gateway.exe" `
146+
-LibxmfFile "$base\native-libs\xmf.dll" `
147+
-PsModuleDir "$base\powershell\package\DevolutionsGateway" `
148+
-WebClientDir "$base\webapp\dist\gateway-ui" `
149+
-WebPlayerDir "$base\webapp\dist\recording-player" `
150+
-OutputDir "$base\output\msi"
151+
# Copies MSI to output\msi\DevolutionsGateway.msi
152+
```
153+
154+
Note: the script requires `MSBuild.exe` to be on `$Env:PATH`. If it isn't, prepend it:
155+
156+
```powershell
157+
$Env:PATH = "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin;$Env:PATH"
158+
```
159+
160+
## Installing the MSI
161+
162+
**Always install with administrator rights.** Double-clicking from Explorer runs the installer under an
163+
impersonated (non-elevated) token, which causes `Error code 5` or silent failures in custom actions.
164+
165+
Install from an **already-elevated** PowerShell prompt:
166+
167+
```powershell
168+
# Option 1 — msiexec with log (recommended for debugging)
169+
msiexec /i "C:\path\to\DevolutionsGateway.msi" /l*v "C:\path\to\log.log"
170+
171+
# Option 2 — silent install
172+
msiexec /i "C:\path\to\DevolutionsGateway.msi" /quiet
173+
```
174+
175+
## Common Build Failures
176+
177+
### Missing `WixSharp.wix.bin` NuGet Package
178+
179+
```
180+
error MSB4018: The "MSBuild" task failed unexpectedly.
181+
```
182+
183+
```powershell
184+
cd D:\devolutions-gateway\package\WindowsManaged
185+
dotnet add package WixSharp.wix.bin --prerelease
186+
```
187+
188+
### `0xC0000135` / Exit Code `-1073741515` at Runtime
189+
190+
The binary was built without static CRT. Rebuild with `$Env:RUSTFLAGS = "-C target-feature=+crt-static"`
191+
(see Step 1). This matches CI and eliminates the dependency on the MSVC redistributable.
192+
193+
### `DGATEWAY_EXECUTABLE` Not Found
194+
195+
Program.cs checks that the file exists at MSBuild time. Ensure `cargo build` completed successfully
196+
and the path in `$Env:DGATEWAY_EXECUTABLE` is correct (debug vs. release).
197+
198+
### `DGATEWAY_PSMODULE_PATH` Not Found / Empty
199+
200+
Run `pwsh powershell/build.ps1` first. The output directory is `powershell\package\DevolutionsGateway`.
201+
If the build fails due to a missing NuGet source, the script adds `api.nuget.org` automatically; ensure
202+
internet access or restore from cache.
203+
204+
### `DGATEWAY_WEBCLIENT_PATH` / `DGATEWAY_WEBPLAYER_PATH` Not Found
205+
206+
Run `pnpm install && pnpm build:libs && pnpm build:apps` in `webapp/`. Both `gateway-ui` and
207+
`recording-player` must exist under `webapp\dist\` before the MSI build starts.
208+
209+
### `pnpm install` Fails with `ERR_PNPM_FETCH_404` for `@devolutions/icons`
210+
211+
`@devolutions/icons` is a private package not available on the public npm registry. It requires
212+
access to a private npm registry (JFrog Artifactory). Fix:
213+
214+
1. Log in: `npm login --registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/`
215+
2. Append to `%USERPROFILE%\.npmrc`:
216+
```
217+
@devolutions:registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/
218+
registry=https://registry.npmjs.org
219+
//devolutions.jfrog.io/artifactory/api/npm/npm/:_authToken=<same token as the /devolutions/ line>
220+
```
221+
The `/artifactory/` token entry is needed because some packages in the lockfile resolve
222+
to that URL path rather than `/devolutions/`.
223+
224+
### `pnpm build:libs` / `pnpm build:apps` — "No projects matched"
225+
226+
Use explicit `--filter` calls instead (see Step 4). The glob filters in `package.json` scripts
227+
may not resolve depending on pnpm version.
228+
229+
The workspace library packages (`shadow-player`, `multi-video-player`) are in `packages/` and
230+
must be built before the apps that consume them. Build order: `shadow-player``multi-video-player`
231+
`gateway-ui` and `recording-player`.

0 commit comments

Comments
 (0)