# Build Gateway Msi

> Build the Devolutions Gateway Windows MSI installer locally. Use when asked to build, compile, or rebuild the gateway MSI.

- Skill: `devolutions/build-gateway-msi` (Agent Skill)
- Install (CLI): `npx skillmds@latest add devolutions/build-gateway-msi`
- Raw SKILL.md: https://api.skillmd.com/api/skills/devolutions/build-gateway-msi/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: devolutions (https://skillmd.com/u/devolutions)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/devolutions/build-gateway-msi

---


# Build Devolutions Gateway MSI Locally

## Required Tools

- **MSBuild** — VS 2022: `C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe`
- **Rust/cargo**
- **pnpm** — for building the web app
- **PowerShell 7+** (`pwsh`)
- **dotnet** — for the PowerShell module build

## Important Notes

- **Static CRT is required for CI-matching builds.** Without `+crt-static`, the binary fails at runtime
  with exit code `-1073741515` (`0xC0000135`, DLL not found) on machines without the MSVC redistributable.
  Always set `$Env:RUSTFLAGS = "-C target-feature=+crt-static"` before `cargo build --release`.
  For a local debug build this is optional, but release builds must use it.

- **Version stripping.** The MSI product version must have a major component < 256, so the century
  prefix `20` must be stripped: `2026.1.0` → `26.1.0`. This matches what `ci/Build/Build.psm1` does
  via `$version.Substring(2)`.

- **MSBuild output filename.** MSBuild writes `Release\DevolutionsGateway.msi`. Rename it to the
  versioned form if needed for distribution.

- **Web app build is slow** (~5-10 min). Skip it on repeated builds by reusing the existing
  `webapp\dist\` output; only rebuild if webapp source changed.

- **`download-cadeau.ps1` runs from `ci/`** and always writes to `../native-libs/`, which resolves to
  the repo root's `native-libs\` directory. The file you need is `native-libs\xmf.dll`.

## Step 1 — Build Rust Binary

```powershell
cd D:\devolutions-gateway
$Env:RUSTFLAGS = "-C target-feature=+crt-static"  # required for CI-matching release build
cargo build --release -p devolutions-gateway
# Output: target\release\devolutions-gateway.exe
```

For a quick local debug build (no static CRT required, faster):

```powershell
cd D:\devolutions-gateway
cargo build -p devolutions-gateway
# Output: target\debug\devolutions-gateway.exe
```

## Step 2 — Download Cadeau (xmf.dll)

```powershell
cd D:\devolutions-gateway
pwsh ci/download-cadeau.ps1 -Platform win -Architecture x64
# Output: native-libs\xmf.dll
```

## Step 3 — Build PowerShell Module

```powershell
cd D:\devolutions-gateway
pwsh powershell/build.ps1
# Output: powershell\package\DevolutionsGateway\
```

## Step 4 — Build Web App

`@devolutions/*` packages are hosted on a private JFrog Artifactory registry.
Before running `pnpm install` for the first time, configure authentication:

1. Log in: `npm login --registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/`
2. Add these lines to `%USERPROFILE%\.npmrc` (they won't be added by `npm login` automatically):
   ```
   @devolutions:registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/
   registry=https://registry.npmjs.org
   //devolutions.jfrog.io/artifactory/api/npm/npm/:_authToken=<same token as above>
   ```
   The third line is needed because the lockfile contains some package tarballs
   at `/artifactory/` paths (different from `/devolutions/`), so a second token entry is required.

Then build:

```powershell
cd D:\devolutions-gateway\webapp
pnpm install

# Build in dependency order: shadow-player → multi-video-player → apps
pnpm --filter '@devolutions/shadow-player' build       # packages/shadow-player
pnpm --filter '@devolutions/multi-video-player' build  # packages/multi-video-player (depends on shadow-player)
pnpm --filter './apps/gateway-ui' build
pnpm --filter './apps/recording-player' build          # depends on multi-video-player and shadow-player
# Outputs:
#   webapp\dist\gateway-ui\
#   webapp\dist\recording-player\
```

> **Note:** `pnpm build:libs` and `pnpm build:apps` may report "No projects matched" depending
> on pnpm version/workspace state. Use the explicit `--filter` commands above instead.

## Step 5 — Build the MSI

```powershell
cd D:\devolutions-gateway\package\WindowsManaged

$msbuild = "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe"
$base = "D:\devolutions-gateway"

# Point at release binary (or replace with target\debug\devolutions-gateway.exe for debug build)
$Env:DGATEWAY_EXECUTABLE     = "$base\target\release\devolutions-gateway.exe"
$Env:DGATEWAY_LIB_XMF_PATH   = "$base\native-libs\xmf.dll"
$Env:DGATEWAY_PSMODULE_PATH  = "$base\powershell\package\DevolutionsGateway"
$Env:DGATEWAY_WEBCLIENT_PATH = "$base\webapp\dist\gateway-ui"
$Env:DGATEWAY_WEBPLAYER_PATH = "$base\webapp\dist\recording-player"
$version = (Get-Content "$base\VERSION" -Raw).Trim()
if ($version.StartsWith("20")) { $version = $version.Substring(2) }  # strip century: 2026.1.0 → 26.1.0
$Env:DGATEWAY_VERSION = $version

& $msbuild DevolutionsGateway.sln /t:clean,restore,build /p:Configuration=Release /verbosity:minimal
```

**Output:** `package/WindowsManaged/Release/DevolutionsGateway.msi`

Optionally rename to a versioned filename and compute the SHA-256:

```powershell
$msi = "D:\devolutions-gateway\package\WindowsManaged\Release\DevolutionsGateway.msi"
$versioned = "D:\devolutions-gateway\package\WindowsManaged\Release\DevolutionsGateway-x86_64-20$($Env:DGATEWAY_VERSION).0.msi"
Copy-Item $msi $versioned -Force
$hash = (Get-FileHash $versioned -Algorithm SHA256).Hash
$hash | Set-Content "$([System.IO.Path]::ChangeExtension($versioned, 'sha'))"
```

## Alternative — Use the Packaging Script

The `ci/package-gateway-windows.ps1` script sets env vars and runs MSBuild for you:

```powershell
$base = "D:\devolutions-gateway"
New-Item -ItemType Directory -Force -Path "$base\output\msi" | Out-Null

pwsh "$base\ci\package-gateway-windows.ps1" `
  -Exe        "$base\target\release\devolutions-gateway.exe" `
  -LibxmfFile "$base\native-libs\xmf.dll" `
  -PsModuleDir "$base\powershell\package\DevolutionsGateway" `
  -WebClientDir "$base\webapp\dist\gateway-ui" `
  -WebPlayerDir "$base\webapp\dist\recording-player" `
  -OutputDir  "$base\output\msi"
# Copies MSI to output\msi\DevolutionsGateway.msi
```

Note: the script requires `MSBuild.exe` to be on `$Env:PATH`. If it isn't, prepend it:

```powershell
$Env:PATH = "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin;$Env:PATH"
```

## Installing the MSI

**Always install with administrator rights.** Double-clicking from Explorer runs the installer under an
impersonated (non-elevated) token, which causes `Error code 5` or silent failures in custom actions.

Install from an **already-elevated** PowerShell prompt:

```powershell
# Option 1 — msiexec with log (recommended for debugging)
msiexec /i "C:\path\to\DevolutionsGateway.msi" /l*v "C:\path\to\log.log"

# Option 2 — silent install
msiexec /i "C:\path\to\DevolutionsGateway.msi" /quiet
```

## Common Build Failures

### Missing `WixSharp.wix.bin` NuGet Package

```
error MSB4018: The "MSBuild" task failed unexpectedly.
```

```powershell
cd D:\devolutions-gateway\package\WindowsManaged
dotnet add package WixSharp.wix.bin --prerelease
```

### `0xC0000135` / Exit Code `-1073741515` at Runtime

The binary was built without static CRT. Rebuild with `$Env:RUSTFLAGS = "-C target-feature=+crt-static"`
(see Step 1). This matches CI and eliminates the dependency on the MSVC redistributable.

### `DGATEWAY_EXECUTABLE` Not Found

Program.cs checks that the file exists at MSBuild time. Ensure `cargo build` completed successfully
and the path in `$Env:DGATEWAY_EXECUTABLE` is correct (debug vs. release).

### `DGATEWAY_PSMODULE_PATH` Not Found / Empty

Run `pwsh powershell/build.ps1` first. The output directory is `powershell\package\DevolutionsGateway`.
If the build fails due to a missing NuGet source, the script adds `api.nuget.org` automatically; ensure
internet access or restore from cache.

### `DGATEWAY_WEBCLIENT_PATH` / `DGATEWAY_WEBPLAYER_PATH` Not Found

Run `pnpm install && pnpm build:libs && pnpm build:apps` in `webapp/`. Both `gateway-ui` and
`recording-player` must exist under `webapp\dist\` before the MSI build starts.

### `pnpm install` Fails with `ERR_PNPM_FETCH_404` for `@devolutions/icons`

`@devolutions/icons` is a private package not available on the public npm registry. It requires
access to a private npm registry (JFrog Artifactory). Fix:

1. Log in: `npm login --registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/`
2. Append to `%USERPROFILE%\.npmrc`:
   ```
   @devolutions:registry=https://devolutions.jfrog.io/devolutions/api/npm/npm/
   registry=https://registry.npmjs.org
   //devolutions.jfrog.io/artifactory/api/npm/npm/:_authToken=<same token as the /devolutions/ line>
   ```
   The `/artifactory/` token entry is needed because some packages in the lockfile resolve
   to that URL path rather than `/devolutions/`.

### `pnpm build:libs` / `pnpm build:apps` — "No projects matched"

Use explicit `--filter` calls instead (see Step 4). The glob filters in `package.json` scripts
may not resolve depending on pnpm version.

The workspace library packages (`shadow-player`, `multi-video-player`) are in `packages/` and
must be built before the apps that consume them. Build order: `shadow-player` → `multi-video-player`
→ `gateway-ui` and `recording-player`.

