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