Repository navigation
Windows PowerShell 5.1 grandchild inherits pwsh 7's PSModulePath and loads 7.0.0.0 modules: Get-FileHash throws CommandNotFoundException #27774
Description
Activity
This would appear to be an issue with Windows Powershell. This repository, however, is for Powershell (core). Thus, this issue should be closed and an issue raised with the Windows Feedback tool. Sorry if this is not the answer you wanted.
Thanks for looking at this, and the repo-scope point is fair.
One measured detail from the report may change the framing, though: PowerShell Core already implements the mitigation. When
pwshlaunchespowershell.exedirectly, it rewritesPSModulePath— 6 entries become 4 — and it even substitutesDocuments\PowerShell\ModuleswithDocuments\WindowsPowerShell\Modules:PSModulePath in pwsh 7.6.4 (6 entries) C:\Users\<user>\Documents\PowerShell\Modules C:\Program Files\PowerShell\Modules c:\program files\windowsapps\microsoft.powershell_7.6.4.0_x64__8wekyb3d8bbwe\Modules C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules C:\Program Files\Intel\Wired Networking\ PSModulePath in a DIRECT powershell.exe 5.1 child (4 entries) C:\Users\<user>\Documents\WindowsPowerShell\Modules <-- substituted C:\Program Files\WindowsPowerShell\Modules C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules C:\Program Files\Intel\Wired Networking\That logic lives in Core, and it means Core has already concluded that its
PSModulePathis unsafe for Windows PowerShell, and owns the mitigation.What is missing is therefore not a Windows PowerShell fix. It is that Core applies that sanitization only at the moment it spawns the process, while exporting the unsanitized value into the environment block that every descendant inherits. A grandchild —
pwsh→ any intermediate process →powershell.exe— receives the raw value and resolvesMicrosoft.PowerShell.Utilityto7.0.0.0.So the question I would put to the team is a Core design one: should Core-only module directories live in the process environment block at all, rather than in session state?
If the team still considers this out of scope, I will close it and raise it through Feedback Hub — no argument. I just wanted the existing sanitization on record first, since it suggests the boundary is already drawn on the Core side.
Reacted by Michael Klement and Matthew GrayDuplicate of:
This current issue does describe the issue in more detail though.
- addedIssue-BugIssue has been identified as a bug in the productIssue has been identified as a bug in the productWG-Enginecore PowerShell engine, interpreter, and runtimecore PowerShell engine, interpreter, and runtimeWG-NeedsReviewNeeds a review by the labeled Working GroupNeeds a review by the labeled Working Group
on Aug 6, 2026 - marked Powershell Framework is broken (again) under Powershell Core #18108 as a duplicate of this issue
on Aug 6, 2026 My question always when I see this sort of thing (irrespective of the PowerShell vs pwsh) is where is the benefit in the multiple layered approach going to
grandchild/great-granchild/great-great-granchildetcUsing Start-Process as opposed to direct calling of the python executable may help here with the -UseNewEnvironment parameter (not tested this as I don't have a python install to hand)
I don't think we could do anything in this codebase to aid with
pwsh -> other executable/s -> powershellother than perhaps provide guidance which would be docs updates.I am marking #18108 as dupe of this as the title in here is clearer & copied the labels that had across.
Thanks Ryan Yates (@kilasuit), and thanks for consolidating #18108 here.
I measured your suggestion rather than assume anything about it. It works. Full matrix below, run today on the machine from the report — pwsh 7.6.4 (MSIX), Windows PowerShell 5.1.26100.8972, Python 3.14.3, Windows 11 10.0.26200.
test51.ps1is unchanged from the original report.# chain PSModulePathseen by 5.1Get-FileHashsession env A pwsh->powershell.exe4 OK — B pwsh->python->powershell.exe6 CommandNotFoundException preserved C B, with PSModulePathstripped in Python4 OK preserved D pwsh->Start-Process python -Environment @{PSModulePath=$null}->powershell.exe4 OK preserved E pwsh->Start-Process python -UseNewEnvironment->powershell.exe4 OK wiped F pwsh->cmd.exe->powershell.exe6 CommandNotFoundException — G pwsh->cmd.exe->cmd.exe->powershell.exe6 CommandNotFoundException — H pwsh->cmd /c "set PSModulePath=&& …"->powershell.exe4 OK — On
-UseNewEnvironment(E)It does fix the symptom here, so the suggestion is sound. One scoping note:
PSModulePathat Machine scope on this machine holds only the three Windows PowerShell entries and User scope is empty, so failure mode 2 from jazzdelightsme's write-up in #18108 — a registry value already polluted with version-specific paths — does not apply here and would need separate testing.Two measured costs:
- It resets the whole environment. I set a marker variable in the pwsh session before launching. Case D still sees
PROBE_SESSION_VAR=PRESENT; case E sees<ABSENT>. For the Python test harness this report came from, that also removesVIRTUAL_ENVand the venvPATH— the workaround breaks the thing it is fixing. - The value handed to the intermediate is
%ProgramFiles%\WindowsPowerShell\Modules, unexpanded. 5.1 normalizes it, but a non-PowerShell consumer readingPSModulePathgets the literal.
A better form of the same idea (D)
Start-Processgained-Environmentin PowerShell 7.4, where a$nullvalue removes a single variable and inherits the rest:Start-Process python -Environment @{ PSModulePath = $null } -NoNewWindow -Wait
Measured:
PSModulePathabsent in the intermediate, 5.1 rebuilds its own 4 entries,Get-FileHashOK, and the rest of the session environment survives. This is the PowerShell-native equivalent of the Python workaround in the report, and I think it is the better line to put in any guidance that comes out of this.On "where is the benefit in the multiple layered approach"
This is the part I would gently push back on, and F/G are the measured answer: no Python, no layering, no design choice — a single
cmd /creproduces it, and a second nestedcmdchanges nothing.That is exactly the chain reported in #18108, and it is what a VS Code integrated terminal running pwsh produces the moment a Makefile, an npm script, or a CI step shells out to
powershell.exe. Nobody chooses to have a grandchild; it is the default shape of ordinary tooling. The Python intermediate in my report is incidental — it is simply what my harness happened to be written in.What is actually missing
H confirms the 2022
set PSModulePath=workaround still works. So there are three known fixes — Python-side,Start-Process-side,cmd-side — and every one of them is caller-side and requires the caller to already know about the defect. A workaround was never the missing piece; the report shipped with one.The gap is that an intermediate process has no supported way to hand a correct environment to a Windows PowerShell descendant, even though Core already computes exactly that value at spawn time — case A is 6 entries in, 4 out, with
Documents\PowerShell\Modulessubstituted forDocuments\WindowsPowerShell\Modules.AssortedBits asked for precisely this in #18108 and it was never answered: either document it as known and unsupported, or expose the fixup. The second is a small change — make the sanitized value Core already computes reachable, so an intermediate can apply it deliberately. That sidesteps the "we cannot control what grandchildren inherit" objection entirely, because the grandchild's parent would then have something supported to call.
If the WG concludes docs-only, that is a fine outcome by me — it would just help to name the target so this is closeable. Concretely: a short section in
about_PSModulePathstating that the Windows PowerShell fixup applies only to direct children and does not survive an intermediate process, with the three workarounds above.I am happy to open the docs PR against MicrosoftDocs/PowerShell-Docs with that text if the WG prefers that route.
- It resets the whole environment. I set a marker variable in the pwsh session before launching. Case D still sees
If you remember IRIX, ("It's a UNIX system! I know this!"), it had separate library path variables for different ELF formats, LD_LIBRARY_PATH, LD_LIBRARYN32_PATH and LD_LIBRARY64_PATH.
In retrospect PowerShell 7 should have kept PSModulePath purely for PowerShell Desktop compatible modules, and put PowerShell 7 ones on a path called PSModulePath7 or PSModulePathCore.
Also, it should not be messing with the PATH variable and adding itself to the PATH.
If that had been done the nesting could have been have been continual until memory limits of the machine were hit.
The Engine WG reviewed this issue and we believe this is a by-design behavior for
pwsh. As forpowershell.exe, we won't be able to change its module path discover behavior. We agree that the doc can be enhanced to call out this behavior inabout_PSModulePatharticle. Sylvain Labeye (@Sylvain13bdr), please open a doc issue or PR at your convivence. Thanks!- addedWG-ReviewedA Working Group has reviewed this and made a recommendationA Working Group has reviewed this and made a recommendationResolution-By DesignThe reported behavior is by design.The reported behavior is by design.and removedWG-NeedsReviewNeeds a review by the labeled Working GroupNeeds a review by the labeled Working Group
on Aug 24, 2026 Sylvain13bdr commented
on Aug 25, 2026 AuthorMore actionsDoc PR opened as invited: MicrosoftDocs/PowerShell-Docs#13248
It extends "Starting Windows PowerShell from PowerShell 7" in
about_PSModulePath(7.4 through 7.7) with the direct-children limitation
and the three measured workarounds (Start-Process -Environmenton 7.4+,
cmd.exeset PSModulePath=, Python subprocess with the variable removed),
and adds an inheriting-side note to the 5.1 article. Thanks for the review,
Dongbo Wang (@daxian-dbw).Dongbo Wang (@daxian-dbw) Sylvain Labeye (@Sylvain13bdr) The docs PR has been merged.
Reacted by Ryan YatesClosed by MicrosoftDocs/PowerShell-Docs#13248
Prerequisites
Summary
pwshsanitizesPSModulePathfor the Windows PowerShell processes it launches directly, but the sanitization does not survive an intermediate process. Whenpowershell.exe(5.1) is launched as a grandchild — for examplepwsh→python→powershell.exe— it inheritspwsh's rawPSModulePath. The PowerShell 7 module directory precedes the Windows PowerShell system module directory, so 5.1 resolves shared module names (Microsoft.PowerShell.Utility,Microsoft.PowerShell.Management, …) to the 7.0.0.0 builds and module auto-loading fails.Net effect:
Get-FileHash— and every other cmdlet from those modules — raisesCommandNotFoundExceptionin a fully normal Windows PowerShell 5.1 session.The direct-launch case works, which makes this hard to diagnose: the defect appears only when an intermediate process sits between
pwshandpowershell.exe.Steps to reproduce
test51.ps1(ASCII, no BOM issues):From a
pwsh7.6.4 session:Expected behavior
Actual behavior
Direct child — correct:
Grandchild through Python — broken:
Mechanism
PSModulePathas seen bypwsh7.6.4 (6 entries):PSModulePathas seen by a directpowershell.exe5.1 child (4 entries) —pwshcorrectly rewrites it:Entries removed by
pwshfor its direct child:So the sanitization exists and is correct — it simply does not apply when the 5.1 process is not a direct child.
The pwsh 7 module directory holds 8 modules whose names collide with Windows PowerShell 5.1 modules:
What 5.1 resolves, with the inherited path:
With
PSModulePathremoved from the child environment, only3.1.0.0is listed and everything works.Why this matters
The symptom is opaque. A test harness written in Python that shells out to
powershell.exefails uniformly with a non-zero exit code that looks like a defect in the harness or in the script under test. In my case an entire 38-case suite failed identically before the cause was found, and the direct-launch check — the natural thing to try first — showed nothing wrong.Any tool that runs user code under
pwshreproduces this: build systems, CI agents, editors' integrated terminals, agentic coding tools.Workaround
Strip
PSModulePathfrom the subprocess environment; the 5.1 child then rebuilds its own system default:Possible fixes
powershell.exe5.1 detect and drop PowerShell-Core-only module directories from an inheritedPSModulePath— arguably the robust fix, but it lives in the Windows PowerShell codebase.pwshavoid exporting a Core-onlyPSModulePathinto the environment it passes to all children, not just Windows PowerShell ones — e.g. keep the Core-specific entries in the session rather than in the process environment block.PSModulePathdifferences, but not that they leak to grandchildren and shadow 5.1's own modules.Environment data
Additional:
Note: the pwsh install here is the MSIX/Store build, which is why the offending directory sits under
WindowsApps. The same shadowing is expected with an MSI install, where the directory would beC:\Program Files\PowerShell\7\Modules.Closed by MicrosoftDocs/PowerShell-Docs#13248