Skip to content

fix(installer): give the NSIS hooks the macro names the bundler checks for - #460

Merged
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:fix/retire-dead-nsis-hooks
Aug 5, 2026
Merged

fix(installer): give the NSIS hooks the macro names the bundler checks for#460
PathGao merged 1 commit into
sftwrdotdev:masterfrom
PathGao:fix/retire-dead-nsis-hooks

Conversation

@PathGao

@PathGao PathGao commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

tauri.conf.json points bundle.windows.nsis.installerHooks at hooks.nsi, which
defines NSIS_HOOK_POST_INSTALL and NSIS_HOOK_POST_UNINSTALL. The bundler
guards its call sites with NSIS_HOOK_POSTINSTALL and
NSIS_HOOK_POSTUNINSTALL — no underscore:

installer.nsi @ tauri-cli-v2.9.6   (the version package-lock.json pins)
  617:  !ifmacrodef NSIS_HOOK_PREINSTALL
  709:  !ifmacrodef NSIS_HOOK_POSTINSTALL
  754:  !ifmacrodef NSIS_HOOK_PREUNINSTALL
  862:  !ifmacrodef NSIS_HOOK_POSTUNINSTALL

Same at tauri-cli-v2.0.0 and on dev — the underscored spelling has never
existed. !ifmacrodef is a compile-time conditional, so the mismatch produces no
error and no warning. The file has never run, from b89fc17 (2026-01-12) to
today. Nothing tested it.

Why the macros were not simply renamed

Renaming alone would have activated three behaviours that conflict with the
template. Each was checked against 2.9.6 rather than assumed:

hooks.nsi did template
create $DESKTOP\Markpad.lnk unconditionally finish-page checkbox (:386), auto only for silent/passive (:702), and CreateOrUpdateDesktopShortcut returns early on $UpdateMode (:945) removed
write Applications\Markpad.exe\shell\open\command not covered removed — see below
write Open with Markpad under .md\shell, SystemFileAssociations\.md\shell and OpenWithList APP_ASSOCIATE (:642) writes the verb under the ProgID only removed — see below
SHChangeNotify(SHCNE_ASSOCCHANGED) not covered; FileAssociation.nsh ships UPDATEFILEASSOC for it and installer.nsi includes the header but never inserts the macro kept

Every write in the file was hardcoded HKCU while the template writes through
SHCTX, which follows installMode: "both" — so an all-users install would have
put per-user keys in an elevated user's hive.

The shortcut row is the concrete one: tauri-plugin-updater 2.10.1 passes
/UPDATE, which is precisely why the template skips shortcut creation during an
update. The hook would have recreated a desktop shortcut the user had deleted, on
every auto-update.

Removing rows that never ran changes nothing at runtime; keeping one is the
behaviour change. SHChangeNotify is the only keeper — no registry writes, so the
hive question does not arise, idempotent, and it restores the intended use of a
header the template already includes.

A fix that never shipped

a5fda8e"Fix markdown preview wrapping and open-with registration",
2026-04-30 — added the Applications\ and OpenWithList registrations to fix a
real report. It went into a file that does not execute, so that fix has never
reached a user, and the open-with gap is probably still open.
Reviving it means
converting the hardcoded HKCU to SHCTX and deconflicting a second
Open with Markpad label against the template's own — Windows-only validation, so
it wants its own PR rather than a rider on this one. Flagging rather than dropping.

The guard

scripts/nsisInstallerHooks.test.ts: the configured hook file exists; every
NSIS_HOOK* macro it defines is one of the four the bundler checks; and while
installMode is not currentUser, no hook names a registry hive outright. Against
the file as it stood:

✖ installer hook macros are spelled the way the bundler checks for them
  hooks.nsi defines !macro NSIS_HOOK_POST_INSTALL, which no !ifmacrodef in the
  bundler template matches, so it is never inserted.
  Expected one of: NSIS_HOOK_PREINSTALL, NSIS_HOOK_POSTINSTALL,
  NSIS_HOOK_PREUNINSTALL, NSIS_HOOK_POSTUNINSTALL

✖ installer hooks write through the install-mode hive, not a hardcoded one
  + [ 'HKCU' × 12 ]   - []

The failure mode here was silence; a name check is the only thing that turns it
into a signal. If installerHooks is ever dropped entirely the tests no-op, so the
guard does not stand in the way of retiring the file later.

Two things found while checking, neither fixed here

<ProgID>_backup self-clobbers, in the template. FileAssociation.nsh:70-71
writes .md's current handler into Markdown File_backup unconditionally; on a
reinstall that value is already Markdown File, so the backup overwrites itself,
and APP_UNASSOCIATE then restores .md to a ProgID it deletes in the next line.
The restore is unconditional too, so uninstalling stomps a .md association a
different editor took over afterwards. This is #255/#256 again, in the NSIS path.
A NSIS_HOOK_PREINSTALL alone cannot fix it — it runs before APP_ASSOCIATE — so
it needs a PRE/POST pair, and arguably belongs upstream in FileAssociation.nsh.

The two install paths use different ProgIDs. NSIS writes Markdown File;
setup.rs writes Markpad.File with its own PreviousAssociations subkey. They
can clobber each other's .md default. Relevant to #395.

Verification

npm test        643 / 643
npm run check   0 errors
cargo test      154 / 154
npm audit       0 vulnerabilities

Not verifiable without Windows, and stated as inference rather than
measurement: that the shell actually refreshes on the SHChangeNotify call; that
NSIS compiles the file (there is no NSIS on macOS — the guard is a name check, not
a compile); that NSIS_HOOK_POSTINSTALL now genuinely fires; and whether the
removed Applications\/OpenWithList writes were producing value the template
misses. Worth a look on a real machine before the next release.

…s for

`src-tauri/hooks.nsi` defined `NSIS_HOOK_POST_INSTALL` and
`NSIS_HOOK_POST_UNINSTALL`. The bundler template guards its call sites with
`!ifmacrodef NSIS_HOOK_POSTINSTALL` and `!ifmacrodef NSIS_HOOK_POSTUNINSTALL`
(crates/tauri-bundler/src/bundle/windows/nsis/installer.nsi, lines 709 and 862
at tauri-cli-v2.9.6, which is what package-lock.json pins) — no underscore
between POST and INSTALL. `!ifmacrodef` is a compile-time conditional, so the
mismatch produced no error and no warning: the installer built, installed, and
skipped the hook. The names have been the ones spelled here since
tauri-cli-v2.0.0, and the file was written against Tauri v2 from the start, so
it has been inert for its whole life — b89fc17 on 2026-01-12 through today.

Renaming the macros alone would have activated four behaviours against a
template that has moved on, so each was checked against the pinned template
first:

- Desktop shortcut. Dropped. The template creates it from the finish-page
  checkbox (installer.nsi:386-388) and automatically for silent and passive
  installs (:702-707), skipping it when updating or when `/NS` was passed
  (:940-947), and removes it on uninstall only when it still targets our exe
  (:818-824). The hook created it unconditionally, so it would have forced one
  on a user who declined it and recreated a deleted one on every auto-update —
  and the updater is configured to run passive, so that path is live.

- `Applications\Markpad.exe\shell\open\command`, the `Open with Markpad` verbs
  under `.md\shell` and `SystemFileAssociations\.md\shell`, and `OpenWithList`.
  Dropped. The template's APP_ASSOCIATE (:642) already writes an
  `Open with Markpad` open verb and its command under the ProgID, so the two
  verbs above would have added a second and third context-menu entry with that
  same label. The `Applications` and `OpenWithList` keys have no counterpart in
  the template, but reviving them is a behaviour change, not a rename: every
  write here was hardcoded `HKCU` while the template writes through `SHCTX` to
  follow `installMode: "both"`, so an all-users install would have left
  per-user keys its own uninstaller cannot see. That belongs in its own change,
  validated on Windows.

- `SHChangeNotify(SHCNE_ASSOCCHANGED)`. Kept, and it is now the whole file. The
  template includes FileAssociation.nsh but never inserts that header's own
  UPDATEFILEASSOC, so nothing tells Explorer to re-read the association it just
  wrote and the old handler and icon can survive until the next logon. The call
  writes no registry keys, so the hive question does not arise, and it is
  idempotent.

`scripts/nsisInstallerHooks.test.ts` pins the failure mode rather than the
instance: a configured `installerHooks` must exist, must define at least one
entry point, every `NSIS_HOOK*` macro it defines must be one of the four the
bundler checks for, and — while `installMode` is not `currentUser` — it must
not name a registry hive outright. All three assertions fail against the file
as it stood.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao merged commit 7ff4b2e into sftwrdotdev:master Aug 5, 2026
4 checks passed
@PathGao
PathGao deleted the fix/retire-dead-nsis-hooks branch August 5, 2026 02:53
PathGao added a commit that referenced this pull request Aug 6, 2026
The review discipline here is real but undocumented: a mechanism section that
explains why the old behaviour happened rather than what was done about it, a
Scope section saying what was deliberately left alone, a falsification step on
fixes, and a Verification section with the exact commands, their counts and an
honest list of what was not checked. #468, #464, #462, #460 and #458 all have
that shape. Nobody arriving from outside can know it. #463 came close by
instinct, which is the argument for writing it down rather than hoping.

Five headings, prompts only, no checkboxes. A checkbox that feels mandatory is
a required field wearing a disguise, and friction is what makes a contributor
abandon a template rather than fill it in; the header says outright that every
section can be deleted. No licensing, conduct or "I read the guide" line --
there is no contributing guide to read.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant