Repository navigation
Building and Releasing
build.ps1 is the whole build. CI runs it, releases run it, and it is the same
script you run locally.
- 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.exefrom the SDK builds the app'sresources.pri, and the build stops without it.
Optional:
-
cimipkg, which produces both the MSI and the NUPKG. If it is not onPATHor in the repo root,build.ps1downloadscimipkg-win-x64.ziporcimipkg-win-arm64.zip, matching the host, from the latestcimian-pkgrelease. -
nugetonPATH, for the NUPKG step. - A code-signing certificate with a private key in
Cert:\CurrentUser\My, andsigntool.exefrom the Windows SDK. -
gh, for-CreateRelease. -
cimiimportfor-Import, and the Cimianinstallerfor-Install.
.\build.ps1That 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| 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 |
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.
| 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 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.
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.
.github/workflows/ci.yml runs on pull requests to main and pushes to
main, on windows-latest:
- Checkout, set up .NET 10, restore the NuGet cache.
.\build.ps1 -NoSign -SkipMSI -SkipNUPKG -SkipZIP -Configuration Releasedotnet test tests\ReportMate.WindowsClient.Tests\ReportMate.WindowsClient.Tests.csproj
The test step runs after the build for the module-version reason above.
.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:
- Checks out with submodules and builds with
.\build.ps1 -NoSign -Version <tag>. - Verifies every version against the tag: the file and product versions of
managedreportsrunner.exe,Managed Reports Runner.exeandusagetracker.exefrom the ZIP, and the MSI'sProductVersionandCIMIAN_PKG_FULL_VERSIONproperties. Windows Installer keeps only three version fields with a major of 255 or less, socimipkgmaps the date intoProductVersionand keeps the whole tag inCIMIAN_PKG_FULL_VERSION. - Verifies that the MSI, the NUPKG and the ZIP each contain
managedreportsrunner.exe,Managed Reports Runner.exeandresources.pri. - Captures the .NET SDK version, the runner OS and the
cimipkgversion. - Generates release notes from the GitHub API, strips contributor attribution, prepends a build-provenance block and appends a signing recipe.
- Creates the release titled
ReportMate <version>and uploads every.msi,.nupkg,.intunewinand.zipunderrelease/.
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.
release/
ReportMate-<version>.msi
ReportMate-<version>.nupkg
ReportMate-<version>.zip
MIT licensed. reportmate.app