Skip to content

Add PSContentPath Standard Platform Paths - #1912

Merged
alerickson merged 11 commits into
PowerShell:masterfrom
jshigetomi:PSContentPath
Sep 9, 2026
Merged

Add PSContentPath Standard Platform Paths#1912
alerickson merged 11 commits into
PowerShell:masterfrom
jshigetomi:PSContentPath

Conversation

@jshigetomi

@jshigetomi Justin Chung (jshigetomi) commented Nov 19, 2025

Copy link
Copy Markdown
Contributor

PR Summary

This pull request makes PSResourceGet consume PowerShell's engine-resolved $PSUserContentPath automatic variable for CurrentUser content paths.

  • When $PSUserContentPath contains a usable string, PSResourceGet installs CurrentUser modules and scripts beneath that path.
  • When the variable is unavailable or empty, PSResourceGet preserves the existing platform-specific CurrentUser path.
  • AllUsers module and script locations remain unchanged.
  • PSResourceGet does not parse environment variables or powershell.config.json directly. PowerShell owns configuration precedence and environment-variable expansion, then exposes the resolved session value through $PSUserContentPath.

The tests cover the resolved-variable path, legacy fallback, hook reporting, explicit scope cleanup/lookups, and unchanged AllUsers behavior.

PR Context

This change is the PSResourceGet integration for the user-content-path work proposed in PowerShell RFC #388 and implemented in PowerShell PR #26509. It addresses the scenarios discussed in PowerShell issue #15552, including roaming profiles, containerized environments, and read-only file systems.

PowerShell resolves the configured user content path. PSResourceGet only consumes that resolved value, keeping policy and configuration parsing in the engine.

PR Checklist

Comment thread src/code/Utils.cs Outdated
Comment thread src/code/Utils.cs Outdated
Comment thread src/code/Utils.cs Outdated
@jshigetomi

Copy link
Copy Markdown
Contributor Author

Added support for Linux and MacOS pathway as well

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@alerickson

Copy link
Copy Markdown
Member

Opened PR to run CI tests here: #1913

@jshigetomi

Copy link
Copy Markdown
Contributor Author

Decided to use pwsh runspaces instead of reflection to call the PSContentPath API.
Talked to Travis Plunk (@TravisEz13) and found out that APIScan will require proof that APIs are public and not internal. So utilizing the new cmdlet Get-PSContentPath in a pwsh runspace to get the path from the config or default LOCALAppData location.

Comment thread src/code/Utils.cs Outdated
@alerickson

Copy link
Copy Markdown
Member

Tests are looking good, currently 3 failing tests related to Lucene index issues (ie these are flakey tests due to a server side issue with the Gallery). In terms of this PR all tests are passing!

@alerickson

Copy link
Copy Markdown
Member

All tests are passing now, PR looks good to me, just have one comment regarding whether to use default runspace created by the .Create() method or use current runspace.

Comment thread src/code/Utils.cs Outdated
if (psVersionObj != null) psVersion = new Version((int)psVersionObj.Major, (int)psVersionObj.Minor);
}
catch {
// Fallback if dynamic access fails

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need to write a verbose message or warning?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If $PSVersionTable doesn't work, what could the user do differently after knowing that this is where it breaks?
I'm not sure there is much they can do, but could be helpful knowing where it went wrong.
I will add a warning for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we add a debug message here, for dev debugging purposes?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there any cases where this variable won't be available?
I don't think so with it being an Automatic Variable - $PSVersionTable Automatic Variable Documentation

Comment thread src/code/Utils.cs Outdated
Comment thread src/code/Utils.cs Outdated
@alerickson

Copy link
Copy Markdown
Member

Justin Chung (@jshigetomi) we reviewed your PR, Anam Navied (@anamnavi) has a couple comments/suggestions. Everything looks good otherwise. Because we can't test these changes until 7.7 is released we're going to hold off on merging until then.

@jshigetomi

Copy link
Copy Markdown
Contributor Author

I added tests so that it skips some PSContentPath tests if it's not enabled.
I check for it with test hooks and the availability of Get-PSContentPath.

Comment thread src/code/Utils.cs Outdated
}

private readonly static Version PSVersion6 = new Version(6, 0);
private readonly static Version PSVersion7_7 = new Version(7, 7);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As mentioned in the PR in the PowerShell repo I & the community really do not want this dragging out until 7.7.0

We can get this in much sooner than that as I mentioned in that PR Justin Chung (@jshigetomi)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ryan Yates (@kilasuit) This feature is coming out in 7.7.0-preivew.1 which shouldn't be too far away from release.

Comment thread src/code/Utils.cs Outdated
if (psVersionObj != null) psVersion = new Version((int)psVersionObj.Major, (int)psVersionObj.Minor);
}
catch {
// Fallback if dynamic access fails

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there any cases where this variable won't be available?
I don't think so with it being an Automatic Variable - $PSVersionTable Automatic Variable Documentation

@jshigetomi

Copy link
Copy Markdown
Contributor Author

Changed implementation to use pwsh variable $PSUserContentPath instead of invoking the Get-PSContentPath cmdlet

Copilot AI lite review requested due to automatic review settings August 20, 2026 18:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds initial PSContentPath / PSUserContentPath support to PSResourceGet’s “standard platform paths” resolution so CurrentUser installs/searches can prefer the newer user-content base path when available, while retaining legacy and AllUsers behavior.

Changes:

  • Update Utils.GetPathsFromEnvVarAndScope() to base CurrentUser “Modules/Scripts” paths on a PSUserContentPath-derived base directory.
  • Add path-resolution logic in Utils.GetStandardPlatformPaths() that attempts to use $PSUserContentPath on PS 7.7+ and falls back to legacy locations.
  • Add end-to-end Pester coverage and introduce internal testing hooks to observe which base path was selected.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
test/PSContentPath.Tests.ps1 Adds Pester end-to-end tests validating install location behavior and hook outputs across feature availability scenarios.
src/code/Utils.cs Updates standard-path resolution to optionally use $PSUserContentPath (PS 7.7+) for CurrentUser base paths, with legacy fallback.
src/code/InternalHooks.cs Adds test hook fields/APIs for capturing the last user content path and its “source”.
Suppressed comments (2)

test/PSContentPath.Tests.ps1:106

  • InternalHooks sets LastUserContentPathSource to "$PSUserContentPath" (see Utils.cs), but this test asserts "Get-PSContentPath". If you want to validate the hook source, align the expected value with the implementation and avoid variable interpolation.
            # PSResourceGet should call Get-PSContentPath
            $pathSource | Should -Be "Get-PSContentPath"
            

test/PSContentPath.Tests.ps1:134

  • InternalHooks sets LastUserContentPathSource to "$PSUserContentPath" (see Utils.cs), but this test asserts "Get-PSContentPath". Align the expected source string with the implementation (and use single quotes to keep it literal).
            # PSResourceGet should call Get-PSContentPath
            $pathSource | Should -Be "Get-PSContentPath"
            

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

Comment thread test/PSContentPath.Tests.ps1 Outdated
Comment thread test/PSContentPath.Tests.ps1 Outdated
Comment thread test/PSContentPath.Tests.ps1 Outdated
Comment thread src/code/Utils.cs
Comment thread src/code/Utils.cs Outdated
Comment thread src/code/Utils.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ecfe8a78-5952-4edd-a25a-a840b33f7e71
@alerickson
alerickson self-requested a review September 9, 2026 22:57
@alerickson

Copy link
Copy Markdown
Member

Test were run in #2027 and are passing

@alerickson
alerickson merged commit 2d2a504 into PowerShell:master Sep 9, 2026
15 checks passed
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.

7 participants