Skip to content

Building and Releasing

Rod Christiansen edited this page Oct 8, 2026 · 2 revisions

Building and releasing

build.ps1 is the whole build. CI runs it, releases run it, and it is the same script you run locally.

Prerequisites

  • PowerShell 7 or later. The script refuses to run on Windows PowerShell 5.
  • .NET SDK 10. CI pins 10.0.x; the release workflow uses the preview quality channel.
  • Windows with the Windows 10 SDK. makepri.exe from the SDK builds the app's resources.pri, and the build stops without it.

Optional:

  • cimipkg, which produces both the MSI and the NUPKG. If it is not on PATH or in the repo root, build.ps1 downloads cimipkg-win-x64.zip or cimipkg-win-arm64.zip, matching the host, from the latest cimian-pkg release.
  • nuget on PATH, for the NUPKG step.
  • A code-signing certificate with a private key in Cert:\CurrentUser\My, and signtool.exe from the Windows SDK.
  • gh, for -CreateRelease.
  • cimiimport for -Import, and the Cimian installer for -Install.

Building

.\build.ps1

That runs the whole pipeline: version resolution, module-version generation, dotnet restore / build / publish of the runner, the usage tracker and the app, the Speedtest CLI fetch, and then NUPKG, MSI and ZIP into release/.

Just the executables, unsigned, for a quick iteration:

.\build.ps1 -NoSign -SkipMSI -SkipNUPKG -SkipZIP

Parameters

Parameter Default Effect
-Version <string> date stamp Version to build, YYYY.MM.DD or YYYY.MM.DD.HHMM. A leading v is stripped; anything else fails
-Configuration <Release|Debug> Release Build configuration
-Clean off Remove previous artifacts first
-SkipBuild off Reuse the existing .NET output
-SkipMSI off Skip the MSI
-SkipNUPKG off Skip the NUPKG
-SkipZIP off Skip the ZIP
-Sign off Force signing. Fails when no certificate can be selected
-NoSign off Never sign, even if a certificate is found
-Thumbprint <sha1> auto Use a specific certificate. Wins over auto-detection
-ApiUrl <url> empty Default API URL for the installer
-CreateTag off Accepted, but tag creation is disabled in the script; tag by hand
-CreateRelease off Create a GitHub release with gh, uploading every .msi, .nupkg and .zip in release/
-Import off Run cimiimport <msi> --nointeractive to import the MSI into a Cimian repository
-Install off Install the built MSI locally with sudo installer --pkg <msi>
-Verbose off Verbose output

Versioning

Versions are date stamps, YYYY.MM.DD.HHMM. Without -Version the script uses the current local time. The value is passed to every project as -p:VersionPrefix= and -p:Version=, so the runner, the app, the usage tracker and the packages all carry the same version. A release passes its tag.

Per-module versions are separate. Before compiling, build.ps1 writes src/Generated/ModuleVersions.g.cs by asking git for the last commit date touching each module's three files (its processor, its models and its osquery pack), formatted the same way. A module's moduleVersion in the payload therefore tells you when that module last changed, independent of the client build. Without git history the fallback is the current timestamp.

This generated file is why the test project cannot compile before build.ps1 has run: the client does not build without it.

What gets built

Output How
managedreportsrunner.exe dotnet publish of src/ReportMate.WindowsClient.csproj for win-x64: self-contained, single file, trimmed
usagetracker.exe dotnet publish of usagetracker/UsageTracker.csproj for win-x64
Managed Reports Runner.exe and its folder dotnet publish of src/App/ReportMate.App.csproj for win-x64, self-contained with the Windows App SDK; resources.pri made with makepri.exe. The build stops if either is missing
speedtest.exe Taken from build/resources/speedtest/, or downloaded from Ookla (Speedtest CLI 1.2.0, win64) when absent

Signing

Signing is on when -Sign is given or a certificate is found. Auto-detection selects the newest unexpired certificate with a private key in Cert:\CurrentUser\My whose subject contains CN=<ENTERPRISE_CERT_CN>, where ENTERPRISE_CERT_CN is an environment variable. With the variable unset, auto-detection is skipped and the build is unsigned unless you pass -Thumbprint. The certificate subject is site-specific and never lives in the source.

Set the certificate name for the session:

$env:ENTERPRISE_CERT_CN = 'Contoso Code Signing'

The script resolves signtool.exe by walking the Windows SDK bin\<version>\<arch>\ directories and validating the PE header of each candidate, rather than trusting PATH; a Developer Command Prompt can otherwise put an ARM64 signtool.exe in front on an x64 host. Timestamping falls through DigiCert, Sectigo and Entrust. When signing is on, the runner, the app, speedtest.exe and the MSI are signed, and cimipkg receives --sign-thumbprint.

Packaging

The MSI and the NUPKG are both produced by cimipkg from build/pkg/ and build/nupkg/. There is no WiX authoring in the repo; everything a WiX custom action would have done (PATH, registry, ACLs, scheduled tasks, osquery bootstrap, first check-in) lives in the package's postinstall.ps1. See Installation. build-info.yaml in each directory carries the product metadata, with {{VERSION}} substituted at build time and restored afterwards, and install_location: C:\Program Files\ReportMate\.

The MSI payload is the runner, the app folder, usagetracker.exe, speedtest.exe, appsettings.json (when present), appsettings.yaml, appsettings.template.yaml, module-schedules.json, uninstall-tasks.ps1, the osquery\ packs, cimian\postflight.ps1 and a version.txt. A .env in the repo root is copied into both package directories.

After cimipkg runs, build.ps1 opens the MSI's File table and checks it against the $expectedPayload list parsed out of build/pkg/scripts/postinstall.ps1. A mismatch fails the build, because an MSI whose post-install throws on a missing file would leave every device that installs it without scheduled tasks.

The NUPKG's postinstall.ps1 is regenerated each build from postinstall.clean.ps1 with build/resources/install-tasks.ps1 inlined.

The ZIP is the NUPKG payload plus an install.bat that copies the files and runs managedreportsrunner.exe install.

CI

.github/workflows/ci.yml runs on pull requests to main and pushes to main, on windows-latest:

  1. Checkout, set up .NET 10, restore the NuGet cache.
  2. .\build.ps1 -NoSign -SkipMSI -SkipNUPKG -SkipZIP -Configuration Release
  3. dotnet test tests\ReportMate.WindowsClient.Tests\ReportMate.WindowsClient.Tests.csproj

The test step runs after the build for the module-version reason above.

Releases

.github/workflows/release.yml runs on a pushed tag matching v* or a YYYY.MM.DD.HHMM date stamp. The tag is the version, so it must be (after an optional leading v) exactly YYYY.MM.DD.HHMM, or the workflow stops. It:

  1. Checks out with submodules and builds with .\build.ps1 -NoSign -Version <tag>.
  2. Verifies every version against the tag: the file and product versions of managedreportsrunner.exe, Managed Reports Runner.exe and usagetracker.exe from the ZIP, and the MSI's ProductVersion and CIMIAN_PKG_FULL_VERSION properties. Windows Installer keeps only three version fields with a major of 255 or less, so cimipkg maps the date into ProductVersion and keeps the whole tag in CIMIAN_PKG_FULL_VERSION.
  3. Verifies that the MSI, the NUPKG and the ZIP each contain managedreportsrunner.exe, Managed Reports Runner.exe and resources.pri.
  4. Captures the .NET SDK version, the runner OS and the cimipkg version.
  5. Generates release notes from the GitHub API, strips contributor attribution, prepends a build-provenance block and appends a signing recipe.
  6. Creates the release titled ReportMate <version> and uploads every .msi, .nupkg, .intunewin and .zip under release/.

Release artifacts are unsigned. The workflow passes -NoSign explicitly. Anyone deploying to a fleet is expected to sign the binaries and the installer with their own certificate; the generated notes carry the signtool commands.

Releases are cut deliberately by pushing a tag; merging to main runs CI only. The latest release is listed on the releases page.

Where things land

release/
  ReportMate-<version>.msi
  ReportMate-<version>.nupkg
  ReportMate-<version>.zip

Clone this wiki locally