diff --git a/projects/msp-tools/guru-rmm/installer/.gitignore b/projects/msp-tools/guru-rmm/installer/.gitignore new file mode 100644 index 0000000..3a9dff7 --- /dev/null +++ b/projects/msp-tools/guru-rmm/installer/.gitignore @@ -0,0 +1,5 @@ +# Build artifacts — reproducible from wxs + downloaded binary +*.msi +*.wixpdb +install-test.log +src/gururmm-agent.exe diff --git a/projects/msp-tools/guru-rmm/installer/README.md b/projects/msp-tools/guru-rmm/installer/README.md new file mode 100644 index 0000000..3990c7f --- /dev/null +++ b/projects/msp-tools/guru-rmm/installer/README.md @@ -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-.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-.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 `.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`. diff --git a/projects/msp-tools/guru-rmm/installer/build-msi.ps1 b/projects/msp-tools/guru-rmm/installer/build-msi.ps1 new file mode 100644 index 0000000..8881054 --- /dev/null +++ b/projects/msp-tools/guru-rmm/installer/build-msi.ps1 @@ -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" diff --git a/projects/msp-tools/guru-rmm/installer/gururmm.wxs b/projects/msp-tools/guru-rmm/installer/gururmm.wxs new file mode 100644 index 0000000..6598bc9 --- /dev/null +++ b/projects/msp-tools/guru-rmm/installer/gururmm.wxs @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +