Files
james.maesandClaude Fable 5.1 397b60026a
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: Shared .NET CI / verify (pull_request) Skipped
QRun-IO/ci-policy: Shared .NET CI / portable (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1b3648d9421543350067e95e54959dfb471b26d5] (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: Shared .NET CI / secrets (pull_request) Successful in 20s
QRun-IO/ci-policy: Shared source and config CI / verify (pull_request) Successful in 14s
QRun-IO/ci-policy: Shared source and config CI / secrets (pull_request) Successful in 17s
QRun-IO/ci-policy: Shared source and config CI / gate (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Successful in 3m12s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 4s
QRun-IO/ci-policy: Shared .NET CI / hosted (pull_request) Failing after 6m13s
QRun-IO/ci-policy: Shared .NET CI / gate (pull_request) Failing after 8s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Failing after 6m22s
QRun-IO/ci-policy: native / native-result (pull_request) Failing after 4s
feat: edition options for server wording and About sections
An edition descriptor may set serverName (what user-facing text calls the server;
the generic build keeps QQQ), and aboutMascot/aboutLicense (default true) to leave
the Query mascot and License sections out of the About dialog while the header and
Open source components stay. The generic build is unchanged. About composition moves
to AboutContent so the window and the layout harness render the same dialog. The
gg-forge edition sets serverName to GG Forge and turns both sections off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-05 11:29:11 -06:00

8.7 KiB
Raw Permalink Blame History

Customer branding

There are two levels. A client edition is a separate, fully branded build and installer: its own mark, icon and logo, the client's product and company in every executable and in Windows, and its own installer and update asset. Deployment branding applies labels, a colour and artwork to the shared installer at install time. Editions are for clients who get their own installer; deployment branding is for customers of the shared one.

Client editions

Add a folder under editions/ (see editions/README.md) with edition.json, app-icon.svg, app.ico and an optional logo.png, then build with:

./build/Build-Installer.ps1 -ProductVersion 1.2.0 -ReleaseVersion 1.2.0 -Edition gg-forge

The result is artifacts/installer/<assetPrefix>-<version>-x64.msi, for example GgForgePrintAgent-1.2.0-x64.msi. Installed, it shows the client's name and mark on the splash, About, window title, tray tooltip, Start menu, Programs list and service; Task Manager and file properties show the client's product, company and description; and the in-app updater follows <assetPrefix>-v<version>-x64.msi on the release feed. CI builds every edition's unsigned installer, and the stable release workflow signs and publishes one MSI per edition beside the generic one, all listed in the same SHA256SUMS. An edition installer forces its client's labels over any labels an earlier install stored.

An edition's descriptor can also name the server as its users know it (serverName, used in sign-in and status text instead of QQQ) and leave the Query mascot and License sections out of About (aboutMascot, aboutLicense); the generic build keeps all of them. Editions share application id, data paths, executable file names, the install directory, the protocol handler, the MSI upgrade identity and the signing identity, so an edition installer upgrades the generic build and other editions in place and keeps the pairing and history. Deployment branding below still applies on top of an edition.

Deployment branding

Use the same signed stable machine MSI for every customer. No customer-specific build is required. The deployment wrapper stages an optional machine branding file and local assets, then supplies customer labels to Windows Installer. The companion, service display name, installed-product labels and all-users Start menu shortcut use that identity.

Install with a customer name

Copy and edit branding.example.json. The text-only example works without image files:

{
  "displayName": "Acme Print Connector",
  "companyName": "Acme Logistics",
  "supportUrl": "https://support.example.com/printing",
  "accentColor": "#0F766E"
}

Use PowerShell 7.4 or newer. First validate a downloaded MSI and configuration without making machine changes:

./build/Install-Branded.ps1 -MsiPath 'C:\Deployment\QqqPrintAgent.msi' -BrandingFile './docs/branding.example.json' -WhatIf

Run the same command without -WhatIf from an elevated administrator PowerShell to install. The script does not silently elevate or bypass UAC. Supply -ExpectedSHA256 '<64 hexadecimal digits>' when you have the expected MSI checksum from your approved release source. The wrapper verifies it before installation and keeps the source MSI read-locked through execution. A checksum does not substitute for publisher signing; verify the stable MSI signature as described in Installation. Customer branding does not change the Authenticode publisher or update feed.

The wrapper launches msiexec.exe with /i, /passive and /norestart, using separate arguments. It accepts success/reboot codes 0, 1641 and 3010 and reports the result. It creates %ProgramData%\QqqPrintAgent with SYSTEM/Administrators permissions when absent, or rejects unsafe existing permissions. It never broadens access to existing device state. It copies validated assets under unique names, replaces branding.json, and restores the previous configuration if Windows Installer fails. The client/service/enrollment remain one coherent MSI installation.

./build/Test-BrandedInstaller.ps1 exercises validation using synthetic files and -WhatIf only. It runs in CI after the normal verification gate; it does not install an MSI.

Fields and assets

Every field is optional. The wrapper rejects unknown fields and invalid supplied values before installation.

Field Behavior and limits
displayName Companion title/heading/tray tooltip/About, service display name, installed-product label and Start menu label; at most 180 characters
companyName Customer/company presentation and installed-product publisher label; at most 180 characters
supportUrl Explicit Contact support link; HTTPS without embedded credentials, at most 2,048 characters
accentColor AppKit splash/About background; #RRGGBB. Runtime uses it only if white text has at least 4.5:1 contrast; otherwise built-in colors remain
logoPath Optional PNG in the companion heading/About; maximum 2 MiB and 2,048 by 2,048 pixels
iconPath Optional ICO for the AppKit application icon and branded Start menu shortcut; maximum 2 MiB, 1–64 bounded image-directory entries

Names and other scalar values cannot contain control characters. JSON must be an object no larger than 16 KiB. Image paths may be local absolute paths or relative to the input branding file. Network paths, alternate data streams and reparse-point components are rejected by the wrapper. Local image files are copied into protected ProgramData; staged configuration uses their new relative filenames. Do not point managed deployments at user-writable asset locations. The runtime loader also limits image paths to 260 characters and checks PNG/ICO structure; malformed or inaccessible runtime settings fall back to built-in presentation.

To provide customer artwork, add "logoPath": "acme-logo.png" and/or "iconPath": "acme-icon.ico" beside the example fields and put those files beside the input JSON. An ICO is a separate Windows icon asset, not a renamed PNG. The PNG adds customer artwork to the window/About; it does not replace AppKit's bundled vector mark. Green/orange/red tray dots continue to communicate printing health. Embedded executable/MSI icons and binary resources remain those of the shared release.

Upgrade and rename behavior

The MSI persists DisplayName and CompanyName in HKLM\SOFTWARE\QRunIO\PrintAgent. Future installations of the ordinary shared MSI, including the in-app updater, recover these values when new BRANDNAME/BRANDCOMPANY properties are not supplied. ProgramData branding/assets and registry labels are retained through upgrades and full uninstall with device state. Removing branding.json therefore does not erase persisted customer labels.

The installer creates the companion shortcut under Start → Print Connectors. Unsafe filename characters are sanitized; truncated or sanitized names receive a stable hash suffix. Rename cleanup and full-uninstall cleanup only remove the tracked shortcut after verifying its exact installed target, arguments and ownership marker. Other shortcuts are left alone. An existing conflicting shortcut causes an explicit installation error rather than an overwrite.

For a customer rename, rerun the wrapper with revised JSON and the intended MSI. This updates service/product/shortcut labels together with runtime settings. To return to built-in labels, explicitly deploy displayName: "QQQ Print Agent" and companyName: "QRun-IO" through the wrapper. Omitting -BrandingFile preserves existing configuration. Software-management systems may instead supply BRANDNAME and BRANDCOMPANY as MSI public properties; JSON takes precedence for companion presentation, so keep both sources consistent.

Restart the companion to load changed presentation. Use Exit companion, then launch it again; closing the window merely hides it to the tray. Directly editing branding.json changes runtime presentation after restart, but does not itself rerun the installer to update Windows product/service/shortcut labels.

Fixed identities and attribution

Customer labels do not change the AppId, internal service name QqqPrintAgent, MSI upgrade identity, executable names, install directories, protocol identity or data paths. The installation remains under %ProgramFiles%\QQQ Print Agent and state under %ProgramData%\QqqPrintAgent. Source, license and third-party attribution remain available in About and the distribution. Put only presentation settings in branding JSON; enrollment codes and device credentials belong in protected service configuration.