Skip to content

Handle MSIX installation specially when prepend to PATH - #27782

Merged
Dongbo Wang (daxian-dbw) merged 4 commits into
PowerShell:masterfrom
daxian-dbw:env-path-2
Sep 2, 2026
Merged

Dongbo Wang (daxian-dbw) merged 4 commits into
PowerShell:masterfrom
daxian-dbw:env-path-2

Conversation

@daxian-dbw

@daxian-dbw Dongbo Wang (daxian-dbw) commented Aug 7, 2026 •

Copy link
Copy Markdown
Member

Context

Prepend $PSHOME to PATH env variable at startup causes a problem to cmake-based build system when it runs in the MSIX PowerShell installation because it caches the location of PowerShell on its first run from within PowerShell.

At startup, PowerShell adds $PSHOME to the beginning of PATH, and for MSIX installation, $PSHOME contains version numbers that change when PowerShell is updated.

When cmake is started for the 1st time from MSIX PowerShell, the path it caches will be that $PSHOME, which will become invalid after an update of the MSIX PowerShell.

PR Summary

This PR updated the code that prepend $PSHOME to PATH. It now handles the MSIX package installation specially -- it uses the directory that contains the ExecutionAlias of the MSIX installation instead of $PSHOME. For example:

Microsoft.PowerShell -> $env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.PowerShell_8wekyb3d8bbwe
Microsoft.PowerShell-LTS -> $env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.PowerShell-LTS_8wekyb3d8bbwe
Microsoft.PowerShellPreview -> $env:LOCALAPPDATA\Microsoft\WindowsApps\Microsoft.PowerShellPreview_8wekyb3d8bbwe

Those are the stable paths that contain the pwsh.exe alias pointing to corresponding channels of MSIX. They won't change when the MSIX packages get updated.

PR Checklist

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@daxian-dbw Dongbo Wang (daxian-dbw) added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Aug 7, 2026

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.

Pull request overview

This PR adjusts how ConsoleHost prepends the PowerShell executable location to PATH at startup, with special handling for MSIX installs so that pwsh resolves to a stable path (not a versioned MSIX package folder), preventing downstream tools (e.g., CMake) from caching an update-volatile path.

Changes:

  • Replace $PSHOME-based PATH prepending with a computed “pwsh executable home” via GetPSExecutableHome().
  • Add MSIX-specific path stabilization (ResolveStablePathIfMsix) to prefer the WindowsApps execution-alias directory over the versioned package directory.
  • Update the console host PATH test to invoke pwsh -v (via command resolution) instead of invoking the known $powershell path directly.

Reviewed changes

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

File Description
src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs Computes the executable home path for PATH-prepending and adds MSIX-specific stable-path resolution.
test/powershell/Host/ConsoleHost.Tests.ps1 Adjusts the PATH behavior test to validate pwsh command resolution matches the current running build.

Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs Outdated

@iSazonov Ilya (iSazonov) left a comment •

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.

Dongbo Wang (@daxian-dbw) I think it is more right approach since msix manifest already defines App Execution Alias and we can benefit from this.

One more note, if msix scenario is the main one on Windows, then maybe we should start checking with it. (I don't know if it's worth doing an explicit check like https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getcurrentpackagepath - probably not.)

Below minor comments.

Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
Comment thread src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
@daxian-dbw
Dongbo Wang (daxian-dbw) marked this pull request as ready for review August 10, 2026 22:10
@daxian-dbw
Dongbo Wang (daxian-dbw) requested a review from a team as a code owner August 10, 2026 22:11
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@iSazonov Ilya (iSazonov) left a comment

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.

LGTM.

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.

LGTM!

@daxian-dbw
Dongbo Wang (daxian-dbw) merged commit d0f43b0 into PowerShell:master Sep 2, 2026
36 checks passed
@daxian-dbw
Dongbo Wang (daxian-dbw) deleted the env-path-2 branch September 2, 2026 15:47
string processName = Path.GetFileName(psExePath);

// Use 'Environment.ProcessPath' if it points to 'pwsh.exe' or 'pwsh'.
if (pwshName.Equals(processName, StringComparison.Ordinal))

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.

The ProcessPath on Windows could really be any case as it depends on what was provided as the first argv in the lpCommandLine argument to CreateProcess. Seems like we would want this to be OrdinalIgnoreCase on Windows.

Image

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.

Environment.ProcessPath return real file name from file system.

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.

My screenshot shows you that's not the case. The -CommandLine argument there maps directly to the lpCommandLine used when calling CreateProcess. Note the change in capitalisation for P in pwsh.exe changes based on what was provided.

Most tools will probably normalize it but you can't guarantee it'll always match the FS on Windows as anything can call CreateProcess how they like.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch. I will submit a follow-up PR to correct the comparison on Windows.

/// "%ProgramFiles%\WindowsApps\Microsoft.PowerShell_7.x.x.0_x64__8wekyb3d8bbwe".
/// </summary>
/// <param name="psExeHome">Path to the directory that contains the pwsh executable.</param>
private static string ResolveStablePathIfMsix(string psExeHome)

@jborean93 Jordan Borean (jborean93) Sep 3, 2026 •

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 seems a bit unwise to hardcode a lot of these checks to specific publishers and paths. Not only does this stop someone from packaging their own MSIX of their PowerShell fork without changing this code it also stops you from installing the MSIX package into a custom volume. Granted the latter still surfaces as being run under C:\Program Files\WindowsApps\...\pwsh.exe through the use of junction points but who knows if that will change at any future date. Also who knows if Windows will change up anything about this application directory.

Wouldn't a better idea to instead see if you can call GetCurrentPackageFamilyName to see if 1 the package has a package identity associated with it (is an MSIX package) and also get the family name for the later psExeHome check.

For example take this pwsh script as a POC

$APPMODEL_ERROR_NO_PACKAGE = 15700

$k32 = New-CtypesLib Kernel32.dll
$l = 0
$b = [Text.StringBuilder]::new()

$res = $k32.CharSet('Unicode').GetCurrentPackageFamilyName([ref]$l, $b)
if ($res -eq $APPMODEL_ERROR_NO_PACKAGE) {
    "PSHome = $PSHome"
}
else {
    $null = $b.EnsureCapacity($l)
    $null = $k32.GetCurrentPackageFamilyName([ref]$l, $b)
    $familyId = $b.ToString()
    "PSHome = $env:LocalAppData\Microsoft\WindowsApps\$familyId"
}
Image

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.

Yeah, I had the same question, but they clearly aren’t keen on supporting any alternative distributors in any form.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

About "installing the MSIX package into a custom volume", Ilya (@iSazonov) and I had this discussion in #27782 (comment), and I don't find a way to install the MSIX PowerShell to a different drive.

Wouldn't a better idea to instead see if you can call GetCurrentPackageFamilyName

The idea to do it with the path check was to:

  1. Avoid having the code to handle this API not being available on editions like Server Core (Appx/MSIX packages not supported).
  2. Avoid a PInvoke at startup.

If it turns out the path check is not sufficient, we can always get back to the GetCurrentPackageFamilyName API call.

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.

and I don't find a way to install the MSIX PowerShell to a different drive.

It's certainly possible but luckily when I tested it, Windows goes to some lengths to pretend it's still under C:\Program Files\WindowsApps through the use of junction points and a lot of the metadata still report the C:\Program Files\WindowsApps location. I still don't think it's a good idea because this seems more like an implementation detail and could potentially change in the future or have some unknown permutation that changes how it works.

If you are interested, you can install an msix package to another volume by using the -Volume parameter in Add-AppxPackage or by using Set-AppxDefaultVolume to change the default volume a package is installed to.

For example I have an msixbundle of 7.6.6 and a separate volume D:\ which I just mounted from a vhdx. I ran the following to setup PowerShell on that volume

Add-AppxVolume -Path D:
Add-AppxPackage -Path .\Downloads\PowerShell-7.6.6.msixbundle -Volume D:

Once installed the package metadata still points to C:\Program Files\WindowsApps and running the process still pretends it's in that directory like I mentioned above but you can see that it's actually a junction point to your custom volume

image image

The idea to do it with the path check was to ...

I can see the concerns around startup time, I have not measured the cost of calling GetCurrentPackageFamilyName and a path check is technically simpler. It just seems wrong to rely on something that may possibly be an implementation detail. Probably a better question for Howard Kapustein (@DrusTheAxe) as to whether this is something that can be relied upon or if there is another alternative.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thank you Jordan Borean (@jborean93) for going extra mile to try installing the MSIX package to a different volume.

Copilot couldn't find current Microsoft documentation that explicitly promises, as a public contract, that a package installed to a secondary AppX volume will always have InstallLocation represented as a junction beneath C:\Program Files\WindowsApps.

So, I guess I just have to get back to the GetCurrentPackageFamilyName API.

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

Labels

Backport-7.6.x-Done CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants