mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-14 05:39:11 +00:00
Use GetCurrentPackageFamilyNameNative API instead of path matching to detect MSIX installation (#28029)
This commit is contained in:
1 parent
c172d736a6
commit
29a743d40f
3 files changed
+88
-46
No files matched your search
@@ -378,20 +378,41 @@ namespace Microsoft.PowerShell
|
||||
#if UNIX
|
||||
const string pwshName = "pwsh";
|
||||
const string dotnetToolPathSegment = "/.store/powershell/";
|
||||
const StringComparison procNameCompType = StringComparison.Ordinal;
|
||||
#else
|
||||
const string pwshName = "pwsh.exe";
|
||||
const string dotnetToolPathSegment = @"\.store\powershell\";
|
||||
const StringComparison procNameCompType = StringComparison.OrdinalIgnoreCase;
|
||||
#endif
|
||||
|
||||
string psExePath = Environment.ProcessPath;
|
||||
string psExeHome = Path.GetDirectoryName(psExePath);
|
||||
string processName = Path.GetFileName(psExePath);
|
||||
|
||||
// Use 'Environment.ProcessPath' if it points to 'pwsh.exe' or 'pwsh'.
|
||||
if (pwshName.Equals(processName, StringComparison.Ordinal))
|
||||
// Use 'Environment.ProcessPath' if it points to 'pwsh.exe' or 'pwsh'. The 'ProcessPath' on Windows
|
||||
// could be any case as it depends on the `lpCommandLine` argument passed to `CreateProcess`, so we
|
||||
// compare with 'OrdinalIgnoreCase' on Windows.
|
||||
if (pwshName.Equals(processName, procNameCompType))
|
||||
{
|
||||
#if !UNIX
|
||||
psExeHome = ResolveStablePathIfMsix(psExeHome);
|
||||
// When running from an MSIX package, 'psExeHome' points to the MSIX package folder under "Program Files".
|
||||
//
|
||||
// That path contains a version string and will change with every update. Prepending that path to the PATH
|
||||
// environment variable caused a problem for CMake-based build systems: CMake cached the path to 'pwsh.exe'
|
||||
// when running for the first time from the MSIX PowerShell. That cached path became invalid after the MSIX
|
||||
// PowerShell was updated, which broke CMake.
|
||||
//
|
||||
// So, instead of using the "Program Files" package folder path, we need to use the stable path that contains
|
||||
// the execution alias for the specific MSIX package, e.g. use "%LOCALAPPDATA%\Microsoft\WindowsApps\Microsoft.PowerShell_8wekyb3d8bbwe"
|
||||
// instead of "%ProgramFiles%\WindowsApps\Microsoft.PowerShell_7.x.x.0_x64__8wekyb3d8bbwe".
|
||||
if (Utils.IsMSIXInstallation(out string packageFamilyName))
|
||||
{
|
||||
string localAppDataDir = Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.LocalApplicationData,
|
||||
Environment.SpecialFolderOption.DoNotVerify);
|
||||
|
||||
psExeHome = $"{localAppDataDir}\\Microsoft\\WindowsApps\\{packageFamilyName}";
|
||||
}
|
||||
#endif
|
||||
return psExeHome;
|
||||
}
|
||||
@@ -439,49 +460,6 @@ namespace Microsoft.PowerShell
|
||||
}
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Handle the MSIX package scenario where <paramref name="psExeHome"/> points to the MSIX package folder under "Program Files".
|
||||
///
|
||||
/// That path contains a version string and will change with every update. Prepending that path to the PATH environment variable
|
||||
/// caused a problem for CMake-based build systems: CMake cached the path to 'pwsh.exe' when running for the first time from the
|
||||
/// MSIX PowerShell. That cached path became invalid after the MSIX PowerShell was updated, which broke CMake.
|
||||
///
|
||||
/// So, instead of using the "Program Files" package folder path, we need to use the stable path that contains the execution alias
|
||||
/// for the specific MSIX package, e.g. use "%LOCALAPPDATA%\Microsoft\WindowsApps\Microsoft.PowerShell_8wekyb3d8bbwe" instead of
|
||||
/// "%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)
|
||||
{
|
||||
const string msixPublisherSuffix = "_8wekyb3d8bbwe";
|
||||
const string msixPackageBaseName = "Microsoft.PowerShell";
|
||||
|
||||
if (psExeHome.EndsWith(msixPublisherSuffix, StringComparison.Ordinal))
|
||||
{
|
||||
string programFileDir = Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ProgramFiles,
|
||||
Environment.SpecialFolderOption.DoNotVerify);
|
||||
|
||||
string prefix = $"{programFileDir}\\WindowsApps\\{msixPackageBaseName}";
|
||||
if (psExeHome.StartsWith(prefix, StringComparison.Ordinal))
|
||||
{
|
||||
int startIndex = prefix.Length;
|
||||
int underbarIndex = psExeHome.IndexOf('_', startIndex);
|
||||
if (underbarIndex > 0)
|
||||
{
|
||||
ReadOnlySpan<char> channelSuffix = psExeHome.AsSpan(startIndex, underbarIndex - startIndex);
|
||||
string localAppDataDir = Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.LocalApplicationData,
|
||||
Environment.SpecialFolderOption.DoNotVerify);
|
||||
|
||||
psExeHome = $"{localAppDataDir}\\Microsoft\\WindowsApps\\{msixPackageBaseName}{channelSuffix}{msixPublisherSuffix}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return psExeHome;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The break handler for the program. Dispatches a break event to the current Executor.
|
||||
/// </summary>
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal static partial class Interop
|
||||
{
|
||||
internal static partial class Windows
|
||||
{
|
||||
[LibraryImport("kernel32.dll", EntryPoint = "GetCurrentPackageFamilyName", StringMarshalling = StringMarshalling.Utf16)]
|
||||
private static partial int GetCurrentPackageFamilyNameNative(ref uint packageFamilyNameLength, Span<char> packageFamilyName);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the package family name of the current process when it has package (MSIX) identity; otherwise null.
|
||||
/// </summary>
|
||||
internal static string? GetCurrentPackageFamilyName()
|
||||
{
|
||||
const int ErrorSuccess = 0;
|
||||
const int PackageFamilyNameMaxLength = 65; // Maximum name plus null terminator.
|
||||
|
||||
try
|
||||
{
|
||||
uint length = PackageFamilyNameMaxLength;
|
||||
Span<char> buffer = stackalloc char[PackageFamilyNameMaxLength];
|
||||
int result = GetCurrentPackageFamilyNameNative(ref length, buffer);
|
||||
if (result is not ErrorSuccess || length is 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// The returned length includes the null terminator, which we don't want in the managed string.
|
||||
return new string(buffer[..(int)(length - 1)]);
|
||||
}
|
||||
catch (Exception exception) when (exception is DllNotFoundException or EntryPointNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,6 +475,27 @@ namespace System.Management.Automation
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static string s_packageFamilyName;
|
||||
private static bool? s_isMSIXInstallation;
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether the current process has package (MSIX) identity, caching the result
|
||||
/// so the native API is only called once.
|
||||
/// </summary>
|
||||
/// <param name="packageFamilyName">Receives the package family name when the process has package identity; otherwise null.</param>
|
||||
/// <returns>True if the current process has package (MSIX) identity; otherwise false.</returns>
|
||||
internal static bool IsMSIXInstallation(out string packageFamilyName)
|
||||
{
|
||||
if (s_isMSIXInstallation is null)
|
||||
{
|
||||
s_packageFamilyName = Interop.Windows.GetCurrentPackageFamilyName();
|
||||
s_isMSIXInstallation = s_packageFamilyName is not null;
|
||||
}
|
||||
|
||||
packageFamilyName = s_packageFamilyName;
|
||||
return s_isMSIXInstallation.Value;
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static string DefaultPowerShellAppBase => GetApplicationBase(DefaultPowerShellShellID);
|
||||
|
||||
Reference in new issue
Block a user