Ця сторінка описує Sigil 0.1.0-alpha.
Цю сторінку ще не перекладено українською, тому вона показана англійською.
Upgrades & downgrades¶
When you ship a new exe package for an app that is already installed, Sigil
compares the version being installed against the version already recorded in
Add/Remove Programs and picks one of four paths automatically — the equivalent
of WiX's MajorUpgrade plus a downgrade block, or the Inno/NSIS
"detect the old version and uninstall it first" idiom. There is nothing to
configure: the version comes from app.version, and the app identity from
app.id.
The four paths¶
At startup the installer reads the scope-correct ARP entry for app.id
(DisplayVersion, InstallLocation, UninstallString) and classifies the run:
| Installed vs. this build | What happens |
|---|---|
| Nothing installed | Fresh install. |
| Same version | Repair / reinstall — the recorded install is replayed in reverse, then re-applied in the same install directory (idempotent: no duplicate PATH entries, shortcuts, or ARP rows). |
| Older installed | Upgrade. The previous version's uninstall.exe /S /Uninstall is run first (and must exit 0), then the new version installs into the previous install directory so user data outside the install journal is preserved. |
| Newer installed | Downgrade — blocked. The wizard shows a notice screen; a silent install exits with code 3. Pass /force-downgrade to override. |
An upgrade over a running app is the other common non-zero outcome: files held
open by a running process abort the run with exit 4 unless /closeapps was
passed. See the setup.exe reference. The
installer.hooks.pre_install steps run before that check, so an app that ships its
own --quit can be stopped by a hook instead — see below.
The upgrade removes the old version by running its own uninstall.exe
(not the new build's uninstall logic), because that binary owns the previous
version's rollback journal and knows exactly how to reverse it.
Two things about that spawn are worth knowing before you debug one:
- The prior uninstaller is admitted through the single-instance lock explicitly.
It derives the same app+scope mutex name as the run that spawned it, so it would
otherwise refuse to start as "already running" and the whole upgrade would exit 5.
It is let through by a
SIGIL_SETUP_LOCK_HANDOFFenvironment token, validated against the real parent process id and that process's creation time — so only a genuine child of the running setup is admitted, not anything else that happens to know the token. - An unverifiable prior uninstaller is refused. When privilege is at stake — a
machine-scope install, or a process that is already elevated — the previous version's
uninstall.exeis only launched if it is Authenticode-valid or sits in a directory only administrators can write. Otherwise the run aborts before anything is installed withcannot upgrade: '…' is not verified. This is reachable in practice for an unsigned machine-scope install on a secondary volume such asD:\Apps\MyApp, whose default permissions grantUserswrite. See Uninstaller.
Scope¶
If the app was previously installed per-user, the upgrade stays per-user; if it
was per-machine, it stays per-machine. The existing install's scope wins over an
auto-resolved scope, so an upgrade always re-targets exactly what was installed.
An explicit /allusers / /currentuser, or a manifest that fixes scope:, is
still authoritative. If you force a different scope than the existing install
(e.g. /allusers over a per-user install), Sigil removes the old version in its
own scope and installs fresh into the new scope's default directory — the prior
directory is not carried across a scope change.
Install directory is preserved¶
An upgrade, a forced downgrade and a same-version reinstall install into the
prior location even when the build's default destination differs (a folder the
user picked at the first install, a changed installer.install_dir, a renamed
app, a different scope root). The wizard's location screen opens on that folder,
and a silent run uses it. Precedence for the destination is:
- the wizard-collected path, then
/D=<path>, then- the prior install directory (upgrade, forced downgrade or reinstall, same scope only), then
installer.install_dir, then- the default
<scope root>\<App.Name>.
Choosing a different folder — on the location screen or with /D= — moves the
app: the previous install is removed from the folder it was recorded in, and the
new one goes into the folder you chose. Files the install did not lay down
(settings or data the app wrote next to itself) stay in the old folder. An
upgrade and a reinstall behave the same way here.
Option choices are preserved¶
Every install records the resolved value of each installer.options component —
the built-ins and your components[] — with its uninstall state
(uninstall.json in %LOCALAPPDATA%\Sigil\<app id>\, or %ProgramData% for a
per-machine install). An upgrade, a forced downgrade or a same-version reinstall
of the same app reads it back, so a user who ticked Add to PATH at 1.0 sees
it ticked again on 1.1's Options screen, and a silent upgrade applies it again.
Precedence for each component is:
- a
lockedcomponent's manifestdefault— always, then - the wizard checkbox (interactive runs), then
/P<name>=//Poption.<name>=on the command line, then- the value the previous install recorded, then
- the component's manifest
default.
A component the new version no longer declares is ignored; one it adds starts at
its default. A custom component whose when made it inapplicable is not recorded,
so a forced-off value nobody chose never overrides the default later. The prior
version's teardown deletes its state, so the new version reads the recorded values
when the run starts — before that teardown — and writes its own once the install
completes. State written by a Sigil build that predates this has no recorded
options; that upgrade uses the manifest defaults, and records its own for next time.
Nothing is carried across a scope change, because each scope keeps its own state.
/force-downgrade¶
Setup.exe /S /force-downgrade
Installs an older version over an installed newer one. Without it, a silent
downgrade refuses to run and returns exit code 3 (distinct from 1 step
failure, 2 cancelled, and 64 usage error) so automation can detect the
block. With it, the newer version is removed first (exactly like an upgrade)
and the older version is installed.
Version comparison & pre-release tags¶
Version ordering uses .NET's numeric dotted-version comparison — the same
semantics as the version_gte(a, b) expression function. Only numeric dotted
forms with at least two components parse (1.2, 1.2.3, 1.2.3.4).
SemVer pre-release / build tags are not understood. A value like
1.2.0-rc1 or 1.2.0+build.7 is not parsed as "before 1.2.0"; it falls back
to a plain lexicographic comparison. Sigil does not implement SemVer
pre-release precedence.
For the upgrade decision this is deliberately conservative: whenever a numeric
ordering can't be proven, the run is treated as an upgrade, never a block.
An installed version that does not parse as a numeric dotted version is treated
as older than the incoming build (an upgrade, with a warning); an incoming
app.version that is a SemVer tag (or absent) likewise never blocks. Keep
app.version a plain numeric dotted version to get precise upgrade detection.
Interaction with pre_install hooks¶
Lifecycle pre_install hooks are the first thing an install runs, before
anything else touches the machine. The order is:
installer.hooks.pre_installsteps;- the files-in-use check (in the wizard, the Close-applications screen);
installer.prerequisites;- removal of the previous version (its own
uninstall.exe /S /Uninstall); - the journalled install body, then
post_installhooks.
So on an upgrade the hooks run while the previous version is still installed,
and {install_dir} — which an upgrade resolves to the previous install directory —
still holds its files. That is what makes the common teardown hook work:
installer:
hooks:
pre_install:
- id: quit-running
type: run_program
program: "{install_dir}\\myapp.exe"
args: ["--quit"]
timeout_seconds: 30
on_failure: continue
On a fresh install the program does not exist yet and the step fails; with
on_failure: continue that is logged and the install goes on. Leave it at the
default fail and a fresh install aborts.
A hook can also read the old files (configuration capture, for instance) — but it
runs outside the rollback journal, so nothing it does is undone if the install
fails later. The upgrade path itself performs no migration beyond removing the old
version and preserving the install directory; the data-retrieval expression
functions (registry_read, file_version, …) remain the way to read prior state
into a when or a var.
Not covered¶
- Side-by-side installs of multiple versions — Sigil keeps a single ARP row
per
app.id. - Delta / patch updates — upgrades install the full new package.