Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .agents/skills/build-agent-msi/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
name: build-agent-msi
description: Build the Devolutions Agent Windows MSI installer locally. Use when asked to build, compile, or rebuild the agent MSI.
---

# Build Devolutions Agent MSI Locally

## Required Tools

- **MSBuild** — VS 2022: `C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe`
- **MakeAppx.exe** — Windows SDK: `C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\MakeAppx.exe`
- **Rust/cargo**

## Important Notes

- **Static CRT is required.** Local builds do NOT link the CRT statically by default, but CI does.
Without `+crt-static`, the binary fails at runtime with exit code `-1073741515` (`0xC0000135`, DLL not found)
on any machine that doesn't have the MSVC redistributable installed — including fresh VMs.
Always set `$Env:RUSTFLAGS = "-C target-feature=+crt-static"` before `cargo build`.

- **`devolutions-agent-updater` is NOT a separate package.** It is a `[[bin]]` target inside the
`devolutions-agent` package (see `devolutions-agent/Cargo.toml`). Building `-p devolutions-agent`
produces both `devolutions-agent.exe` and `devolutions-agent-updater.exe`. Do NOT use
`-p devolutions-agent-updater` — it will fail with "package ID specification did not match any packages".

- **PEDM shell extension MSIX only builds in debug profile.** The MakeAppx step is not wired into the
release build. Always point `DAGENT_PEDM_SHELL_EXT_MSIX` at `target\debug\DevolutionsPedmShellExt.msix`
even when building a release MSI.

- **MSI version encoding.** The MSI product version must have a major component < 256, so the year prefix
`20` must be stripped: `2026.1.0` → `26.1.0`. The registry decoder adds 2000 back.

- **MSBuild output filename.** MSBuild writes `Release\DevolutionsAgent.msi`. Rename it to the versioned
form (`DevolutionsAgent-x86_64-<version>.msi`) after the build if needed for distribution.

## Step 1 — Build Rust Binaries

```powershell
cd D:\devolutions-gateway
$Env:PATH += ";C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64" # needed for PEDM MSIX
$Env:RUSTFLAGS = "-C target-feature=+crt-static" # required — matches CI
cargo build --release -p devolutions-agent -p devolutions-session
cargo build -p devolutions-pedm-shell-ext # debug only — MSIX step not wired into release profile
# devolutions-agent-updater.exe is produced automatically alongside devolutions-agent.exe
```

## Step 2 — Download tun2socks and wintun

```powershell
cd D:\devolutions-gateway
pwsh ci/download-tun2socks.ps1
# Produces: tun2socks.exe and wintun.dll in the repo root
```

## Step 3 — Build the .NET DesktopAgent

```powershell
cd D:\devolutions-gateway\dotnet\DesktopAgent
dotnet build
# Output: bin\Debug\net48\DevolutionsDesktopAgent.exe
```

## Step 4 — Build the MSI

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

$base = "D:\devolutions-gateway"
$Env:DAGENT_EXECUTABLE = "$base\target\release\devolutions-agent.exe"
$Env:DAGENT_UPDATER_EXECUTABLE = "$base\target\release\devolutions-agent-updater.exe"
$Env:DAGENT_PEDM_SHELL_EXT_DLL = "$base\target\release\devolutions_pedm_shell_ext.dll"
$Env:DAGENT_PEDM_SHELL_EXT_MSIX = "$base\target\debug\DevolutionsPedmShellExt.msix" # always debug
$Env:DAGENT_SESSION_EXECUTABLE = "$base\target\release\devolutions-session.exe"
$Env:DAGENT_TUN2SOCKS_EXE = "$base\tun2socks.exe"
$Env:DAGENT_WINTUN_DLL = "$base\wintun.dll"
$Env:DAGENT_DESKTOP_AGENT_PATH = "$base\dotnet\DesktopAgent\bin\Debug\net48"
$version = (Get-Content "$base\VERSION" -Raw).Trim()
if ($version.StartsWith("20")) { $version = $version.Substring(2) } # strip century: 2026.1.0 → 26.1.0
$Env:DAGENT_VERSION = $version
$Env:DAGENT_PLATFORM = "x64"

& "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe" DevolutionsAgent.sln /t:clean,restore,build /p:Configuration=Release /verbosity:minimal
```

**Output:** `package/AgentWindowsManaged/Release/DevolutionsAgent.msi`

Optionally rename to a versioned filename and compute the SHA-256 (needed for `productinfo.json`):

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

## Installing the MSI

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

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

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

# Option 2 — silent install
msiexec /i "C:\path\to\DevolutionsAgent-x86_64-2026.1.0.0.msi" /quiet
```

## Common Build Failures

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

```powershell
cd D:\devolutions-gateway\package\AgentWindowsManaged
dotnet add package WixSharp.wix.bin --prerelease
# Must match the WixSharp version (3.14.1)
```

### MakeAppx Not Found During PEDM Shell Ext Build

```powershell
$Env:PATH += ";C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64"
```

### `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 the CI build and eliminates the dependency on the MSVC redistributable.
231 changes: 231 additions & 0 deletions .agents/skills/build-gateway-msi/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
---
name: build-gateway-msi
description: Build the Devolutions Gateway Windows MSI installer locally. Use when asked to build, compile, or rebuild the 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`.
Loading