Skip to content

Add PSModulePath override to InitialSessionState.#27126

Open
adamdriscoll wants to merge 6 commits intoPowerShell:masterfrom
adamdriscoll:IPSModulePath
Open

Add PSModulePath override to InitialSessionState.#27126
adamdriscoll wants to merge 6 commits intoPowerShell:masterfrom
adamdriscoll:IPSModulePath

Conversation

@adamdriscoll
Copy link
Copy Markdown
Contributor

@adamdriscoll adamdriscoll commented Mar 31, 2026

PR Summary

Adds PSModulePath override to InitialSessionState so we can rely on per-runspace configuration rather than process level environment variable.

PR Context

When hosting the PowerShell SDK in a multi-runspace environment, dealing with $ENV:PSModulePath is difficult. It inherits from many places (e.g. system, user, automatic values, parent processes) and changes based on version (PS7 and WinPS). It will be changing again once the PSContentPath PR is merged. As a PowerShell host process developer, I want to have full control over where the PowerShell SDK looks for modules, even system ones. This change should not affect existing users of either pwsh.exe or other users of the PS SDK as the feature is opt-in.

The goal of this PR is to avoid environment variables completely and have a small, focused property I can set when constructing a runspace to completely control module loading.

PR Change Summary

I have added a new PSModulePath property to InitialSessionState. In several key areas, I check for, and use, that property to ensure my PSModulePath is exactly as I expect. I have added some tests and verified they pass. I have run the code interactively to verify my change.

I will admit that it has been many years since I have contributed so I may be missing something.

Output from the preview pwsh.exe

PS .\publish> .\pwsh.exe
PowerShell 7.0.0-preview.1-4178-g6010e41f113e33eeecbc224fc19c416c2e1d5c6c
PS .\publish> $ENV:PSModulePath
d:\git\powershell\src\powershell-win-core\bin\debug\net11.0\win7-x64\publish\Modules;C:\Users\AdamDriscoll\Documents\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;D:\modules;;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
PS .\publish> Get-Module

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Manifest   7.0.0.0               Microsoft.PowerShell.Utility        {Add-Member, Add-Type, Clear-Variable, Compare-Ob…
Script     2.4.5                 PSReadLine                          {Get-PSReadLineKeyHandler, Get-PSReadLineOption,

Output from a custom runspace in preview pwsh.exe

PowerShell 7.0.0-preview.1-4178-g6010e41f113e33eeecbc224fc19c416c2e1d5c6c
PS .\publish> $init = [InitialSessionState]::CreateDefault()
PS .\publish> $init.PSModulePath = "D:\MyModules"
PS .\publish> $runspace = [RunspaceFactory]::CreateRunspace($init)
PS .\publish> $runspace.Open()
PS .\publish> $ps = [PowerShell]::Create()
PS .\publish> $ps.Runspace = $runspace
PS .\publish> $ps.AddCommand("Write-Something").Invoke()
Hello from MyModule
PS .\publish> Write-Something
Write-Something: The term 'Write-Something' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

PR AI Usage

I have used AI (Copilot\OpenAI GPT5.4) to assist in the development of the code for this but have kept it to a small, focused changes and reviewed it extensively. The contents of this description are my own words and typos.

PR Checklist

Copilot AI review requested due to automatic review settings March 31, 2026 19:00
@adamdriscoll adamdriscoll requested a review from a team as a code owner March 31, 2026 19:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in InitialSessionState.PSModulePath override so hosts can control module discovery per runspace instead of relying on the process-wide PSModulePath environment variable.

Changes:

  • Introduces InitialSessionState.PSModulePath and ensures it’s copied on InitialSessionState.Clone().
  • Routes module-path resolution through the new per-runspace override and avoids AppDomain-level module path caching when the override is set.
  • Adds xUnit coverage for runspaces/runspace pools and verifies the override doesn’t leak via the AppDomain module cache.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/System.Management.Automation/engine/InitialSessionState.cs Adds PSModulePath property and clones it.
src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs Reads module path from InitialSessionState.PSModulePath when provided.
src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs Disables AppDomain module cache writes when a custom module path override is active.
src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs Disables AppDomain module cache reads when a custom module path override is active.
test/xUnit/csharp/test_Runspace.cs Adds tests for custom per-runspace module path behavior and cache leakage prevention.

@kilasuit kilasuit added WG-Engine core PowerShell engine, interpreter, and runtime CL-Engine Indicates that a PR should be marked as an engine change in the Change Log WG-NeedsReview Needs a review by the labeled Working Group labels Mar 31, 2026
@ALIENQuake
Copy link
Copy Markdown

Honestly, this is great enchantment to the Apps that are using SDK and are distributed along with all necessary files. Good work!

}

if (includeSystemModulePath)
if (includeSystemModulePath && !HasCustomPSModulePath(context))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When PSModulePath is set, this line suppresses $PSHOME/Modules even when the caller passes includeSystemModulePath: true.

This means hosts that set a custom path lose access to core built-in modules unless they manually include $PSHOME/Modules in their custom string.

Is this the intended behavior? If so, the XML doc on InitialSessionState.PSModulePath should call this out explicitly. If not, consider letting includeSystemModulePath still take effect.

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.

I'll look at the value a little more clearly. I don't know how to set that on InitialSessionState so I want to make sure I understand that I can, if I need to, exclude $PSHome/Modules from the PSModulePath.

@jshigetomi
Copy link
Copy Markdown
Collaborator

jshigetomi commented Apr 1, 2026

@adamdriscoll Great idea! Declarative control over created runspaces could reduce some confusion over PSModulePath inheritance. Just a few things to consider.

  1. $env:PSModulePath inside the runspace will still reflect the process-level env var, not the custom path. Get-Module -ListAvailable will enumerate from the custom path though. Should the env var be updated in-session to match?

  2. Could you add a test for Clone() preserving PSModulePath? Something like: set it on the original, clone, assert equality, then verify independence.

  3. The cache-leak test covers custom→default direction. Worth adding the reverse: load a module in a default runspace, then verify a custom-path runspace doesn't pick it up from the AppDomain cache.

  4. Could you add a test asserting that system modules from $PSHOME/Modules are not available when a custom PSModulePath is set?


internal static bool HasCustomPSModulePath(ExecutionContext context)
{
return context?.InitialSessionState?.PSModulePath != null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This allows empty strings. Is that intended?

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.

I'd like that. It would be nice to set it to an empty value to bypass any module look up. I don't know exactly why I would do that but I could see it being useful.

@adamdriscoll
Copy link
Copy Markdown
Contributor Author

@jshigetomi

$env:PSModulePath inside the runspace will still reflect the process-level env var, not the custom path. Get-Module -ListAvailable will enumerate from the custom path though. Should the env var be updated in-session to match?

I considered this but I guess I didn't know how to proceed. I feel like if I set it in-session, it wouldn't really be a true environment variable. We might be able to update the environment variable provider to pull the runspace-scoped value. I also don't want to actually update the environment variable for the whole process.

I guess it all depends on how much calling scripts may examine or use this variable. In PowerShell Universal, I don't know if it would matter much if we used the true value here.

Could you add a test for Clone() preserving PSModulePath? Something like: set it on the original, clone, assert equality, then verify independence.

Will do.

The cache-leak test covers custom→default direction. Worth adding the reverse: load a module in a default runspace, then verify a custom-path runspace doesn't pick it up from the AppDomain cache.

Will do.

Could you add a test asserting that system modules from $PSHOME/Modules are not available when a custom PSModulePath is set?

Will do.

I should have a little time this afternoon to get these changes in.

@adamdriscoll
Copy link
Copy Markdown
Contributor Author

Private GetModulePath has been renamed to ResolveModulePath. Add new tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-Engine Indicates that a PR should be marked as an engine change in the Change Log WG-Engine core PowerShell engine, interpreter, and runtime WG-NeedsReview Needs a review by the labeled Working Group

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants