Skip to content

Uninstall PowerShell 7 modules from the scope they were installed in - #5494

Merged
Gabriel Dufresne (GabrielDuf) merged 1 commit into
mainfrom
fix/ps7-uninstall-scope
Oct 9, 2026
Merged

Gabriel Dufresne (GabrielDuf) merged 1 commit into
mainfrom
fix/ps7-uninstall-scope

Conversation

@GabrielDuf

@GabrielDuf Gabriel Dufresne (GabrielDuf) commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Root cause

Uninstall-PSResource was invoked without -Scope. In PSResourceGet the Scope parameter is a non-nullable ScopeType, so "unset" means CurrentUser and the cmdlet only searches the CurrentUser module paths. A module installed for AllUsers could never be uninstalled:

Cannot uninstall version '2026.2.4' of resource 'Devolutions.PowerShell' because it does not exist

This broke both a plain uninstall from the Installed page and the "Uninstall previous versions when updated" post-update step (the reported path).

Fix

  • Uninstall passes -Scope from the copy's detected scope (from the installed-packages listing). The options-dialog scope is deliberately not used: the scope selector is hidden for uninstall, and the post-update purge hands every old copy the update's options, so a dialog scope would point CurrentUser copies at AllUsers. With no detected scope it targets CurrentUser, matching the cmdlet's previous behaviour.

Follow-ups needed once uninstalls can actually reach AllUsers:

  • Post-update purge respects scope. Old copies were matched with IsEquivalentTo, which ignores scope, so updating one scope's copy would now genuinely delete older copies in the other scope, including machine-wide modules other accounts rely on. The purge now only queues copies in the scope the update installed into (the options-dialog scope when the manager honours one on update, else the detected scope). Copies or updates with no recorded scope still match everything, so managers that don't track scope are unchanged.
  • Elevated retry for AllUsers uninstalls. A non-elevated AllUsers uninstall fails with PSResourceGet's '<path>' could not be deleted record, which never matched the AdminPrivilegesAreRequired retry. Failures are classified from that message (hard-coded English in PSResourceGet, so not affected by localized .NET text):
    • version folder / script could not be deleted on an AllUsers copy → retry once elevated; on a CurrentUser copy → fail (elevation can't help);
    • only the empty module folder or the script metadata file was left behind → the version is already gone, so Success rather than an elevated retry that would fail with "does not exist".
  • No duplicate -Scope. Users who worked around this bug with -Scope AllUsers in custom arguments would otherwise get a parameter-binding error. -Scope is not injected when custom arguments already carry it, including PowerShell's accepted prefixes (-Sc, -Scop, -Scope:…).

Testing

  • Unit tests for the uninstall scope (detected / missing / empty), custom and abbreviated -Scope, the retry / leftover / CurrentUser result classification, and the scope-aware purge (same scope, dialog-chosen scope, scope-less update).
  • End to end with a dev build and real PSResourceGet (pwsh 7.6.6, PSResourceGet 1.2.0):
    • installed 2026.3.x build reproduces the report exactly (Uninstall-PSResource … -Version 1.0.4 with no -Scope, both post-operations fail);
    • fixed build: AllUsers 1.0.4 + 1.0.5 → update to 1.0.7 runs Uninstall-PSResource … -Scope AllUsers for both, leaving only 1.0.7;
    • with a CurrentUser 1.0.3 copy present, the CurrentUser update purges only 1.0.3 and leaves the AllUsers copies untouched.

Reviewer note: the elevated-retry and leftover-only paths are covered by unit tests only. The test machine's AllUsers module folder is writable by the test account, so a real access-denied failure couldn't be produced; the message format is taken from PSResourceGet's UninstallPSResource.cs.

Out of scope: IsEquivalentTo/GetHash still ignore scope everywhere else (ignored updates, installed-tag lookups). Making identity scope-aware touches persisted keys across every manager, so only the destructive consumer (the purge) is changed here.


Closes #5491

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Brokered PowerShell 7 operations bypass the new scope handling, leaving the reported failure unresolved when Agent Broker is enabled.

1 open finding
What changed in this PR

Fixes PowerShell 7 uninstall and post-update cleanup to respect installation scope.

Changes:

  • Adds detected-scope uninstall parameters and elevation retry handling.
  • Makes previous-version cleanup scope-aware.
  • Adds regression tests for scope and failure classification.
File Description
PowerShell7ManagerTests.cs Tests scoped uninstall behavior and retries.
PackageOperationsTests.cs Tests scope-aware version cleanup.
PackageOperations.cs Filters cleanup candidates by scope.
PowerShell7.cs Updates scope capability documentation.
PowerShell7PkgOperationHelper.cs Adds scope arguments and failure handling.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@GabrielDuf
Gabriel Dufresne (GabrielDuf) merged commit 846ad11 into main Oct 9, 2026
6 checks passed
@GabrielDuf
Gabriel Dufresne (GabrielDuf) deleted the fix/ps7-uninstall-scope branch October 9, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] PowerShell uninstallation of old module versions fails by not respecting scope

3 participants