Handle MSIX installation specially when prepend to PATH - #27782
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
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” viaGetPSExecutableHome(). - 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$powershellpath 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. |
There was a problem hiding this comment.
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.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Patrick Meinecke (SeeminglyScience)
left a comment
There was a problem hiding this comment.
LGTM!
| string processName = Path.GetFileName(psExePath); | ||
|
|
||
| // Use 'Environment.ProcessPath' if it points to 'pwsh.exe' or 'pwsh'. | ||
| if (pwshName.Equals(processName, StringComparison.Ordinal)) |
There was a problem hiding this comment.
Environment.ProcessPath return real file name from file system.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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"
}
There was a problem hiding this comment.
Yeah, I had the same question, but they clearly aren’t keen on supporting any alternative distributors in any form.
There was a problem hiding this comment.
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:
- Avoid having the code to handle this API not being available on editions like Server Core (Appx/MSIX packages not supported).
- 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.
There was a problem hiding this comment.
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
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.
There was a problem hiding this comment.
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.

Context
Prepend
$PSHOMEtoPATHenv 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
$PSHOMEto the beginning ofPATH, and for MSIX installation,$PSHOMEcontains version numbers that change when PowerShell is updated.When
cmakeis 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
$PSHOMEtoPATH. It now handles the MSIX package installation specially -- it uses the directory that contains theExecutionAliasof the MSIX installation instead of$PSHOME. For example:Those are the stable paths that contain the
pwsh.exealias pointing to corresponding channels of MSIX. They won't change when the MSIX packages get updated.PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header