Skip to content

Replace GVFS.Service with logon task and file-based repo registry#2020

Draft
tyrielv wants to merge 3 commits into
microsoft:masterfrom
tyrielv:tyrielv/local-repo-registry
Draft

Replace GVFS.Service with logon task and file-based repo registry#2020
tyrielv wants to merge 3 commits into
microsoft:masterfrom
tyrielv:tyrielv/local-repo-registry

Conversation

@tyrielv

@tyrielv tyrielv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Background

One obstacle to automatically updating GVFS is that is requires administrator privileges to install and run.
Currently, GVFS.Service runs as a Windows Service under SYSTEM (high-privilege) account. That means every upgrade has to update the service, because it is a binary that changes in every release, so every upgrade needs administrator privileges.

The service itself currently performs these functions:

  • Maintains a registry of repositories that have been mounted before. The registry is updated by calls to gvfs mount and "gvfs unmount" by sending messages on a named pipe to the service from the user-initiated gvfs.exe calls.
  • Provides --list-mounted, --mount-all, and --unmount-all operations for the gvfs service verb. These don't change the registry, but read it and perform the given operation.
  • On logon, mounts all repos in the registry
  • When a repository tries to mount, if it determines that ProjFS is not enabled/attached to the volume then it sends a pipe message to the Service, and the service attaches ProjFS.
  • Supports the recently-added "PendingUpgrade" feature - if repos are mounted and /STAGEIFMOUNTED is provided, the installer places the new binaries in a pending folder inside the installation, upgrades only the service, and the service completes the install on next reboot or if all current mounts are unmounted.

Of these functions, only the ProjFS enable/attach and the PendingUpgrade feature require elevated privileges. PR #2028 replaces PendingUpgrade feature with simpler Current junction, which does not require GVFS.Service support - it's handled entirely in the installer.

Changes

This pull request removes GVFS.Service entirely, replacing it with:

  • LocalRepoRegistry: Kept at user-level instead of system level. Initially populated (first use by a user) by copying from system registry if a system registry from a prior version exists. Verbs that would call the service to update/access this now update/access it directly.
  • Logon task: machine-wide \GVFS\AutoMount fires for all interactive users at logon, runs gvfs service --mount-all (which will mount repos in that user's LocalRepoRegistry)
  • ProjFS boot task: SYSTEM task enables ProjFS + attaches PrjFlt on all volumes at boot. gvfs mount can also trigger it if needed, for example if a new volume was created during the current user session.

Part of Plan UAC-Free Install Modernization (Phase 2)

@tyrielv tyrielv force-pushed the tyrielv/local-repo-registry branch from 6714021 to 38798d2 Compare June 15, 2026 18:46
@tyrielv tyrielv force-pushed the tyrielv/local-repo-registry branch from 68f0326 to 141d39a Compare June 17, 2026 17:05
@tyrielv tyrielv changed the title User-level install infrastructure (PR-A): LocalRepoRegistry, verb fallbacks, logon task, ProjFS attach scripts Replace GVFS.Service with per-user logon task and file-based repo registry Jun 17, 2026
@tyrielv tyrielv changed the title Replace GVFS.Service with per-user logon task and file-based repo registry Replace GVFS.Service with logon task and file-based repo registry Jun 17, 2026
@tyrielv tyrielv force-pushed the tyrielv/local-repo-registry branch from b5ae764 to ac437f2 Compare June 17, 2026 23:48
Replaces flat {app}\ deployment with versioned Versions\<ver>\ + Current junction.
Eliminates PendingUpgrade staging flow — new version goes to new folder, junction
swaps atomically, mounts continue running from old version until unmounted.

Changes:
- [Files]: Deploy to {app}\Versions\{version}\ instead of {app}\
- [Registry]: PATH points to {app}\Current (junction) instead of {app}\
- [Dirs]: ProgramData under versioned folder
- [Code]: Add CreateOrUpdateCurrentJunction() — creates/updates junction post-install
- [Code]: Add GarbageCollectOldVersions() — keeps 1 most recent old version, deletes older
  (skips versions with running mounts detected via Get-Process gvfs.mount | .Path)
- [Code]: Remove KeepMountsRunning variable, IsNormalInstall/IsStagingInstall checks
- [Code]: Remove StagingUpdateService, ShowMountChoiceDialog (no longer needed)
- [Code]: Simplify PrepareToInstall — no mount detection, no staging, just stop service
- [Code]: Update InstallGVFSService to reference {app}\Current\GVFS.Service.exe
- [Code]: Update MountRepos, MigrateConfigAndStatusCacheFiles, WriteOnDiskVersion16CapableFile
  to use Current junction paths
- CurStepChanged: Remove staging logic, call CreateOrUpdateCurrentJunction + GarbageCollectOldVersions
- CurUninstallStepChanged: Remove {app}\Current from PATH instead of {app}

Flat-layout migration stub added (detects {app}\GVFS.exe, logs version) but defers
actual file move to future PR to reduce complexity.

Assisted-by: Claude Sonnet 4.5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/local-repo-registry branch from ac437f2 to ab7f221 Compare June 18, 2026 17:46
tyrielv added 2 commits June 18, 2026 11:03
Fixes 5 critical issues in the Phase 2 versioned-layout installer:

1. Forward reference violation - Move CreateOrUpdateCurrentJunction,
   GetFileVersion, IsProcessRunningFromPath, and GarbageCollectOldVersions
   before CurStepChanged. Inno Setup Pascal does not allow forward refs.

2. Service starts before junction exists - Create Current junction in
   CurStepChanged(ssInstall) BEFORE file extraction, ensuring the junction
   exists when InstallGVFSService runs (AfterInstall callback during file
   copy). Service binPath references {app}\Current\GVFS.Service.exe.

3. Old PATH entry not cleaned up - On upgrade from flat to versioned layout,
   remove legacy {app} PATH entry in CurStepChanged(ssPostInstall). New
   entry points to {app}\Current.

4. Non-atomic junction swap - Use Current.new temporary: create junction at
   Current.new, rmdir old Current (check ResultCode), rename Current.new to
   Current. Eliminates window where Current doesn't exist.

5. Add upgrade test scenarios - Add versioned-fresh-install,
   versioned-upgrade, and flat-to-versioned-upgrade scenarios to
   .github/workflows/upgrade-tests.yaml with full test implementations.

Assisted-by: Claude Sonnet 4.5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Replace the GVFS.Service Windows service with a simpler architecture:

Infrastructure:
- LocalRepoRegistry: file-based repo tracking, wire-compatible with old
  service format. SYSTEM uses ProgramData; per-user uses platform default.
  Seed-on-first-use copies accessible entries from system registry.
- LogonTaskRegistration: machine-wide \GVFS\AutoMount task fires for all
  interactive users (GroupId S-1-5-4) at logon, runs gvfs service --mount-all.
  Each user's repos loaded from their own LocalRepoRegistry.
- ProjFS boot task: enable-projfs-on-all-drives.ps1 enables ProjFS and
  attaches PrjFlt on all volumes. Embedded in task XML via build-task-xml.ps1
  with SHA-256 hash marker for drift detection.
- CLI verb fallbacks: mount/unmount/service verbs fall back to LocalRepoRegistry
  when the service named pipe is unavailable.
- GVFSVerb: silent-success fallback for PrjFlt FilterAttach.
- InProcessMount: restore exception safety net in HandleRequest.

Installer:
- Stop and delete GVFS.Service on upgrade from older versions.
- Register \GVFS\AutoMount logon task.
- Remove service deployment, install, start from [Files]/[Run].
- Remove PendingUpgrade staging logic and ShowMountChoiceDialog.
- Exclude GVFS.Service.exe from payload (layout.bat).

Functional tests:
- Remove service install/uninstall (no service to test against).
- Settings.cs auto-detects user-mode gvfs at %LocalAppData%\VFSForGit\Current.

926 unit tests pass.

Assisted-by: Claude Sonnet 4.5
Assisted-by: Claude Opus 4.6
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
@tyrielv tyrielv force-pushed the tyrielv/local-repo-registry branch from ab7f221 to adb620b Compare June 18, 2026 18:15
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