Current release: v2.0.1

Build native-feeling installers from one YAML manifest.

PolyInstall is a cross-platform installer builder for teams that want predictable packaging without maintaining separate Windows, Linux, and macOS installer projects. Describe your application once, bundle the payload with Brotli compression, and produce self-contained installer binaries with a configurable Avalonia wizard UI.

2.0.1 Current stable release.
.NET 10 C# across the CLI, core library, runtime, UI, uninstaller, and schema generator.
Release zips Host-specific CLI archives with separate stub archives for cross-target builds.
Schema v1 Published JSON Schema keeps YAML manifests editor-friendly and validated.
1. Project overview

What PolyInstall does

PolyInstall turns a YAML manifest into one or more self-contained installer binaries. It is designed for product teams, open-source maintainers, and internal platform engineers who need a repeatable packaging flow without adopting a different installer authoring system for every operating system.

Current project state The repository is at v2.0.1, targets net10.0, uses Avalonia 12.0.4 for the installer UI, and runs CI restore/build/test with coverage on GitHub Actions. The release pipeline publishes self-contained CLI zips for win-x64, linux-x64, and osx-arm64; each CLI zip carries its host stub, while separate stubs-<rid> archives are available when you need to build installers for another target.

Single source of truth

Define metadata, files, build targets, UI steps, platform settings, and lifecycle tasks in one manifest.

Self-contained output

The CLI appends the compressed payload and embedded JSON manifest to a pre-published runtime stub.

Friendly install flow

Ship an Avalonia wizard with welcome, licence, destination, progress, and finish steps.

Value proposition PolyInstall removes the overhead of maintaining separate installer definitions by giving you a clear YAML contract, deterministic build commands, schema validation, bundled payload compression, and install-state tracking for reliable uninstall behaviour.
Project Role
PolyInstall.Core Shared manifest models, packaging logic, validation, and task/runtime primitives.
PolyInstall.Cli Build tool that validates manifests, gathers files, compresses payloads, and emits installers.
PolyInstall.Runtime The generated installer host that reads its appended bundle and launches the install flow.
PolyInstall.UI Avalonia wizard UI used by the runtime.
PolyInstall.Uninstall Dedicated Windows uninstall host used for Add/Remove Programs and command-line uninstall.
PolyInstall.SchemaGen Generates schema/v1.json from the C# manifest model.
2. Prerequisites

Before you install

The recommended path is to download a release build of the CLI. Release archives are self-contained, so users do not need the .NET SDK just to build installers from manifests.

For normal use

  • A 64-bit host OS that matches the downloaded CLI archive.
  • A YAML manifest describing your application.
  • A payload directory containing the files you want to install.
  • The included host RID stub, a downloaded stubs-<rid> archive, or a custom stubs/ directory.

For source builds

  • .NET SDK 10.x, aligned with the repository target framework.
  • Git for cloning and contributing.
  • mksquashfs from squashfs-tools for Linux AppImage output.
  • hdiutil on macOS for DMG output.
Target compatibility The finished installer must run on a machine that matches its target runtime identifier. For example, a windows-x64 installer is intended for 64-bit Windows, not Linux or macOS.
3. Installation guide

Install and configure PolyInstall

Use the release build when you want to package an application. Build from source only when contributing, testing unreleased changes, or publishing custom runtime stubs.

Download a release archive

Open the PolyInstall v2.0.1 release and download the archive that matches your build machine, such as polyinstall-win-x64-<tag>.zip, polyinstall-linux-x64-<tag>.zip, or polyinstall-osx-arm64-<tag>.zip. Each CLI archive includes the runtime stub for that same RID; download a separate stubs-<rid>-<tag>.zip archive when you need to build a different target from the same host.

# Example layout after extraction
polyinstall/
  polyinstall      # or polyinstall.exe on Windows
  schema/v1.json
  stubs/
    win-x64/       # host RID stub included with a win-x64 CLI archive

# Optional: extract separate stubs-linux-x64-<tag>.zip or stubs-osx-arm64-<tag>.zip
# under the same stubs/ root when building those targets from this host.

Add the CLI to your shell

Either run polyinstall from the extracted folder or add that folder to PATH.

# PowerShell example for the current session
$env:PATH = "C:\Tools\polyinstall;$env:PATH"

# Confirm the command is visible
polyinstall validate .\manifest.yaml

Connect your editor to the schema

Add the schema comment to the top of each manifest for completions and validation in YAML-aware editors.

# yaml-language-server: $schema=https://bolorundurowb.github.io/PolyInstall/schema/v1.json

Validate before building

Validation catches JSON Schema issues before packaging. The full build command also runs semantic checks such as relative file paths, wizard ordering, task compatibility, and signing settings.

polyinstall validate manifest.yaml --base ./release-input
polyinstall build manifest.yaml --base ./release-input

Build from source

Contributors can build the CLI and publish runtime stubs directly from the repository.

git clone https://github.com/bolorundurowb/PolyInstall.git
cd PolyInstall

dotnet restore src/PolyInstall.slnx
dotnet build src/PolyInstall.slnx -c Release --no-restore

# Publish a Windows runtime stub and uninstaller
dotnet publish src/PolyInstall.Runtime/PolyInstall.Runtime.csproj -c Release -r win-x64 -o stubs/win-x64
dotnet publish src/PolyInstall.Uninstall/PolyInstall.Uninstall.csproj -c Release -r win-x64 -o stubs/win-x64

# Build the sample installer
dotnet run --project src/PolyInstall.Cli/PolyInstall.Cli.csproj -- build examples/polyinstall.sample.yaml --base examples --stubs stubs
4. Quick start

Build your first installer

This example packages a tiny application directory and creates a Windows x64 installer. It is intentionally small, so you can validate the full build loop in minutes.

Create a payload directory

Put the files you want to install under a stable source directory. This example uses a single text file.

mkdir -p hello-payload
printf "Hello from PolyInstall\n" > hello-payload/hello.txt

Create manifest.yaml

The manifest declares metadata, output settings, UI steps, and which files to include.

# yaml-language-server: $schema=https://bolorundurowb.github.io/PolyInstall/schema/v1.json
metadata:
  name: HelloPoly
  version: 0.1.0
  publisher: Example Ltd

build:
  output_dir: dist
  compression: brotli
  targets:
    - windows-x64
  windows:
    install_scope: user
    register_arp: true

ui:
  theme: system
  wizard_steps:
    - type: welcome
      title: Welcome to HelloPoly
    - type: destination
      title: Choose an install location
      default_path: "{UserHome}/HelloPoly"
    - type: progress
      title: Installing HelloPoly
    - type: finish
      title: Installation complete

files:
  - source_dir: hello-payload
    include:
      - "**/*"

Validate and build

Run validation first, then build the installer. Output files are written under build.output_dir, relative to --base.

polyinstall validate manifest.yaml --base .
polyinstall build manifest.yaml --base .

Run the installer

Launch the generated binary on a matching target machine. The wizard lets the user choose the destination, copies the payload, records install state, and finishes with a summary.

# Windows output
dist/HelloPoly-windows-x64.exe
5. Advanced usage and features

Use the full manifest surface

PolyInstall supports multi-target builds, platform-specific packaging, wizard customisation, lifecycle tasks, glob filtering, custom output names, optional signing, environment variable substitution, and install-state tracking.

Multi-platform targets

A single manifest can build installers for Windows, Linux, and macOS. Platform options let you enable Add/Remove Programs registration, AppImage packaging, or DMG creation where the host supports it.

Wizard UI and branding

Configure the wizard sequence, select light, dark, or system theme mode, and provide a logo path resolved from the extracted payload at install time.

Lifecycle tasks

Run pre-install, post-install, pre-uninstall, and post-uninstall actions such as shortcuts, registry writes, desktop entries, permission changes, and PATH updates.

Reliable uninstall

Install state is written under .polyinstall/, allowing the Windows uninstaller to remove registered entries, installed files, and configured resources consistently.

Complete multi-platform manifest

This manifest demonstrates advanced build options, glob filtering, environment substitution, branding, and OS-gated tasks.

# yaml-language-server: $schema=https://bolorundurowb.github.io/PolyInstall/schema/v1.json
metadata:
  name: Contoso Studio
  version: "${APP_VERSION:-1.0.0}"
  id: com.contoso.studio
  publisher: Contoso Ltd

build:
  output_dir: dist
  output_name: "{name}-{version}-{target}"
  compression: brotli
  targets:
    - windows-x64
    - linux-x64
    - osx-arm64
  windows:
    install_scope: user
    register_arp: true
  linux:
    package: appimage
  macos:
    package: dmg
  signing:
    windows:
      certificate_path: "${WINDOWS_CERT_PATH}"
      certificate_password_env: WINDOWS_CERT_PASSWORD
      timestamp_url: "http://timestamp.digicert.com"
    macos:
      identity: "Developer ID Application: Contoso Ltd"
      keychain: "${MACOS_KEYCHAIN_PATH}"
      notarization_profile: "polyinstall-notary"

ui:
  theme: system
  logo_path: "branding/contoso-logo.svg"
  wizard_steps:
    - type: welcome
      title: Welcome to Contoso Studio
    - type: eula
      title: Licence agreement
      source: "legal/LICENCE.txt"
    - type: destination
      title: Choose where to install
      default_path: "{ProgramFiles}/Contoso Studio"
    - type: progress
      title: Installing
    - type: finish
      title: Ready to launch

files:
  - source_dir: app
    include:
      - "**/*"
    exclude:
      - "**/*.pdb"
      - "**/*.map"
      - "**/tmp/**"
  - source_dir: packaging
    include:
      - "branding/**"
      - "legal/**"

file_associations:
  - extension: ".cstudio"
    description: "Contoso Studio project"
    prog_id: "ContosoStudio.Project.1"
    icon: "{AppDir}\\branding\\contoso.ico"
    command: '"{AppDir}\\ContosoStudio.exe" "%1"'
    mime_type: "application/x-contoso-studio"
    bundle_path: "{AppDir}/Contoso Studio.app"

tasks:
  pre_install:
    - require: os.isWindows
      action: write_registry
      parameters:
        key_path: "HKCU\\Software\\Contoso\\Studio"
        value_name: InstallStarted
        value: "true"
        value_kind: string
  post_install:
    - require: os.isWindows
      action: create_shortcut
      parameters:
        target_path: "{AppDir}\\ContosoStudio.exe"
        name: "Contoso Studio"
        location: desktop
        description: "Launch Contoso Studio"
        icon_path: "{AppDir}\\branding\\contoso.ico"
    - require: os.isWindows
      action: add_to_path
      parameters:
        path: "{AppDir}"
        scope: user
    - require: os.isLinux
      action: create_desktop_entry
      parameters:
        file_name: "contoso-studio.desktop"
        name: "Contoso Studio"
        exec: "{AppDir}/ContosoStudio"
        icon: "{AppDir}/branding/contoso.png"
        comment: "Launch Contoso Studio"
    - require: os.isUnix
      action: set_permissions
      parameters:
        path: "{AppDir}/ContosoStudio"
        mode: 755
  pre_uninstall:
    - require: os.isWindows
      action: write_registry
      parameters:
        key_path: "HKCU\\Software\\Contoso\\Studio"
        value_name: UninstallStarted
        value: "true"
        value_kind: string

Shortcut and file association behaviour

Shortcut names are display names, not full destination paths. PolyInstall chooses the correct desktop or Start Menu directory from location and optional subfolder, then combines it with name. On Windows, .lnk is appended automatically when name does not already end with it; name: "Contoso Studio" and name: "Contoso Studio.lnk" both produce a single Contoso Studio.lnk file. Prefer omitting .lnk in manifests so the same task reads naturally on Linux and macOS.

tasks:
  post_install:
    - require: os.isWindows
      action: create_shortcut
      parameters:
        target_path: "{AppDir}\\ContosoStudio.exe"
        name: "Contoso Studio"        # Do not include .lnk unless you need to be explicit.
        location: start_menu          # start_menu or desktop
        subfolder: "Contoso"
        description: "Launch Contoso Studio"
        icon_path: "{AppDir}\\branding\\contoso.ico"
Do not package generated shortcuts Do not include .lnk, .desktop, or symlink shortcut files in your files payload just to place launchers on the user's machine. Include the real executable and icon assets, then create launchers with create_shortcut or create_desktop_entry. This keeps uninstall state accurate and lets PolyInstall place shortcuts in the correct per-user or machine-wide location.

File associations should normally be declared with top-level file_associations entries. Use a leading dot in extension, choose a stable prog_id that will not change between releases, include the icon file in your payload, and quote %1 in the open command so paths with spaces work correctly. If prog_id is omitted, PolyInstall derives one from the app name and extension, but explicit values are clearer for long-lived products.

file_associations:
  - extension: ".cstudio"
    description: "Contoso Studio project"
    prog_id: "ContosoStudio.Project.1"
    icon: "{AppDir}\\branding\\contoso.ico"
    command: '"{AppDir}\\ContosoStudio.exe" "%1"'
    mime_type: "application/x-contoso-studio"     # Linux; optional if the derived type is fine.
    bundle_path: "{AppDir}/Contoso Studio.app"    # macOS; required for macOS associations.

Use the file_association task action only when an association must be created conditionally in a lifecycle phase. Top-level associations are easier to read, participate in feature selection, and are registered after files are copied.

Custom stub RID note

Most users only need the manifest target tokens. The .NET runtime identifiers matter when you publish your own stubs, extract separate release stub archives, or set build.stub_path.

Manifest token Stub folder
windows-x64win-x64
windows-arm64win-arm64
linux-x64linux-x64
linux-arm64linux-arm64
osx-x64osx-x64
osx-arm64osx-arm64

Path placeholders

Placeholder Meaning
{AppDir}The selected install directory or extracted app directory.
{ProgramFiles}The OS-appropriate programme files location.
{UserHome}The current user's home directory.
{Desktop}The current user's desktop folder.

Platform packaging notes

Windows ARP

With register_arp: true, PolyInstall records install state, copies Uninstall.exe, and registers the app in Add/Remove Programs under HKCU or HKLM.

Linux AppImage

Set build.linux.package: appimage. AppImage creation must run on Linux with mksquashfs available on PATH.

macOS DMG

Set build.macos.package: dmg. DMG creation requires macOS because PolyInstall invokes hdiutil.

Optional installer signing

Signing is opt-in. If build.signing is omitted, PolyInstall produces unsigned installers. If you configure a platform signing block, the build validates that the required identity reference is present and signs only the matching platform artifacts. Do not put plaintext private keys or account passwords in YAML.

Windows signing

Windows uses signtool from PATH or tool_path. Configure exactly one identity source: certificate_path, certificate_thumbprint, or certificate_subject. Certificate passwords must come from certificate_password_env.

macOS signing and notarisation

macOS uses codesign with an identity. DMG builds can also use an Apple notarytool keychain profile through notarization_profile; PolyInstall submits and staples when that profile is configured. Linux signing is not built in.

build:
  signing:
    windows:
      certificate_thumbprint: "0123456789abcdef..."
      store_name: My
      store_location: current_user
      timestamp_url: "http://timestamp.digicert.com"
    macos:
      identity: "Developer ID Application: Example Corp"
      keychain: "${MACOS_KEYCHAIN_PATH}"
      notarization_profile: "polyinstall-notary"

What can be used together?

Feature Allowed with Not allowed or ignored Validation note
build.signing.windows windows-* targets, ARP registration, PFX files, or Windows certificate store references. Linux/macOS-only target lists. Requires exactly one identity source and rejects plaintext certificate_password.
build.signing.macos osx-* targets and optional build.macos.package: dmg. Windows/Linux-only target lists. identity is required. notarization_profile requires package: dmg.
build.signing.linux External signing workflows after PolyInstall finishes. Manifest-based Linux executable signing. Rejected by semantic validation because Linux has no single platform signing equivalent here.
build.linux.package: appimage linux-x64 or linux-arm64 targets on a Linux build host. Windows/macOS hosts for AppImage creation. Requires mksquashfs; raw Linux installer output is still created first.
build.macos.package: dmg osx-x64 or osx-arm64 targets on macOS. Windows/Linux hosts for DMG creation. Requires hdiutil. DMG signing/notarisation happens after packaging.
add_to_path task scope: user with any install scope; scope: machine with machine installs. scope: machine in a user-scope Windows install. Machine PATH changes require build.windows.install_scope: machine.

Environment variable substitution

During build and validate, string values support ${VAR} and ${VAR:-default}. This is useful in CI, but avoid embedding secrets because the generated manifest is stored inside the installer binary.

metadata:
  version: "${GITHUB_REF_NAME:-0.0.0-local}"

build:
  output_dir: "${DIST_DIR:-dist}"

Command reference

polyinstall build <manifest.yaml> [--base <dir>] [--stubs <dir>]
polyinstall validate <manifest.yaml> [--base <dir>]
Option Purpose
--base Working directory used to resolve file source directories and the default output directory.
--stubs Root folder containing per-RID runtime stubs. Omit it when your target matches the included host stub; pass it when using separate stub archives or custom stubs.
6. GitHub Actions

Build installers in CI

Use GitHub Actions to validate manifests on pull requests and build release installers from the same YAML file you use locally. The project includes a reusable setup action that downloads a PolyInstall release, verifies its checksum, extracts it, and adds polyinstall to PATH.

Choose a runner that matches the output you need The release CLI archive includes the stub for its own RID. A Linux runner is the easiest path for linux-x64 output and AppImage packaging, a Windows runner for windows-x64, and a macOS ARM64 runner for osx-arm64 and DMG packaging. Cross-target raw installer builds are possible when you also provide the matching stubs-<rid> archive or custom stubs directory.
Public release downloads Keep github-token: "" when downloading the public PolyInstall release from another repository. Set a token only when you point the action at a private release repository or mirror.

Validate manifests on pull requests

A lightweight validation workflow catches schema mistakes, missing payload paths, invalid shortcut parameters, and incompatible task/platform settings before a release job tries to package artifacts.

name: Validate installer manifest

on:
  pull_request:
    paths:
      - "installer/**"
      - ".github/workflows/installer.yml"

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: bolorundurowb/PolyInstall/.github/actions/setup-polyinstall@v2.0.1
        with:
          version: v2.0.1
          github-token: ""

      - name: Validate manifest
        run: polyinstall validate installer/manifest.yaml --base installer

Build and upload installers

Use a matrix when you want separate artifacts per target. The example below sets APP_VERSION from the tag name so manifests can use ${APP_VERSION:-0.0.0-local} without hard-coding release versions. For per-target matrix builds, set build.targets in the manifest to a substituted value such as "${POLYINSTALL_TARGET:-windows-x64}".

name: Build installers

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - runner: windows-latest
            rid: win-x64
            target: windows-x64
            artifact: installer-windows-x64
          - runner: ubuntu-latest
            rid: linux-x64
            target: linux-x64
            artifact: installer-linux-x64
          - runner: macos-14
            rid: osx-arm64
            target: osx-arm64
            artifact: installer-osx-arm64

    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v4

      - uses: bolorundurowb/PolyInstall/.github/actions/setup-polyinstall@v2.0.1
        with:
          version: v2.0.1
          rid: ${{ matrix.rid }}
          github-token: ""

      - name: Validate manifest
        env:
          POLYINSTALL_TARGET: ${{ matrix.target }}
        run: polyinstall validate installer/manifest.yaml --base installer

      - name: Build installer
        env:
          APP_VERSION: ${{ github.ref_name }}
          POLYINSTALL_TARGET: ${{ matrix.target }}
        run: polyinstall build installer/manifest.yaml --base installer

      - name: Upload installer artifacts
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact }}
          path: installer/dist/**

Use secrets carefully

Store signing passwords and keychain material in GitHub secrets, then reference them through environment variables such as WINDOWS_CERT_PASSWORD. Do not substitute runtime secrets into the manifest because the resolved manifest is embedded in the installer.

Keep output predictable

Set build.output_name to a template such as {name}-{version}-{target} so artifact upload patterns and release automation do not depend on display-name formatting.

7. Schema reference

Manifest options in detail

PolyInstall manifests are YAML documents that validate against the published schema at https://bolorundurowb.github.io/PolyInstall/schema/v1.json. Think of a manifest as the recipe for your installer: it names the product, chooses where output files go, describes the installer screens, selects the files to copy, and optionally runs setup tasks.

Recommended schema header Add this comment to the top of a manifest to enable YAML language server completion and validation.
# yaml-language-server: $schema=https://bolorundurowb.github.io/PolyInstall/schema/v1.json

Start small

For a first installer, set metadata, build, ui, and files. Leave tasks empty until you need shortcuts, registry values, desktop entries, or permissions.

Use schema keys

Manifest keys use snake_case. Let your editor load the published schema so typos such as outputDir instead of output_dir are visible while you write the manifest.

Paths are relative

Most file paths are resolved from --base or from the extracted payload. If a file is not found, check the command's --base value before changing the manifest.

Validation model The published schema catches field names, basic shapes, and allowed enums in YAML-aware editors. The validate command checks the manifest after YAML parsing and environment substitution. The build command also runs semantic validation: target names, compression, relative file source directories, non-empty includes, destination-before-progress wizard order, task/platform compatibility, machine-scope PATH rules, and signing combinations.

Root object

The root object groups product metadata, build settings, UI flow, payload selection, optional integrations, and lifecycle behaviour. Use these links when you see a field in a sample manifest and want to understand what it controls.

Option Type What it holds Beginner guidance
metadata object Product identity used by output naming, UI labels, registry metadata, and user-facing installer text. Always set name and version. Add id once you need stable updates/uninstall identity.
build object Controls output location, compression, target platforms, stubs, and platform-specific packaging. Start with one target, then add more targets after the first installer works.
ui object Configures the wizard theme, branding assets, and ordered installer pages. Include welcome, destination, progress, and finish for a normal install wizard.
files array Defines one or more payload file groups using source directories and glob patterns. This is what actually gets copied to the user's install folder. Start with "**/*".
file_associations array or null Registers document extensions with the installed app after files are copied. Use for open-with behaviour. Prefer this over ad hoc registry tasks for normal file associations.
tasks object or null Optional lifecycle actions that run before or after install and uninstall phases. Use only when copying files is not enough, for example creating a desktop shortcut.
features array or null Optional user-selectable feature list used by files, tasks, and file_associations. Add a features wizard step when users should choose optional components.
services array or null Optional background service or daemon registrations. Always include an OS require predicate so each service uses the right platform manager.

metadata

metadata describes the product you are packaging. These values appear in generated filenames, installer text, and platform integration such as Windows Add/Remove Programs.

Option Type What to put here Example
metadata.name string The human-readable product name. PolyInstall also uses it when deriving output filenames. Contoso Studio
metadata.version string The version you are shipping. Semantic versions are easiest to reason about during updates. 1.2.0
metadata.id string or null A stable identifier that should not change between releases of the same product. com.contoso.studio
metadata.publisher string or null The company, person, or project that publishes the app. Contoso Ltd

build

build controls what PolyInstall creates. This is where you choose output folders, compression, target operating systems, and optional native packaging formats.

Option Type What it does How to choose
build.output_dir string Directory where generated installers are written, resolved relative to --base. Use dist unless your release pipeline expects another folder.
build.output_name string or null Optional output filename template. Supports {name}, {version}, and {target}. Use it when your release pipeline needs predictable names such as {name}-{version}-{target}.
build.compression string Compression format for the zip payload appended to the installer stub. Use brotli for smaller output. Use gzip only if you need that format for compatibility.
build.targets array of strings Installer targets to produce, such as windows-x64, linux-x64, or osx-arm64. Start with the OS you can test locally. Add more once the manifest works.
build.installer_target string or null Optional target hint used by the runtime when it needs one specific installer target context. Most users can leave this empty; the CLI sets target context while building each output.
build.stub_path string or null Advanced override for locating runtime stubs. Supports {rid} when you manage custom stub folders. Leave empty when using release zips and their stubs/ layout.
build.windows object or null Windows-specific install and Add/Remove Programs options. Use it when building Windows installers, especially if you want ARP registration.
build.linux object or null Linux-specific packaging options. Use it when you want an AppImage in addition to the raw Linux installer.
build.macos object or null macOS-specific packaging options. Use it when you want a DMG in addition to the raw macOS installer.
build.signing object or null Optional Windows and macOS signing configuration. Leave empty for unsigned installers. Configure only the platform blocks you intend to sign.

Windows options

install_scope: user installs per user and writes registration under HKCU. Use it for most desktop apps because it avoids administrator prompts.

install_scope: machine writes under HKLM and requires elevation. Use it only when the app must be available to every user on the machine.

register_arp: true adds the app to Add/Remove Programs and installs the dedicated Uninstall.exe.

Linux options

package: none creates the raw Linux installer binary. package: appimage also wraps it as an AppImage for a more familiar Linux distribution format.

AppImage output must run on Linux with mksquashfs installed.

macOS options

package: none creates the raw macOS installer binary. package: dmg also creates a compressed DMG beside it.

DMG output must run on macOS because PolyInstall invokes hdiutil.

build.signing

build.signing is optional. It is validated only when present, so unsigned installers require no signing configuration. Platform signing blocks should reference existing certificate stores, files, keychains, or CI secrets rather than storing credentials in the manifest.

Block Important fields Allowed values and combinations
build.signing.windows tool_path, one of certificate_path, certificate_thumbprint, or certificate_subject, optional store_name, store_location, certificate_password_env, timestamp_url, digest algorithms. Use with windows-* targets. store_location is current_user or local_machine. Digest algorithms are sha1, sha256, sha384, or sha512.
build.signing.macos identity, optional codesign_path, xcrun_path, keychain, timestamp, options, notarization_profile, staple. Use with osx-* targets. Notarisation requires build.macos.package: dmg because the DMG is what gets submitted and stapled.
build.signing.linux No supported fields. Do not configure this block. Use external detached signing or checksums after the build if your Linux release process requires them.

ui

ui controls the install wizard the user sees. It is about presentation and flow, not which files are packaged. File selection happens in files.

Option Type What it does Beginner guidance
ui.theme string Controls the visual theme of the installer wizard. system follows the user's OS preference. light and dark force one mode.
ui.logo_path string or null Optional path to an SVG or raster logo shown in the wizard header. The logo file must be included in your payload through files.
ui.assets array or null Optional named assets with id and path, resolved from the extracted payload. Useful for packaging icons and branding. AppImage packaging can use the first PNG asset as the application icon.
ui.wizard_steps array Ordered pages shown by the installer wizard. Include a progress step. Without it, the normal file-copy phase is not part of the wizard flow.

wizard_steps entries

Each wizard step is one screen. The order in the manifest is the order the user sees.

Step type What the user sees Important fields When to use it
welcome An opening screen that introduces the installer. title Use as the first step for a friendly start.
eula A licence or terms screen loaded from a text file. title, source Use when users must read or accept licence text before install.
destination A folder picker for the install location. title, default_path Use when the user should choose where the app is installed.
features A full/custom install screen with checkboxes for optional features. title Use when the manifest defines top-level features. It is skipped when no features exist.
progress A progress screen while tasks run and files are copied. title Use in most installers. This is the normal place where installation work happens.
finish A final confirmation screen. title Use as the last step to show that installation completed.

files entries

files tells PolyInstall what to place inside the installer. Each entry starts at one source directory, includes matching files, and then removes anything matched by exclude.

Option Type What it does Example
source_dir string Root directory to scan, resolved relative to --base. app or publish/win-x64
include array of strings Glob patterns to include in the payload. "**/*" includes everything under source_dir.
exclude array or null Optional glob patterns removed from the included set. Use "**/*.pdb", "**/*.map", or "**/tmp/**" to skip files you do not want to ship.
features array or null Optional feature ids that gate the matched files. Omit for core files that are always installed. Set only when users can choose optional components.
File path mental model If you run polyinstall build manifest.yaml --base ./release-input and set source_dir: app, PolyInstall scans ./release-input/app. Files are stored in the payload relative to that source directory.

file_associations entries

Top-level file_associations entries register document extensions after installation. They are easier to maintain than platform-specific registry or desktop-database tasks, and they can be gated by optional features in the same way as file groups and tasks.

Option Type What it does Best practice
extension string Document extension to register. Include the leading dot, for example .cstudio.
description string User-facing file type description. Use a readable name such as Contoso Studio project.
prog_id string or null Stable programmatic identifier for the association. Set an explicit value such as ContosoStudio.Project.1 for shipped apps.
icon string or null Icon file for the associated document type. Include the icon in your payload and reference it with {AppDir} or a relative path.
command string Command used when the OS opens a matching file. Quote both the executable path and %1, for example "{AppDir}\App.exe" "%1".
mime_type string or null Linux MIME type. Optional on Linux. If omitted, PolyInstall derives application/x-<extension>.
bundle_path string or null macOS app bundle to update for document support. Required for macOS associations, for example {AppDir}/Contoso.app.
features array or null Optional feature ids that gate the association. Use only when the associated executable or plugin is optional.
file_associations:
  - extension: ".cstudio"
    description: "Contoso Studio project"
    prog_id: "ContosoStudio.Project.1"
    icon: "{AppDir}\\branding\\contoso.ico"
    command: '"{AppDir}\\ContosoStudio.exe" "%1"'

tasks

Lifecycle task arrays are optional. Install tasks run around file copy. Uninstall tasks run while installed files are still present, so they can stop services, remove shortcuts, or clean configuration before the install tree is deleted. A complete novice can ignore tasks until the installer needs to change the system outside the install folder.

Option When it runs Common use
tasks.pre_install After the user confirms the destination and before files are copied. Check prerequisites or prepare a folder before files are installed.
tasks.post_install After files have been copied to the install directory. Create shortcuts, write registry values, create desktop entries, or set permissions.
tasks.pre_uninstall At the start of uninstall, before registration and files are removed. Stop services or close helper processes while installed files still exist.
tasks.post_uninstall After pre_uninstall, but still before files and Add/Remove Programs entries are removed. Run cleanup that must happen after earlier uninstall tasks but before the install tree is deleted.

InstallTask entries

Option Type What it does Beginner guidance
require string or null Optional OS predicate. Supported values include os.isWindows, os.isLinux, os.isOSX, os.isMacOS, and os.isUnix. Use it for platform-specific tasks. Omit it when a task should run on every OS.
action string Task action to run. Supported actions are create_shortcut, write_registry, create_desktop_entry, set_permissions, add_to_path, and file_association. Choose the action first, then fill parameters with the keys that action expects.
parameters object or null Action-specific key/value map. String values can use path placeholders such as {AppDir}, {Desktop}, and {ProgramFiles}. Parameter names are snake_case. The required keys depend on the selected action.
features array or null Optional feature ids that gate this task. Omit for tasks that always run. Set only when the task belongs to an optional feature.

Task action parameters

Action Platforms Typical parameters What it is for
create_shortcut Windows, Linux, macOS target_path, name, location (desktop or start_menu), optional subfolder, description, optional icon_path. Create a user-facing launcher, such as a desktop shortcut.
write_registry Windows only key_path, value_name, value, and value_kind. Write app metadata or settings to the Windows Registry.
create_desktop_entry Linux, macOS file_name, name, exec, optional icon, optional comment. Create a Freedesktop-style app launcher entry.
set_permissions Unix platforms path and mode. Make a copied file executable or adjust Unix permissions after install.
add_to_path Windows, Linux, macOS Optional path (defaults to install directory), optional scope (user or machine). Add an installed CLI or tool directory to PATH. Machine scope requires build.windows.install_scope: machine.
file_association Windows, Linux, macOS extension, description, command, optional prog_id, icon, mime_type, and macOS bundle_path. Register or unregister an association during a lifecycle phase. Prefer top-level file_associations for normal app associations.
Placeholders you will use often {AppDir} means the chosen install directory, {ProgramFiles} means the OS program files location, {UserHome} means the current user's home folder, and {Desktop} means the current user's desktop folder.
8. Troubleshooting and FAQ

Common issues

Most build failures come from missing stubs, schema mismatches, host-specific packaging tools, or file globs that do not match the intended payload directory.

The build says the stub cannot be found.

Confirm that the resolved stubs root contains stubs/<rid>/PolyInstall.Runtime or PolyInstall.Runtime.exe. Windows targets also need stubs/<rid>/PolyInstall.Uninstall.exe unless you provide a custom layout.

My manifest fails schema validation.

Use polyinstall validate manifest.yaml and check that keys use snake_case. Add the yaml-language-server schema comment so your editor catches issues before the CLI runs. If polyinstall build fails after schema validation passes, read the semantic validation message: it usually points to incompatible task/platform settings, file path rules, wizard ordering, or signing configuration.

No files are included in the installer.

Check files[].source_dir relative to --base, then review include and exclude glob patterns. Start with "**/*" and narrow the match once the build works.

AppImage or DMG output is missing.

AppImage output requires a Linux host with mksquashfs. DMG output requires macOS with hdiutil. Cross-building the raw installer binary is separate from host-native packaging.

Windows shortcuts or registry tasks fail.

Check paths after placeholder expansion, confirm the install has sufficient permissions, and remember that install_scope: machine writes to HKLM and requires elevation. For shortcuts, use the current parameters target_path, name, and location; older shortcut_path examples are no longer accepted.

Signing is configured but the build fails.

Signing runs only when build.signing.windows or build.signing.macos is present. Windows needs signtool and exactly one identity source. macOS needs codesign and an identity. Notarisation requires a DMG package and a notarytool keychain profile. Linux signing is not supported in the manifest.

Can I combine machine install scope with PATH changes?

Yes, but only deliberately. add_to_path defaults to scope: user. If you set scope: machine, also set build.windows.install_scope: machine and expect the Windows installer to require elevation.

Can I put secrets in environment variables?

Do not use PolyInstall manifests for runtime secrets. Environment substitution happens before the manifest is embedded in the installer, so substituted values can be inspected from the final binary.

9. Contributing and support

Help improve PolyInstall

PolyInstall welcomes focused issues, documentation improvements, tests, and implementation changes that keep the manifest contract, generated schema, CLI behaviour, and runtime behaviour aligned.

Report an issue

Open a GitHub issue with the PolyInstall version, host OS, target OS, manifest excerpt, command output, and the smallest payload structure that reproduces the problem.

Open a pull request

Create a branch from master, keep the change focused, add tests for behavioural changes, and update docs, examples, or schema files when the manifest surface changes.

Contributor workflow

git clone https://github.com/bolorundurowb/PolyInstall.git
cd PolyInstall

dotnet restore src/PolyInstall.slnx
dotnet build src/PolyInstall.slnx -c Release --no-restore
dotnet test src/PolyInstall.slnx -c Release --no-build --verbosity normal
When changing manifest models Regenerate schema/v1.json with dotnet run --project src/PolyInstall.SchemaGen/PolyInstall.SchemaGen.csproj and commit the schema update with the related code.