Add GuruRMM Agent MSI installer (WiX 5) — Phase 1 MVP

Signed Windows installer using our Azure Trusted Signing pipeline. Phase 1
scope: installs signed agent to Program Files, creates ProgramData dir,
Apps & Features entry with proper publisher, clean install + uninstall.

Phase 2 deferred: service registration, MSI properties for site-code
injection, agent install/uninstall custom actions, firewall rules.

Verified end-to-end on Windows workstation:
- wix build produces 1.16 MB MSI
- sign.ps1 signs it against gururmm-public-trust cert profile
- msiexec /qn installs silently, signature chain verifies on installed binary
- msiexec /x uninstalls cleanly, retains ProgramData

Tooling prerequisites documented in installer/README.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 08:19:52 -07:00
parent 2937c29f07
commit 148ac75a25
4 changed files with 235 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
# Build artifacts — reproducible from wxs + downloaded binary
*.msi
*.wixpdb
install-test.log
src/gururmm-agent.exe

View File

@@ -0,0 +1,97 @@
# GuruRMM Agent MSI Installer
Signed Windows installer for the GuruRMM agent. Builds on Windows via WiX 5,
signed with Azure Trusted Signing. Produces a `gururmm-agent-<version>.msi`
suitable for double-click install, silent install via `msiexec /qn`, or GPO
Software Installation deployment.
## Status
**Phase 1 (current):** MVP — installs binary to `C:\Program Files\GuruRMM\`,
creates `C:\ProgramData\GuruRMM\` data directory, Apps & Features entry with
proper publisher, clean silent install + uninstall.
**Phase 2 (planned):**
- `ServiceInstall` element to register the Windows service on install
- MSI properties for `SITE_CODE`, `SERVER_URL`, `API_KEY` passed at install time
- Custom actions to invoke the agent's native `install` / `uninstall` subcommands
- Firewall rule registration (if the tunnel subscriber path requires inbound)
- Start menu entry (optional; most customers don't need it for background agent)
## Prerequisites (build host)
- Windows 10 / 11 / Server 2019+ (WiX v5 is Windows-only per upstream)
- .NET SDK 8 — `winget install --id Microsoft.DotNet.SDK.8 -e`
- WiX v5 — `dotnet tool install --global wix --version 5.0.2`
- Windows SDK signtool — typically already present if Visual Studio Build Tools
or Windows SDK is installed
- Azure Trusted Signing `sign.ps1` + dlib at `C:\tools\trusted-signing\`
- `az login` active session with the `gururmm-build-signer` SP, or an
interactive user with the `Artifact Signing Certificate Profile Signer`
role on the `gururmm-public-trust` certificate profile
## Build
```powershell
cd installer
.\build-msi.ps1 -Version 0.6.1
```
Defaults:
- Downloads `gururmm-agent-windows-amd64-<version>.exe` from
`https://rmm-api.azcomputerguru.com/downloads/`
- Refuses to package an unsigned agent (verifies signature before packaging)
- Signs the resulting MSI against the `gururmm-public-trust` cert profile
- Emits `<msi>.sha256` alongside
Flags:
- `-SkipSign` — build without signing (dev/test)
- `-KeepSource` — don't delete `src/gururmm-agent.exe` after build
- `-SourceUrl` — override download origin (e.g., for staging)
## Install
```powershell
# Interactive (UAC prompt → "Verified publisher: Arizona Computer Guru LLC")
.\gururmm-agent-0.6.1.msi
# Silent (no UI, return code 0 = success, writes verbose log)
msiexec /i gururmm-agent-0.6.1.msi /qn /l*v install.log
# Silent with (future) site-code baking once Phase 2 custom actions land
msiexec /i gururmm-agent-0.6.1.msi /qn SITE_CODE=xyz123 SERVER_URL=wss://rmm-api.example.com/ws /l*v install.log
```
## Uninstall
```powershell
# Via Apps & Features: "GuruRMM Agent" → Uninstall
# Or silent:
msiexec /x gururmm-agent-0.6.1.msi /qn
# By ProductCode if original MSI isn't handy:
msiexec /x {PRODUCT-CODE-GUID-HERE} /qn
```
Uninstall removes `C:\Program Files\GuruRMM\` contents but **preserves
`C:\ProgramData\GuruRMM\`** (logs, config, device identity). Manually delete
that directory if doing a full purge.
## Files
| File | Purpose |
|---|---|
| `gururmm.wxs` | WiX installer definition — canonical source |
| `build-msi.ps1` | Build + sign wrapper |
| `src/gururmm-agent.exe` | Downloaded signed agent at build time (gitignored) |
| `gururmm-agent-*.msi` | Build output (gitignored) |
| `gururmm-agent-*.wixpdb` | WiX debug symbols (gitignored) |
| `install-test.log` | Install log from local smoke tests (gitignored) |
## UpgradeCode
The UpgradeCode `4c0aef59-9d08-4781-a3b4-a1c99b3b2e28` is the **permanent
identity** of the GuruRMM agent product family. Never change it. All future
versions must ship with this same UpgradeCode so MSI upgrades work
automatically via `msiexec /i newer.msi`.

View File

@@ -0,0 +1,75 @@
<#
.SYNOPSIS
Build + sign a GuruRMM Agent MSI installer.
.DESCRIPTION
Downloads the signed agent binary for the target version, packages it into
an MSI via WiX, signs the MSI with Azure Trusted Signing, and writes the
result to the current directory.
Requires:
- .NET SDK 8
- wix global tool (dotnet tool install --global wix --version 5.0.2)
- Azure Trusted Signing access via sign.ps1 at C:\tools\trusted-signing\
- az login session (DefaultAzureCredential)
.EXAMPLE
.\build-msi.ps1 -Version 0.6.1
.\build-msi.ps1 -Version 0.6.1 -SourceUrl https://rmm-api.azcomputerguru.com/downloads -SkipSign
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $Version,
[string] $SourceUrl = 'https://rmm-api.azcomputerguru.com/downloads',
[string] $WixExe = "$env:USERPROFILE\.dotnet\tools\wix.exe",
[string] $SignScript = 'C:\tools\trusted-signing\sign.ps1',
[switch] $SkipSign,
[switch] $KeepSource
)
$ErrorActionPreference = 'Stop'
Set-Location $PSScriptRoot
if (-not (Test-Path $WixExe)) { throw "wix.exe not found at $WixExe" }
$srcDir = Join-Path $PSScriptRoot 'src'
if (-not (Test-Path $srcDir)) { New-Item -ItemType Directory -Path $srcDir | Out-Null }
$exePath = Join-Path $srcDir 'gururmm-agent.exe'
$downloadUrl = "$SourceUrl/gururmm-agent-windows-amd64-$Version.exe"
Write-Host "[1] Downloading signed agent $Version from $downloadUrl ..." -ForegroundColor Cyan
Invoke-WebRequest -Uri $downloadUrl -OutFile $exePath -UseBasicParsing
$sig = Get-AuthenticodeSignature $exePath
if ($sig.Status -ne 'Valid') {
throw "Downloaded agent has invalid or missing signature: $($sig.Status). Refusing to package an unsigned agent."
}
Write-Host " signed by: $($sig.SignerCertificate.Subject)" -ForegroundColor Gray
$msiName = "gururmm-agent-$Version.msi"
Write-Host "[2] Building $msiName via WiX ..." -ForegroundColor Cyan
& $WixExe build gururmm.wxs -arch x64 -o $msiName -d "Version=$Version"
if ($LASTEXITCODE -ne 0) { throw "wix build failed (exit $LASTEXITCODE)" }
if (-not $SkipSign) {
if (-not (Test-Path $SignScript)) { throw "sign.ps1 not found at $SignScript" }
Write-Host "[3] Signing $msiName ..." -ForegroundColor Cyan
& $SignScript -File (Join-Path $PSScriptRoot $msiName) `
-Description "GuruRMM Agent Installer v$Version" `
-Url 'https://www.azcomputerguru.com' `
-Verify
if ($LASTEXITCODE -ne 0) { throw "signing failed (exit $LASTEXITCODE)" }
}
if (-not $KeepSource) {
Remove-Item $exePath -ErrorAction SilentlyContinue
}
$msiPath = Join-Path $PSScriptRoot $msiName
$hash = (Get-FileHash $msiPath -Algorithm SHA256).Hash.ToLower()
$hash | Set-Content "$msiPath.sha256"
"$hash $msiName" | Set-Content "$msiPath.sha256"
Write-Host ""
Write-Host "[DONE]" -ForegroundColor Green
Write-Host " msi: $msiPath"
Write-Host " sha256: $hash"

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
GuruRMM Agent Windows Installer
Builds an MSI that installs the signed agent binary, creates the runtime
data directory, registers the Windows service, and supports clean
upgrade + uninstall via Programs and Features.
Build: wix build gururmm.wxs -arch x64 -o gururmm-agent-VERSION.msi
Sign: (use sign.ps1 against the resulting .msi before shipping)
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="GuruRMM Agent"
Manufacturer="Arizona Computer Guru LLC"
Version="0.6.1"
UpgradeCode="4c0aef59-9d08-4781-a3b4-a1c99b3b2e28"
Scope="perMachine"
InstallerVersion="500">
<SummaryInformation Description="GuruRMM Agent — Remote monitoring and management agent"
Manufacturer="Arizona Computer Guru LLC" />
<MajorUpgrade DowngradeErrorMessage="A newer version of GuruRMM Agent is already installed. Uninstall the newer version first if you need to downgrade." />
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<!-- Install location: C:\Program Files\GuruRMM\ -->
<StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="GuruRMM" />
</StandardDirectory>
<!-- Runtime data + logs: C:\ProgramData\GuruRMM\ -->
<StandardDirectory Id="CommonAppDataFolder">
<Directory Id="DATAFOLDER" Name="GuruRMM" />
</StandardDirectory>
<ComponentGroup Id="AgentComponents" Directory="INSTALLFOLDER">
<Component Id="AgentExe" Guid="9b3a6b4f-b6e6-4baf-9dfa-4c6a67cff11c">
<File Id="AgentExeFile"
Source="src\gururmm-agent.exe"
Name="gururmm-agent.exe"
KeyPath="yes" />
</Component>
</ComponentGroup>
<!-- ProgramData GuruRMM folder (created empty; agent populates config + logs) -->
<ComponentGroup Id="DataDirComponents" Directory="DATAFOLDER">
<Component Id="DataDir" Guid="3f2b51c7-9e22-4c11-94d6-f1e6a9e4d8a0" KeyPath="yes">
<CreateFolder />
</Component>
</ComponentGroup>
<Feature Id="MainFeature" Title="GuruRMM Agent" Level="1" AllowAbsent="no">
<ComponentGroupRef Id="AgentComponents" />
<ComponentGroupRef Id="DataDirComponents" />
</Feature>
</Package>
</Wix>