Skip to content

Keep desktop updates, rollback and first-run handoff on the right target - #784

Merged
Sev7eNup merged 3 commits into
mainfrom
fix/audit-desktop-setup
Nov 3, 2026
Merged

Sev7eNup merged 3 commits into
mainfrom
fix/audit-desktop-setup

Conversation

@Sev7eNup

@Sev7eNup Sev7eNup commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Second of three setup/switcher PRs from the 2026-10-28 audit. Stacked on #783; until that merges, the diff here also shows its commit. The change of its own is the last commit.

What changes

  • Custom instances survive updates (Preserve custom instance parameters during desktop updates #709): Update-Desktop.ps1 passes -DataPath, -ApiServiceName and -DbServiceName to re-provisioning, which used to fall back to the defaults.
  • Rollback brings the services back (Restore deleted desktop services when provisioning rolls back #710): before anything changes, the staged update snapshots both service registrations: binary path, account, start type, dependencies, failure actions, environment and key ACL. The rollback registers both services again before the database restore. It also re-applies each component's original ACL, because the rollback copies inherited the data directory's ACL and PostgreSQL could not start from them. A missing service now fails the update or the rollback explicitly.
  • First-run token goes to the setup user (Bind desktop bootstrap handoff to the original setup user #732): setup runs a check as the original user (ExecAsOriginalUser) to learn its SID and passes it as -HandoffUserSid. The provisioner resolves the profile for exactly that SID and fails when it is unknown or has no profile. The console user is no longer used, because under RDP or a switched user it is a different account. Update-Desktop.ps1 passes the account running the update. Setup run as SYSTEM or another service account by software distribution (Intune, SCCM) keeps main's behaviour: no shell runs as that account, so the console user gets the handoff, or the service account itself when nobody is signed in.
  • Repeatable uninstall, desktop part (Stop installer uninstallation when service cleanup fails #714): the desktop uninstaller aborts when Uninstall-Desktop.ps1 fails, cannot start, or is missing while a service is still registered, so NodePilot stays installed and the uninstall can be repeated.

Docs: deploy/desktop/README.md, docs/desktop-troubleshooting.md, the desktop deployment page (en/de) and the CHANGELOG.

Testing

Windows PowerShell 5.1, locally: Test-DeploymentTemplates.ps1, Test-DesktopUpdate.ps1, Test-DesktopUpdatePayload.ps1 and Test-DesktopDatabaseProvisioning.ps1 pass.

Lab (CLIENT1, signed 1.5.4-audit1 desktop setup of the same changes):

  • D04: an update payload without postgres.exe makes provisioning fail after both services are deleted. Both services come back identical, the database is restored and /healthz/ready answers 200. This run first failed on the ACL issue described above; the ACL restore is part of this PR.
  • D09: with service deletion denied, the uninstall exits 1 and keeps the services, the files, deploy\ and the Apps & features entry; the retry removes everything.
  • D27: setup started the provisioner with the SID of the signed-in account. The handoff landed only in that profile, readable only by SYSTEM and that account. An unknown SID stops provisioning with a message.

Not reproduced in the lab: RDP, a user switch and UAC with someone else's credentials, which need an interactive desktop. D03 is covered by the offline invocation test only. No CI job compiles NodePilot.iss; the lab build compiled it.

Closes #709
Closes #710
Closes #714
Closes #732

🤖 Generated with Claude Code

Sev7eNup and others added 2 commits November 3, 2026 21:31
- Setup health probe and first login pin the configured server
  certificate per request and reject redirects, instead of switching
  off certificate validation for the whole process (D01).
- A failed reinstall that changes the service account restores the
  install root DACL before the old binaries come back (#735).
- Uninstall stops when the service cannot be deleted, keeps the
  marker and deploy\ until everything else is gone, and the Inno
  uninstaller aborts on a failed cleanup so it can be repeated (#714,
  server part).
- PurgeData takes ownership with takeown /A item by item, so it works
  on German Windows, and deletes nothing when a native step fails
  (#731).
- The gMSA repair command reads the existing retrieval principals and
  adds this host; the deploy README drops the blanket RBCD recipe
  (#713).

Closes #713
Closes #731
Closes #735
Refs #714

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Update-Desktop.ps1 passes the custom data path and service names to
  re-provisioning instead of letting it fall back to defaults (#709).
- The staged update snapshots both service registrations and the
  component ACLs; the rollback registers the services again before the
  database restore and fails explicitly when one is missing (#710).
- Setup identifies the account that started it (ExecAsOriginalUser) and
  passes its SID as -HandoffUserSid; the provisioner no longer falls
  back to the console user or its own profile (#732).
- The desktop uninstaller aborts on a failed or missing cleanup, so the
  uninstall can be repeated (#714, desktop part).

Closes #709
Closes #710
Closes #714
Closes #732

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Software distribution (Intune, SCCM) runs the desktop setup as SYSTEM.
ExecAsOriginalUser then reports SYSTEM itself, so the handoff landed in
the system profile and the signed-in user never saw the setup page.
For SYSTEM, LocalService and NetworkService the provisioner now resolves
the console user as main did, or keeps the handoff with the service
account when nobody is signed in. Interactive setups still use the
account that started setup.

Refs #732

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Sev7eNup
Sev7eNup merged commit 69855e8 into main Nov 3, 2026
16 checks passed
@Sev7eNup
Sev7eNup deleted the fix/audit-desktop-setup branch November 3, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant