Skip to content

Migrating from Inno Setup to Sigil

Accurate as of the feature-parity track's close (P0–P13, main). Every mapping below points at a shipped Sigil feature — nothing here is aspirational. Where Inno has no Sigil equivalent, that is called out explicitly as a deliberate non-goal rather than left implicit.

Inno Setup's .iss script is organized into [Section] blocks. Sigil's sigil.yaml is one declarative manifest: no sections, no execution order to reason about beyond install_steps:'s own list order (plus the lifecycle hooks and prerequisites that run around it — see below).

[Setup] → manifest top-level + installer:

Inno directive Sigil equivalent Notes
AppId app.id Reverse-DNS style recommended; same role as Inno's GUID — identifies the ARP entry and upgrade target across versions.
AppName app.name
AppVersion app.version Plain numeric dotted version (1.2.3) for precise upgrade/downgrade comparison; SemVer pre-release tags (1.2.0-rc1) are not parsed as pre-release and fall back to lexicographic comparison.
AppPublisher app.publisher Stamped into the ARP entry and the Authenticode signature's subject line.
DefaultDirName installer.install_dir Sigil's install-dir resolution chain (wizard-collected path → /D= → prior install dir on upgrade → installer.install_dir → scope-root default, InstallDirResolver.cs:15-23) supersedes Inno's {autopf}-style constant expansion; use the {scope_root} / {app.name} brace tokens inside the value, not a parameters.install_dir declaration — see the warning in Parameters.
DefaultGroupName shortcut_create step(s) with location: start_menu No separate "group" concept — declare one shortcut_create per shortcut you want in the Start Menu folder.
PrivilegesRequired (admin/lowest/none) installer.scope (machine/user/auto) Direct analog.
PrivilegesRequiredOverridesAllowed installer.scope: auto Sigil's auto scope resolves per-machine-vs-per-user the same way Inno 6's override mechanism does, without a separate flag.
OutputBaseFilename / OutputDir sigil pack --out <dir> Sigil names the artifact <App>-<version>-<arch>-Setup.exe deterministically; there is no free-form rename knob.
Compression / SolidCompression (automatic) Sigil always packs the payload with zstd (SIGIL_PAYLOAD_V2) for deterministic, reproducible builds — not user-tunable.
SetupMutex (automatic) Sigil's Setup.exe always takes its own single-instance mutex (gap G17, shipped with P6) — there is no manifest field to name or disable it, unlike Inno's opt-in SetupMutex. It admits exactly one process besides the first: the previous version's uninstall.exe that an upgrade spawns for its own teardown, and only when that child was really spawned by a live process naming this same application and scope (R76). A second Setup.exe you launch yourself is still refused, with exit code 5.
UninstallDisplayName (automatic, from app.name) The ARP row's DisplayName comes from app.name; there is no separate key.
UninstallDisplayIcon (no equivalent) Sigil writes no DisplayIcon ARP value. Windows falls back to whatever it derives from the executable named by UninstallString — which is uninstall.exe, a copy of Setup.exe, so it does carry installer.icon. But there is no field that sets the ARP icon directly.
MinVersion / OnlyBelowVersion when: os_version(...) on a step, or a prerequisites[] entry with a detect expression No dedicated OS-floor field yet; expression-gate the steps that need it.
Uninstallable=no no equivalent Every successful exe install writes uninstall.exe and the ARP entry, with or without an uninstall: block. There is no way to suppress either.

[Files]file_copy

Inno directive Sigil equivalent Notes
Source: "..."; DestDir: "{app}" file_copy step (from / to) from must start with the payload:// scheme to be rebased onto the extracted payload — a path that does not is resolved against the installer's working directory instead, which is almost never what you want. to is the destination, created if missing.
Flags: recursesubdirs from: payload://** ** recurses; *.ext (no **) is non-recursive, matching the guide's glob semantics.
Flags: onlyifdoesntexist file_copy with overwrite: false An existing file at to is left alone; its prior bytes are still journaled for rollback either way.
Flags: deleteafterinstall (no direct equivalent) Stage transient files under a hook (pre_install/post_install) with run_program, or omit them from the payload entirely.

[Icons]shortcut_create

Inno directive Sigil equivalent Notes
Name: "{group}\MyApp"; Filename: "{app}\MyApp.exe" shortcut_create (location: start_menu) name is the display name (.lnk appended automatically); target is the program path.
Name: "{commondesktop}\MyApp" shortcut_create (location: desktop)
Parameters: "..." args (list)
WorkingDir: "..." working_dir
IconFilename: "..." icon (.ico path, or exe,index)
Comment: "..." description Shortcut tooltip.
Tasks: desktopicon (gating a shortcut by a [Tasks] checkbox) when: option.<name> on the shortcut_create step See [Tasks] mapping below.

[Registry]registry_write / registry_delete_value / registry_delete_key

Inno directive Sigil equivalent Notes
Root: HKLM; Subkey: "..."; ValueType: string; ValueName: "..."; ValueData: "..." registry_write (hive, key, name, type_value, value) type_value accepts REG_SZ/REG_EXPAND_SZ/REG_DWORD/REG_QWORD/REG_MULTI_SZ/REG_BINARY.
Flags: uninsdeletevalue (automatic) registry_write snapshots the prior value and journals a delete; rollback and uninstall.exe both reverse it without a separate flag.
Flags: uninsdeletekeyifempty / uninsdeletekey registry_delete_key (recursive: true for the tree form) Declare the delete explicitly in uninstall: rather than relying on an implicit flag.
RegQueryStringValue / reading a value in [Code] registry_read(hive, key, value) expression function → installer.vars Declarative equivalent of Inno's [Code]-based registry read; exposed as var.<name> in when: clauses, screen defaults, and {var.<name>} brace tokens (gap G1, shipped P1).

[Run] postinstall → installer.run_after_install / hooks

Inno directive Sigil equivalent Notes
Filename: "{app}\MyApp.exe"; Flags: postinstall nowait skipifsilent installer.run_after_install (path, args) Drives the Done screen's checked-by-default "Launch \<App>" checkbox; a headless /silent /launch run launches it too. Always launched unelevated, de-elevated from an admin install token — Inno's postinstall entry runs at the installer's own privilege level, so this is a deliberate hardening, not a gap.
Filename: "..."; Flags: runhidden (silent post-install command) installer.hooks.post_install step list Ordinary steps (typically run_program) run outside the rollback journal, governed only by their own on_failure (default continue for post_install — the install is already committed).
Filename: "..."; Flags: skipifdoesntexist runasoriginaluser (pre-install command) installer.hooks.pre_install Default on_failure: fail — aborts before the journal opens, matching a failed pre-install [Run] entry blocking setup.
[UninstallRun] installer.hooks.pre_uninstall / post_uninstall Same warning applies: hooks have no rollback obligations.

[Tasks]installer.options.components

Inno directive Sigil equivalent Notes
Name: "desktopicon"; Description: "Create a &desktop icon" installer.options.components[] entry (name, label, default, locked) App-defined custom components (gap G11, shipped P10) — Sigil's direct analog of [Tasks]. Flat list, no hierarchy, rendered as checkboxes on the Options screen in declared order.
Tasks: desktopicon gate on a [Files]/[Icons]/[Run] entry when: option.<name> on the corresponding step A task generates no step of its own — it exists only as option.<name> in the expression engine, gating whichever steps reference it.
Flags: unchecked default: false
Flags: exclusive (radio-button task groups) (no direct equivalent) Model as a single parameters.<name>.type: enum screen field instead of mutually-exclusive tasks.

[Languages]installer.language + localization

Inno directive Sigil equivalent Notes
[Languages] section listing .isl files installer.language (fixed) or auto-detect Built-in wizard chrome (buttons, titles, CloseApps/DowngradeBlocked/Failed screens) comes from a compiled-in, source-generated string catalog — no .resx satellite assemblies (gap G10, shipped P9).
Custom [CustomMessages] strings LocalizedText fields (installer.screens[].title/subtitle, parameters.<name>.description, installer.license) Accepts a plain string (English) or a { en: ..., de: ... } map; a map without an en entry is a pack-time error (SIG0290).
/LANG= command-line override /lang Same language-preference chain as installer.language: manifest fixed value → /lang → OS list → en.

AppMutex + CloseApplicationsFilterinstaller.app_mutex + Restart Manager

Inno directive Sigil equivalent Notes
AppMutex installer.app_mutex (array of names) Direct analog (gap G7, shipped P6) — setup opens each named mutex before touching the install directory; a mutex that opens means the app is running. Use the exact name the app passes to CreateMutex, including any Global\ prefix.
CloseApplications / CloseApplicationsFilter (Restart Manager integration) (automatic, complements app_mutex) Sigil always sweeps the install directory with Windows Restart Manager, catching processes holding files open even when no mutex is declared — a superset of Inno's filter-based approach.
Wizard "these applications are using files that need to be updated" screen Close-applications wizard screen Offers Retry / close-for-me.
/CLOSEAPPLICATIONS silent switch /closeapps Without it, a blocked silent install exits with a dedicated nonzero code rather than failing on a locked file.

PrivilegesRequiredinstaller.scope

Already covered under [Setup] above — repeated here because it is one of the most consequential single-line ports: admininstaller.scope: machine, lowestinstaller.scope: user, and Inno 6's PrivilegesRequiredOverridesAllowedinstaller.scope: auto (dual-scope resolution, self-elevating as needed).

[Code] Pascal scripting → declarative equivalents (non-goal)

Inno's [Code] section is a full embedded Pascal Script runtime: InitializeSetup, CurStepChanged, custom wizard pages, arbitrary conditionals, arbitrary file/registry manipulation. Sigil deliberately has no embedded scripting language — the same design line at which WiX custom actions and NSIS plugins are declined. The declarative surface that replaces the ~90% of [Code] that actual Inno scripts use:

[Code] pattern Sigil equivalent
InitializeSetup / CurStepChanged(ssInstall) / CurUninstallStepChanged installer.hooks (pre_install/post_install/pre_uninstall/post_uninstall)
RegQueryStringValue, GetEnv, reading a file's version, reading the previously-installed version installer.vars + the expression functions registry_read, env, file_version, installed_version
if / case branches gating which files or registry entries get written when: expressions on the individual step (closed grammar: param.*, option.*, var.*, app.*, system.*, scope*, plus defined/empty/version_gte/os_version/arch/locale/file_exists/registry_exists)
Custom wizard pages (CreateInputQueryPage, etc.) installer.screens (declared screens over parameters, with per-parameter widget inference)
Arbitrary Exec/ShellExec calls run_program step, or an installer.hooks entry
Detecting/installing a prerequisite by hand (InitializeSetup + Exec) installer.prerequisites[] (detect → acquire → run → re-detect, gap G6)
Anything else procedural Not supported by design. A declarative, AOT-safe, auditable manifest is Sigil's differentiator; arbitrary code reintroduces the class of problems Sigil exists to remove. Use a signed run_program invoking a small helper executable for logic that genuinely cannot be expressed declaratively.

Scheduled tasks, COM registration, firewall rules (shipped P11)

Inno has no first-class support for any of these three — script authors universally reach for [Run] entries invoking the underlying command-line tool. Sigil ships typed, journaled steps instead (gaps G12–G14):

Inno idiom Sigil equivalent Notes
Filename: "schtasks.exe"; Parameters: "/Create /TN ... /TR ... /SC ..." scheduled_task_create Fields name, program, arguments, trigger (logon/daily/onstart), run_level. Always runs the task as SYSTEM; rollback runs schtasks /Delete /TN <name> /F.
Filename: "regsvr32.exe"; Parameters: "/s ""{app}\MyLib.dll""" com_register Invokes the DLL's exported DllRegisterServer/DllUnregisterServer directly via a statically-bound, AOT-safe unmanaged function pointer — no shelling out to regsvr32.
Filename: "netsh.exe"; Parameters: "advfirewall firewall add rule ..." firewall_rule Fields name, direction, action, program/port/protocol. Deletes any same-named rule before adding, so reinstalls stay idempotent.

All three require installer.scope: machine — they touch machine-global state, so packing a manifest that uses any of them under user or auto scope fails at pack time with SIG0310.

DownloadTemporaryFilehttp_download

Inno directive Sigil equivalent Notes
DownloadTemporaryFile / CreateDownloadPage (built in since Inno 6.1) http_download step (url, dest, sha256, timeout_seconds, retries) HTTPS only; sha256 is required — the packer refuses to pack a download step without one. Rollback deletes the downloaded file; transient failures (network/timeout/5xx) retry with backoff, a checksum mismatch fails immediately (gap G5, shipped P4). Whether a downloaded binary is Authenticode-checked before it is launched is governed by installer.require_signed_downloads; a downloaded prerequisite is always checked and has its own allow_unsigned opt-out.

Update checks → /Update

Inno itself ships no update engine — script authors hand-roll version checks in [Code] against a self-hosted URL, or reach for a third-party tool. Sigil ships one built in (gap G15, shipped P12):

Inno idiom Sigil equivalent Notes
[Code]-based "check a URL for a newer version" updates: manifest block + Setup.exe /Update Reads an ECDSA P-256-signed channel manifest (manifestUrl, detached .sig), compares versions against the installed ARP entry, and — if newer — downloads the full package (sha256-verified) and hands off to it. Dedicated exit codes (0 up to date, 6 not configured, 7 check/apply failed or a malformed manifest, 8 hard security reject, 9 below minFromVersion). Note 8 is not only "signature rejected": it also covers a freshness/replay refusal and an Authenticode refusal of the downloaded package. See Updates.
Hand-rolled channel-manifest JSON the fixed channel-manifest contract Your manifest must carry issuedAt, expiresAt and sequence alongside schemaVersion, version, packageUrl and sha256. They are inside the signed byte range and enforced by every shipped client (ADR-011); an older five-field document is rejected as malformed. Mint a fresh, re-signed manifest before expiresAt lapses, and increment sequence on every publish. See the contract.
[Code] check of "is this download signed?" installer.require_signed_downloads Decides whether the downloaded package is Authenticode-verified before it is run: sign_declared (default — armed only when the manifest has a sign: block), always, or always_verified_revocation. See Updates.
Hand-rolled "download and self-replace" web installer sigil pack --payload web --package-url <https-url> Emits a tiny stub Setup.exe (...-WebSetup.exe) whose only install action is an http_download of the full package, then a handoff to it (gap G16, shipped P12).
Delta/binary-patch updates Not implemented updates.deltaTargets is parsed and schema-validated but has zero runtime effect today — delta updates are explicitly deferred, not silently unsupported. See ADR-010. Every /Update today fetches the full package.

Concept differences

  • Imperative vs. declarative: Inno's [Code] section executes Pascal top-to-bottom with mutable global state and event callbacks. Sigil's install_steps: list is a pure description of intent; the only "state" that crosses steps is the explicit installer.vars mechanism.
  • Sections as organization, not scope: Inno's [Section] headers group unrelated concerns (files vs. registry vs. icons) that must all be kept in sync by hand when adding a feature. Sigil's install_steps: is one ordered list; a new feature is a handful of new step records plus one installer.options.components entry gating them.
  • No plugin DLL ecosystem: Inno's third-party .dll plugins (used for things like advanced firewall control before Sigil-equivalent built-ins existed) have no Sigil analog — the step catalog is closed and extended only in-repo (every new step amends the closed-catalog policy in ADR-008).
  • Rollback is transactional, not best-effort: Inno's uninstall log (Uninstall.exe) replays recorded actions but has no notion of a failed install rolling itself back mid-run. Sigil's RollbackJournal reverses every completed step the moment any step fails, before the wizard even reports failure — closer to an MSI transaction than Inno's uninstall-log model.
  • Wizard pages are inferred, not hand-assembled: Inno wizards are built page-by-page in [Code] (CreateInputQueryPage, etc.) or from [Types]/ [Components]. Sigil infers screens from parameters: (grouped by screen:) and renders the widget from the parameter's type/source — there is no page-construction API to call.

Uninstaller mapping

Inno construct Sigil equivalent Notes
Auto-generated unins000.exe uninstall.exe, written on every successful install Not packaged as a resource: at install time the wrapper copies its own running Setup.exe image, byte for byte, to install_dir\uninstall.exe. So it is the size of the whole Setup.exe and carries the same Authenticode signature.
ARP UninstallString (automatic) Points at the deployed uninstall.exe and carries /S /Uninstall plus the scope flag (/allusers or /currentuser), so the uninstall re-resolves to the scope it was installed with.
ARP QuietUninstallString (never written) Sigil writes no QuietUninstallString — it does not need one, because the UninstallString it writes already carries /S. The behaviour Inno gets from the pair is there; the second value is not.
[UninstallDelete] uninstall: step list The install journal already replays the reverse of every install step automatically; declare uninstall: only for extra tear-down the journal can't infer (stopping a service, clearing an AppData cache).
UninstallDisplayIcon (no equivalent) No DisplayIcon value is written; see the [Setup] table above. installer.icon does stamp Setup.exe, the wizard and therefore uninstall.exe, so the icon usually reaches ARP anyway — incidentally, not by declaration.

Examples

See the worked manifests under examples/ in the repo root — each is validated in CI against the current schema, so they stay accurate to shipped behavior by construction.