Add PSContentPath Standard Platform Paths - #1912
Add PSContentPath Standard Platform Paths#1912Justin Chung (jshigetomi) wants to merge 10 commits into
Conversation
|
Added support for Linux and MacOS pathway as well |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Opened PR to run CI tests here: #1913 |
|
Decided to use pwsh runspaces instead of reflection to call the PSContentPath API. |
|
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! |
|
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. |
| if (psVersionObj != null) psVersion = new Version((int)psVersionObj.Major, (int)psVersionObj.Minor); | ||
| } | ||
| catch { | ||
| // Fallback if dynamic access fails |
There was a problem hiding this comment.
do we need to write a verbose message or warning?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Could we add a debug message here, for dev debugging purposes?
There was a problem hiding this comment.
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
|
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. |
|
I added tests so that it skips some PSContentPath tests if it's not enabled. |
| } | ||
|
|
||
| private readonly static Version PSVersion6 = new Version(6, 0); | ||
| private readonly static Version PSVersion7_7 = new Version(7, 7); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Ryan Yates (@kilasuit) This feature is coming out in 7.7.0-preivew.1 which shouldn't be too far away from release.
| if (psVersionObj != null) psVersion = new Version((int)psVersionObj.Major, (int)psVersionObj.Minor); | ||
| } | ||
| catch { | ||
| // Fallback if dynamic access fails |
There was a problem hiding this comment.
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
|
Changed implementation to use pwsh variable $PSUserContentPath instead of invoking the Get-PSContentPath cmdlet |
There was a problem hiding this comment.
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$PSUserContentPathon 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.
| $script:originalPSModulePath = $env:PSModulePath | ||
| $script:actualConfigPath = Join-Path $env:LOCALAPPDATA "PowerShell\powershell.config.json" | ||
| $script:configBackup = $null | ||
|
|
| # Clean up installed test modules | ||
| Uninstall-PSResource $testModuleName -Version "*" -SkipDependencyCheck -ErrorAction SilentlyContinue | ||
| # Clear testing hooks | ||
| [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::ClearPSContentPathHooks() |
|
|
||
| if ($script:getPSContentPathAvailable) { | ||
| # When Get-PSContentPath cmdlet exists, PSResourceGet should use it | ||
| $pathSource | Should -Be "Get-PSContentPath" |
| using System.Text.RegularExpressions; | ||
| using System.Threading; | ||
| using System.Text.Json; | ||
| using System.Threading.Tasks; |
| if (powerShellType == "WindowsPowerShell") | ||
| { | ||
| // Use legacy Documents folder for Windows PowerShell | ||
| localUserDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), powerShellType); | ||
| psCmdlet.WriteVerbose($"Using Windows PowerShell Documents folder: {localUserDir}"); | ||
| } |
| /// <summary> | ||
| /// Gets the user content directory path using PowerShell's $PSUserContentPath variable. | ||
| /// Falls back to legacy path if the variable is not available or PowerShell version is below 7.7.0. | ||
| /// </summary> | ||
| private static string GetUserContentPath(PSCmdlet psCmdlet, Version psVersion, string legacyPath) | ||
| { | ||
|
|
||
| // Only use PSContentPath features if PowerShell version is 7.7.0 or greater (when PSContentPath feature is available) | ||
| if (psVersion >= PSVersion7_7) | ||
| { |
PR Summary
This pull request introduces support for the PowerShell "PSContentPath" experimental feature, allowing users to customize the location of user content (such as modules and scripts) via an environment variable or configuration file. The code now prefers these new paths when the feature is enabled, improving flexibility and future compatibility.
Support for PSContentPath experimental feature:
powershell.config.json).PSUserContentPathin the environment variable or configuration file, using it as the base path for user content if it exists. If not set, it defaults toLocalApplicationData\PowerShell.MyDocuments\PowerShellfolder for user content.Refactoring and path resolution:
GetPathsFromEnvVarAndScopeto use the newpsUserContentPathvariable instead of the legacy documents path, ensuring the correct directory is used based on feature detection.Utility methods:
IsExperimentalFeatureEnabled(to check for enabled features in the config) andGetPSUserContentPath(to retrieve the custom user content path from environment or config).PR Context
This change introduces support for the PowerShell PSContentPath experimental feature, enabling users to customize where PowerShell stores user content (modules, scripts, etc.). Historically, PowerShell has relied on fixed paths such as MyDocuments\PowerShell, which limits flexibility in modern environments. With this update, users can define a custom content path via an environment variable or configuration file, allowing PowerShell to adapt to diverse deployment and security scenarios.
The feature is a direct response to long-standing community feedback (see PowerShell/PowerShell#15552) and aligns with related work in #1912. It was shaped through detailed specifications in PowerShell/PowerShell-RFC#388. This milestone lays the foundation for flexible, user-configurable content storage, addressing needs such as:
Roaming profiles
Containerized environments
Read-only file systems
By introducing this capability, we empower users to tailor PowerShell to modern deployment and compliance requirements.
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.