From 2e7e37ae090b6f36f7fa2edfa54ce316e6e82454 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 27 Feb 2025 17:49:02 -0800 Subject: [PATCH 1/5] Introduce PowerShell installer stub --- .../ProfileIcons/Powershell_black_64.png | Bin 0 -> 3287 bytes src/cascadia/TerminalApp/TabManagement.cpp | 10 + .../TerminalSettingsEditor/Extensions.xaml | 3 +- .../AzureCloudShellGenerator.cpp | 2 +- .../AzureCloudShellGenerator.h | 2 +- .../TerminalSettingsModel/CascadiaSettings.h | 12 +- .../CascadiaSettingsSerialization.cpp | 83 ++- .../IDynamicProfileGenerator.h | 2 +- ...crosoft.Terminal.Settings.ModelLib.vcxproj | 2 + ...Terminal.Settings.ModelLib.vcxproj.filters | 6 + .../PowershellCoreProfileGenerator.cpp | 531 ++++++++---------- .../PowershellCoreProfileGenerator.h | 53 +- ...PowershellInstallationProfileGenerator.cpp | 42 ++ .../PowershellInstallationProfileGenerator.h | 33 ++ .../Resources/en-US/Resources.resw | 17 +- .../SshHostGenerator.cpp | 2 +- .../TerminalSettingsModel/SshHostGenerator.h | 2 +- .../VisualStudioGenerator.cpp | 2 +- .../VisualStudioGenerator.h | 2 +- .../WslDistroGenerator.cpp | 2 +- .../WslDistroGenerator.h | 2 +- 21 files changed, 502 insertions(+), 308 deletions(-) create mode 100644 src/cascadia/CascadiaPackage/ProfileIcons/Powershell_black_64.png create mode 100644 src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp create mode 100644 src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.h diff --git a/src/cascadia/CascadiaPackage/ProfileIcons/Powershell_black_64.png b/src/cascadia/CascadiaPackage/ProfileIcons/Powershell_black_64.png new file mode 100644 index 0000000000000000000000000000000000000000..53bbbee10b75a1932cf557acbe256a921ccc0b63 GIT binary patch literal 3287 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 + Tag="{x:Bind Profile.Guid}" + Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.Deleted)}"> diff --git a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp index 3d42bdd21c..a8cb1bc787 100644 --- a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp @@ -38,7 +38,7 @@ std::wstring_view AzureCloudShellGenerator::GetIcon() const noexcept // - // Return Value: // - a vector with the Azure Cloud Shell connection profile, if available. -void AzureCloudShellGenerator::GenerateProfiles(std::vector>& profiles) const +void AzureCloudShellGenerator::GenerateProfiles(std::vector>& profiles) { if (AzureConnection::IsAzureConnectionAvailable()) { diff --git a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h index 9ba01bf632..bf07125284 100644 --- a/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h +++ b/src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h @@ -27,6 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model std::wstring_view GetNamespace() const noexcept override; std::wstring_view GetDisplayName() const noexcept override; std::wstring_view GetIcon() const noexcept override; - void GenerateProfiles(std::vector>& profiles) const override; + void GenerateProfiles(std::vector>& profiles) override; }; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index b9e6e59a8d..2a8d6af0fe 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -121,7 +121,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _appendProfile(winrt::com_ptr&& profile, const winrt::guid& guid, ParsedSettings& settings); void _addUserProfileParent(const winrt::com_ptr& profile); bool _addOrMergeUserColorScheme(const winrt::com_ptr& colorScheme); - void _executeGenerator(const IDynamicProfileGenerator& generator); + void _executeGenerator(IDynamicProfileGenerator& generator); + void _cleanupPowerShellInstaller(bool isPowerShellInstalled); winrt::com_ptr _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope); std::unordered_set _ignoredNamespaces; @@ -235,14 +236,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation public: FragmentProfileEntry(winrt::guid profileGuid, hstring json) : _profileGuid{ profileGuid }, - _json{ json } {} + _Json{ json } {} winrt::guid ProfileGuid() const noexcept { return _profileGuid; } - hstring Json() const noexcept { return _json; } + WINRT_PROPERTY(hstring, Json); private: winrt::guid _profileGuid; - hstring _json; }; struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT @@ -270,7 +270,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _filename{ filename } {} hstring Source() const noexcept { return _source; } - hstring Json() const noexcept { return _json; } hstring Filename() const noexcept { return _filename; } Windows::Foundation::Collections::IVector ModifiedProfiles() const noexcept { return _modifiedProfiles; } void ModifiedProfiles(const Windows::Foundation::Collections::IVector& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; } @@ -278,7 +277,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void NewProfiles(const Windows::Foundation::Collections::IVector& newProfiles) noexcept { _newProfiles = newProfiles; } Windows::Foundation::Collections::IVector ColorSchemes() const noexcept { return _colorSchemes; } void ColorSchemes(const Windows::Foundation::Collections::IVector& colorSchemes) noexcept { _colorSchemes = colorSchemes; } + WINRT_PROPERTY(hstring, Json); + public: // views Windows::Foundation::Collections::IVectorView ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; } Windows::Foundation::Collections::IVectorView NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; } @@ -286,7 +287,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: hstring _source; - hstring _json; hstring _filename; Windows::Foundation::Collections::IVector _modifiedProfiles; Windows::Foundation::Collections::IVector _newProfiles; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index b2ea96e1b2..fee3645e3a 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -19,6 +19,7 @@ #if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED #include "SshHostGenerator.h" #endif +#include "PowershellInstallationProfileGenerator.h" #include "ApplicationState.h" #include "DefaultTerminal.h" @@ -178,13 +179,77 @@ SettingsLoader::SettingsLoader(const std::string_view& userJSON, const std::stri // (meaning profiles specified by the application rather by the user). void SettingsLoader::GenerateProfiles() { - _executeGenerator(PowershellCoreProfileGenerator{}); - _executeGenerator(WslDistroGenerator{}); - _executeGenerator(AzureCloudShellGenerator{}); - _executeGenerator(VisualStudioGenerator{}); + PowershellCoreProfileGenerator powerShellGenerator{}; + _executeGenerator(powerShellGenerator); + + WslDistroGenerator wslGenerator{}; + _executeGenerator(wslGenerator); + + AzureCloudShellGenerator acsGenerator{}; + _executeGenerator(acsGenerator); + + VisualStudioGenerator vsGenerator{}; + _executeGenerator(vsGenerator); + #if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED - _executeGenerator(SshHostGenerator{}); + SshHostGenerator sshGenerator{}; + _executeGenerator(sshGenerator); #endif + + PowershellInstallationProfileGenerator pwshInstallationGenerator{}; + _executeGenerator(pwshInstallationGenerator); + + _cleanupPowerShellInstaller(!powerShellGenerator.GetPowerShellInstances().empty()); +} + +// Retrieve the "Install Latest PowerShell" profile and... +// - add a comment to the JSON to indicate it's conditionally applied +// - (if PowerShell is installed) mark it for deletion +void SettingsLoader::_cleanupPowerShellInstaller(bool isPowerShellInstalled) +{ + const hstring pwshInstallerNamespace{ PowershellInstallationProfileGenerator::Namespace }; + if (extensionPackageMap.contains(pwshInstallerNamespace)) + { + if (const auto& fragExtList = extensionPackageMap[pwshInstallerNamespace]->Fragments(); fragExtList.Size() > 0) + { + Json::StreamWriterBuilder styledWriter; + styledWriter["indentation"] = " "; + styledWriter["commentStyle"] = "All"; + + auto fragExt = get_self(fragExtList.GetAt(0)); + + // We want the comment to be the first thing in the object, + // "closeOnExit" is the first property, so target that. + auto fragExtJson = _parseJSON(til::u16u8(fragExt->Json())); + fragExtJson[JsonKey(ProfilesKey)][0]["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore); + fragExt->Json(hstring{ til::u8u16(Json::writeString(styledWriter, fragExtJson)) }); + + if (const auto& profileEntryList = fragExt->NewProfilesView(); profileEntryList.Size() > 0) + { + auto profileEntry = get_self(profileEntryList.GetAt(0)); + + // We want the comment to be the first thing in the object, + // "closeOnExit" is the first property, so target that. + auto profileJson = _parseJSON(til::u16u8(profileEntry->Json())); + profileJson["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore); + profileEntry->Json(hstring{ til::u8u16(Json::writeString(styledWriter, profileJson)) }); + + // If PowerShell is installed, mark the installer profile for deletion + if (isPowerShellInstalled) + { + const auto profileGuid = profileEntryList.GetAt(0).ProfileGuid(); + for (const auto& profile : userSettings.profiles) + { + if (profile->Guid() == profileGuid) + { + profile->Deleted(true); + break; + } + } + } + } + } + } } // A new settings.json gets a special treatment: @@ -994,7 +1059,7 @@ bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr> generatedProfiles; @@ -1588,7 +1653,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const auto activeProfileCount = gsl::narrow_cast(_activeProfiles.Size()); for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++) { - remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex)); + const auto& profile = _activeProfiles.GetAt(profileIndex); + if (!profile.Deleted()) + { + remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex)); + } } // We keep track of the "remaining profiles" - those that have not yet been resolved diff --git a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h index 9144bcb325..5ea68f2e3b 100644 --- a/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h @@ -32,6 +32,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model virtual std::wstring_view GetNamespace() const noexcept = 0; virtual std::wstring_view GetDisplayName() const noexcept = 0; virtual std::wstring_view GetIcon() const noexcept = 0; - virtual void GenerateProfiles(std::vector>& profiles) const = 0; + virtual void GenerateProfiles(std::vector>& profiles) = 0; }; }; diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index adf1610f5e..9b8937a4a5 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -90,6 +90,7 @@ KeyChordSerialization.idl + Profile.idl @@ -167,6 +168,7 @@ KeyChordSerialization.idl + Profile.idl diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index 3933a24281..be3dd79437 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -15,6 +15,9 @@ profileGeneration + + profileGeneration + profileGeneration @@ -57,6 +60,9 @@ profileGeneration + + profileGeneration + profileGeneration diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp index 01e4c491bd..047cbd9ef2 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp @@ -25,336 +25,301 @@ static constexpr std::wstring_view POWERSHELL_PREVIEW_ICON{ L"ms-appx:///Profile static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" }; static constexpr std::wstring_view POWERSHELL_PREFERRED_PROFILE_NAME{ L"PowerShell" }; -namespace -{ - enum PowerShellFlags - { - None = 0, - - // These flags are used as a sort key, so they encode some native ordering. - // They are ordered such that the "most important" flags have the largest - // impact on the sort space. For example, since we want Preview to be very polar - // we give it the highest flag value. - // The "ideal" powershell instance has 0 flags (stable, native, Program Files location) - // - // With this ordering, the sort space ends up being (for PowerShell 6) - // (numerically greater values are on the left; this is flipped in the final sort) - // - // <-- Less Valued .................................... More Valued --> - // | All instances of PS 6 | All PS7 | - // | Preview | Stable | ~~~ | - // | Non-Native | Native | Non-Native | Native | ~~~ | - // | Trd | Pack | Trd | Pack | Trd | Pack | Trd | Pack | ~~~ | - // (where Pack is a stand-in for store, scoop, dotnet, though they have their own orders, - // and Trd is a stand-in for "Traditional" (Program Files)) - // - // In short, flags with larger magnitudes are pushed further down (therefore valued less) - - // distribution method (choose one) - Store = 1 << 0, // distributed via the store - Scoop = 1 << 1, // installed via Scoop - Dotnet = 1 << 2, // installed as a dotnet global tool - Traditional = 1 << 3, // installed in traditional Program Files locations - - // native architecture (choose one) - WOWARM = 1 << 4, // non-native (Windows-on-Windows, ARM variety) - WOWx86 = 1 << 5, // non-native (Windows-on-Windows, x86 variety) - - // build type (choose one) - Preview = 1 << 6, // preview version - }; - DEFINE_ENUM_FLAG_OPERATORS(PowerShellFlags); - - struct PowerShellInstance - { - int majorVersion; // 0 = we don't know, sort last. - PowerShellFlags flags; - std::filesystem::path executablePath; - - constexpr bool operator<(const PowerShellInstance& second) const - { - if (majorVersion != second.majorVersion) - { - return majorVersion < second.majorVersion; - } - if (flags != second.flags) - { - return flags > second.flags; // flags are inverted because "0" is ideal; see above - } - return executablePath < second.executablePath; // fall back to path sorting - } - - // Method Description: - // - Generates a name, based on flags, for a powershell instance. - // Return value: - // - the name - std::wstring Name() const - { - std::wstringstream namestream; - namestream << L"PowerShell"; - - if (WI_IsFlagSet(flags, PowerShellFlags::Store)) - { - if (WI_IsFlagSet(flags, PowerShellFlags::Preview)) - { - namestream << L" Preview"; - } - namestream << L" (msix)"; - } - else if (WI_IsFlagSet(flags, PowerShellFlags::Dotnet)) - { - namestream << L" (dotnet global)"; - } - else if (WI_IsFlagSet(flags, PowerShellFlags::Scoop)) - { - namestream << L" (scoop)"; - } - else - { - if (majorVersion < 7) - { - namestream << L" Core"; - } - if (majorVersion != 0) - { - namestream << L" " << majorVersion; - } - if (WI_IsFlagSet(flags, PowerShellFlags::Preview)) - { - namestream << L" Preview"; - } - if (WI_IsFlagSet(flags, PowerShellFlags::WOWx86)) - { - namestream << L" (x86)"; - } - if (WI_IsFlagSet(flags, PowerShellFlags::WOWARM)) - { - namestream << L" (ARM)"; - } - } - return namestream.str(); - } - }; -} - using namespace ::Microsoft::Terminal::Settings::Model; -using namespace winrt::Microsoft::Terminal::Settings::Model; -// Function Description: -// - Finds all powershell instances with the traditional layout under a directory. -// - The "traditional" directory layout requires that pwsh.exe exist in a versioned directory, as in -// ROOT\6\pwsh.exe -// Arguments: -// - directory: the directory under which to search -// - flags: flags to apply to all found instances -// - out: the list into which to accumulate these instances. -static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstring_view directory, PowerShellFlags flags, std::vector& out) +namespace winrt::Microsoft::Terminal::Settings::Model { - const std::filesystem::path root{ wil::ExpandEnvironmentStringsW(directory.data()) }; - if (std::filesystem::exists(root)) + DEFINE_ENUM_FLAG_OPERATORS(PowershellCoreProfileGenerator::PowerShellFlags); + + constexpr bool PowershellCoreProfileGenerator::PowerShellInstance::operator<(const PowerShellInstance& second) const { - for (const auto& versionedDir : std::filesystem::directory_iterator(root)) + if (majorVersion != second.majorVersion) { - const auto versionedPath = versionedDir.path(); - const auto executable = versionedPath / PWSH_EXE; - if (std::filesystem::exists(executable)) + return majorVersion < second.majorVersion; + } + if (flags != second.flags) + { + return flags > second.flags; // flags are inverted because "0" is ideal; see above + } + return executablePath < second.executablePath; // fall back to path sorting + } + + // Method Description: + // - Generates a name, based on flags, for a powershell instance. + // Return value: + // - the name + std::wstring PowershellCoreProfileGenerator::PowerShellInstance::Name() const + { + std::wstringstream namestream; + namestream << L"PowerShell"; + + if (WI_IsFlagSet(flags, PowerShellFlags::Store)) + { + if (WI_IsFlagSet(flags, PowerShellFlags::Preview)) { - const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos; - const auto previewFlag = preview ? PowerShellFlags::Preview : PowerShellFlags::None; - out.emplace_back(PowerShellInstance{ std::stoi(versionedPath.filename()), - PowerShellFlags::Traditional | flags | previewFlag, - executable }); + namestream << L" Preview"; + } + namestream << L" (msix)"; + } + else if (WI_IsFlagSet(flags, PowerShellFlags::Dotnet)) + { + namestream << L" (dotnet global)"; + } + else if (WI_IsFlagSet(flags, PowerShellFlags::Scoop)) + { + namestream << L" (scoop)"; + } + else + { + if (majorVersion < 7) + { + namestream << L" Core"; + } + if (majorVersion != 0) + { + namestream << L" " << majorVersion; + } + if (WI_IsFlagSet(flags, PowerShellFlags::Preview)) + { + namestream << L" Preview"; + } + if (WI_IsFlagSet(flags, PowerShellFlags::WOWx86)) + { + namestream << L" (x86)"; + } + if (WI_IsFlagSet(flags, PowerShellFlags::WOWARM)) + { + namestream << L" (ARM)"; + } + } + return namestream.str(); + } + + // Function Description: + // - Finds all powershell instances with the traditional layout under a directory. + // - The "traditional" directory layout requires that pwsh.exe exist in a versioned directory, as in + // ROOT\6\pwsh.exe + // Arguments: + // - directory: the directory under which to search + // - flags: flags to apply to all found instances + // - out: the list into which to accumulate these instances. + static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstring_view directory, PowershellCoreProfileGenerator::PowerShellFlags flags, std::vector& out) + { + const std::filesystem::path root{ wil::ExpandEnvironmentStringsW(directory.data()) }; + if (std::filesystem::exists(root)) + { + for (const auto& versionedDir : std::filesystem::directory_iterator(root)) + { + const auto versionedPath = versionedDir.path(); + const auto executable = versionedPath / PWSH_EXE; + if (std::filesystem::exists(executable)) + { + const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos; + const auto previewFlag = preview ? PowershellCoreProfileGenerator::PowerShellFlags::Preview : PowershellCoreProfileGenerator::PowerShellFlags::None; + out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ std::stoi(versionedPath.filename()), + PowershellCoreProfileGenerator::PowerShellFlags::Traditional | flags | previewFlag, + executable }); + } } } } -} -// Function Description: -// - Finds the store package, if one exists, for a given package family name -// Arguments: -// - packageFamilyName: the package family name -// Return Value: -// - a package, or nullptr. -static winrt::Windows::ApplicationModel::Package _getStorePackage(const std::wstring_view packageFamilyName) noexcept -try -{ - winrt::Windows::Management::Deployment::PackageManager packageManager; - auto foundPackages = packageManager.FindPackagesForUser(L"", packageFamilyName); - auto iterator = foundPackages.First(); - if (!iterator.HasCurrent()) + // Function Description: + // - Finds the store package, if one exists, for a given package family name + // Arguments: + // - packageFamilyName: the package family name + // Return Value: + // - a package, or nullptr. + static winrt::Windows::ApplicationModel::Package _getStorePackage(const std::wstring_view packageFamilyName) noexcept + try { + winrt::Windows::Management::Deployment::PackageManager packageManager; + auto foundPackages = packageManager.FindPackagesForUser(L"", packageFamilyName); + auto iterator = foundPackages.First(); + if (!iterator.HasCurrent()) + { + return nullptr; + } + return iterator.Current(); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); return nullptr; } - return iterator.Current(); -} -catch (...) -{ - LOG_CAUGHT_EXCEPTION(); - return nullptr; -} -// Function Description: -// - Finds all powershell instances that have App Execution Aliases in the standard location -// Arguments: -// - out: the list into which to accumulate these instances. -static void _accumulateStorePowerShellInstances(std::vector& out) -{ - wil::unique_cotaskmem_string localAppDataFolder; - if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder))) + // Function Description: + // - Finds all powershell instances that have App Execution Aliases in the standard location + // Arguments: + // - out: the list into which to accumulate these instances. + static void _accumulateStorePowerShellInstances(std::vector& out) { - return; - } - - std::filesystem::path appExecAliasPath{ localAppDataFolder.get() }; - appExecAliasPath /= L"Microsoft"; - appExecAliasPath /= L"WindowsApps"; - - if (std::filesystem::exists(appExecAliasPath)) - { - // App execution aliases for preview powershell - const auto previewPath = appExecAliasPath / POWERSHELL_PREVIEW_PFN; - if (std::filesystem::exists(previewPath)) + wil::unique_cotaskmem_string localAppDataFolder; + if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataFolder))) { - const auto previewPackage = _getStorePackage(POWERSHELL_PREVIEW_PFN); - if (previewPackage) - { - out.emplace_back(PowerShellInstance{ - gsl::narrow_cast(previewPackage.Id().Version().Major), - PowerShellFlags::Store | PowerShellFlags::Preview, - previewPath / PWSH_EXE }); - } + return; } - // App execution aliases for stable powershell - const auto gaPath = appExecAliasPath / POWERSHELL_PFN; - if (std::filesystem::exists(gaPath)) + std::filesystem::path appExecAliasPath{ localAppDataFolder.get() }; + appExecAliasPath /= L"Microsoft"; + appExecAliasPath /= L"WindowsApps"; + + if (std::filesystem::exists(appExecAliasPath)) { - const auto gaPackage = _getStorePackage(POWERSHELL_PFN); - if (gaPackage) + // App execution aliases for preview powershell + const auto previewPath = appExecAliasPath / POWERSHELL_PREVIEW_PFN; + if (std::filesystem::exists(previewPath)) { - out.emplace_back(PowerShellInstance{ - gaPackage.Id().Version().Major, - PowerShellFlags::Store, - gaPath / PWSH_EXE, - }); + const auto previewPackage = _getStorePackage(POWERSHELL_PREVIEW_PFN); + if (previewPackage) + { + out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ + gsl::narrow_cast(previewPackage.Id().Version().Major), + PowershellCoreProfileGenerator::PowerShellFlags::Store | PowershellCoreProfileGenerator::PowerShellFlags::Preview, + previewPath / PWSH_EXE }); + } + } + + // App execution aliases for stable powershell + const auto gaPath = appExecAliasPath / POWERSHELL_PFN; + if (std::filesystem::exists(gaPath)) + { + const auto gaPackage = _getStorePackage(POWERSHELL_PFN); + if (gaPackage) + { + out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ + gaPackage.Id().Version().Major, + PowershellCoreProfileGenerator::PowerShellFlags::Store, + gaPath / PWSH_EXE, + }); + } } } } -} -// Function Description: -// - Finds a powershell instance that's just a pwsh.exe in a folder. -// - This function cannot determine the version number of such a powershell instance. -// Arguments: -// - directory: the directory under which to search -// - flags: flags to apply to all found instances -// - out: the list into which to accumulate these instances. -static void _accumulatePwshExeInDirectory(const std::wstring_view directory, const PowerShellFlags flags, std::vector& out) -{ - const std::filesystem::path root{ wil::ExpandEnvironmentStringsW(directory.data()) }; - const auto pwshPath = root / PWSH_EXE; - if (std::filesystem::exists(pwshPath)) + // Function Description: + // - Finds a powershell instance that's just a pwsh.exe in a folder. + // - This function cannot determine the version number of such a powershell instance. + // Arguments: + // - directory: the directory under which to search + // - flags: flags to apply to all found instances + // - out: the list into which to accumulate these instances. + static void _accumulatePwshExeInDirectory(const std::wstring_view directory, const PowershellCoreProfileGenerator::PowerShellFlags flags, std::vector& out) { - out.emplace_back(PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath }); + const std::filesystem::path root{ wil::ExpandEnvironmentStringsW(directory.data()) }; + const auto pwshPath = root / PWSH_EXE; + if (std::filesystem::exists(pwshPath)) + { + out.emplace_back(PowershellCoreProfileGenerator::PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath }); + } } -} -// Function Description: -// - Builds a comprehensive priority-ordered list of powershell instances. -// Return value: -// - a comprehensive priority-ordered list of powershell instances. -static std::vector _collectPowerShellInstances() -{ - std::vector versions; + // Function Description: + // - Builds a comprehensive priority-ordered list of powershell instances. + // Return value: + // - a comprehensive priority-ordered list of powershell instances. + static std::vector _collectPowerShellInstances() + { + std::vector versions; - _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles%\\PowerShell", PowerShellFlags::None, versions); + _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::None, versions); #if defined(_M_AMD64) || defined(_M_ARM64) // No point in looking for WOW if we're not somewhere it exists - _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(x86)%\\PowerShell", PowerShellFlags::WOWx86, versions); + _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(x86)%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::WOWx86, versions); #endif #if defined(_M_ARM64) // no point in looking for WOA if we're not on ARM64 - _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowerShellFlags::WOWARM, versions); + _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowerShellFlags::WOWARM, versions); #endif - _accumulateStorePowerShellInstances(versions); + _accumulateStorePowerShellInstances(versions); - _accumulatePwshExeInDirectory(L"%USERPROFILE%\\.dotnet\\tools", PowerShellFlags::Dotnet, versions); - _accumulatePwshExeInDirectory(L"%USERPROFILE%\\scoop\\shims", PowerShellFlags::Scoop, versions); + _accumulatePwshExeInDirectory(L"%USERPROFILE%\\.dotnet\\tools", PowershellCoreProfileGenerator::PowerShellFlags::Dotnet, versions); + _accumulatePwshExeInDirectory(L"%USERPROFILE%\\scoop\\shims", PowershellCoreProfileGenerator::PowerShellFlags::Scoop, versions); - std::sort(versions.rbegin(), versions.rend()); // sort in reverse (best first) + std::sort(versions.rbegin(), versions.rend()); // sort in reverse (best first) - return versions; -} + return versions; + } -// Legacy GUIDs: -// - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336 -static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } }; + // Legacy GUIDs: + // - PowerShell Core 574e775e-4f2a-5b96-ac1e-a2962a402336 + static constexpr winrt::guid PowershellCoreGuid{ 0x574e775e, 0x4f2a, 0x5b96, { 0xac, 0x1e, 0xa2, 0x96, 0x2a, 0x40, 0x23, 0x36 } }; -std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept -{ - return PowershellCoreGeneratorNamespace; -} - -std::wstring_view PowershellCoreProfileGenerator::GetDisplayName() const noexcept -{ - return RS_(L"PowershellCoreProfileGeneratorDisplayName"); -} - -std::wstring_view PowershellCoreProfileGenerator::GetIcon() const noexcept -{ - return GENERATOR_POWERSHELL_ICON; -} - -// Method Description: -// - Checks if pwsh is installed, and if it is, creates a profile to launch it. -// Arguments: -// - -// Return Value: -// - a vector with the PowerShell Core profile, if available. -void PowershellCoreProfileGenerator::GenerateProfiles(std::vector>& profiles) const -{ - const auto psInstances = _collectPowerShellInstances(); - auto first = true; - - for (const auto& psI : psInstances) + std::wstring_view PowershellCoreProfileGenerator::GetNamespace() const noexcept { - const auto name = psI.Name(); - auto profile{ CreateDynamicProfile(name) }; + return PowershellCoreGeneratorNamespace; + } - const auto& unquotedCommandline = psI.executablePath.native(); - std::wstring quotedCommandline; - quotedCommandline.reserve(unquotedCommandline.size() + 2); - quotedCommandline.push_back(L'"'); - quotedCommandline.append(unquotedCommandline); - quotedCommandline.push_back(L'"'); - profile->Commandline(winrt::hstring{ quotedCommandline }); + std::wstring_view PowershellCoreProfileGenerator::GetDisplayName() const noexcept + { + return RS_(L"PowershellCoreProfileGeneratorDisplayName"); + } - profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); - profile->DefaultAppearance().DarkColorSchemeName(L"Campbell"); - profile->DefaultAppearance().LightColorSchemeName(L"Campbell"); - profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON }); + std::wstring_view PowershellCoreProfileGenerator::GetIcon() const noexcept + { + return GENERATOR_POWERSHELL_ICON; + } - if (first) + // Method Description: + // - Checks if pwsh is installed, and if it is, creates a profile to launch it. + // Arguments: + // - + // Return Value: + // - a vector with the PowerShell Core profile, if available. + void PowershellCoreProfileGenerator::GenerateProfiles(std::vector>& profiles) + { + GetPowerShellInstances(); + auto first = true; + + for (const auto& psI : _powerShellInstances) { - // Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID. - // This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store" - // (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version. - profile->Guid(PowershellCoreGuid); - profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME }); + const auto name = psI.Name(); + auto profile{ CreateDynamicProfile(name) }; - first = false; + const auto& unquotedCommandline = psI.executablePath.native(); + std::wstring quotedCommandline; + quotedCommandline.reserve(unquotedCommandline.size() + 2); + quotedCommandline.push_back(L'"'); + quotedCommandline.append(unquotedCommandline); + quotedCommandline.push_back(L'"'); + profile->Commandline(winrt::hstring{ quotedCommandline }); + + profile->StartingDirectory(winrt::hstring{ DEFAULT_STARTING_DIRECTORY }); + profile->DefaultAppearance().DarkColorSchemeName(L"Campbell"); + profile->DefaultAppearance().LightColorSchemeName(L"Campbell"); + profile->Icon(winrt::hstring{ WI_IsFlagSet(psI.flags, PowerShellFlags::Preview) ? POWERSHELL_PREVIEW_ICON : POWERSHELL_ICON }); + + if (first) + { + // Give the first ("algorithmically best") profile the official, and original, "PowerShell Core" GUID. + // This will turn the anchored default profile into "PowerShell Core Latest for Native Architecture through Store" + // (or the closest approximation thereof). It may choose a preview instance as the "best" if it is a higher version. + profile->Guid(PowershellCoreGuid); + profile->Name(winrt::hstring{ POWERSHELL_PREFERRED_PROFILE_NAME }); + + first = false; + } + + profiles.emplace_back(std::move(profile)); } + } - profiles.emplace_back(std::move(profile)); + std::vector PowershellCoreProfileGenerator::GetPowerShellInstances() noexcept + { + if (_powerShellInstances.empty()) + { + _powerShellInstances = _collectPowerShellInstances(); + } + return _powerShellInstances; + } + + // Function Description: + // - Returns the thing it's named for. + // Return value: + // - the thing it says in the name + const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName() + { + return POWERSHELL_PREFERRED_PROFILE_NAME; } } - -// Function Description: -// - Returns the thing it's named for. -// Return value: -// - the thing it says in the name -const std::wstring_view PowershellCoreProfileGenerator::GetPreferredPowershellProfileName() -{ - return POWERSHELL_PREFERRED_PROFILE_NAME; -} diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h index d473292e15..77f2aaa7a0 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.h @@ -25,9 +25,60 @@ namespace winrt::Microsoft::Terminal::Settings::Model public: static const std::wstring_view GetPreferredPowershellProfileName(); + enum PowerShellFlags + { + None = 0, + + // These flags are used as a sort key, so they encode some native ordering. + // They are ordered such that the "most important" flags have the largest + // impact on the sort space. For example, since we want Preview to be very polar + // we give it the highest flag value. + // The "ideal" powershell instance has 0 flags (stable, native, Program Files location) + // + // With this ordering, the sort space ends up being (for PowerShell 6) + // (numerically greater values are on the left; this is flipped in the final sort) + // + // <-- Less Valued .................................... More Valued --> + // | All instances of PS 6 | All PS7 | + // | Preview | Stable | ~~~ | + // | Non-Native | Native | Non-Native | Native | ~~~ | + // | Trd | Pack | Trd | Pack | Trd | Pack | Trd | Pack | ~~~ | + // (where Pack is a stand-in for store, scoop, dotnet, though they have their own orders, + // and Trd is a stand-in for "Traditional" (Program Files)) + // + // In short, flags with larger magnitudes are pushed further down (therefore valued less) + + // distribution method (choose one) + Store = 1 << 0, // distributed via the store + Scoop = 1 << 1, // installed via Scoop + Dotnet = 1 << 2, // installed as a dotnet global tool + Traditional = 1 << 3, // installed in traditional Program Files locations + + // native architecture (choose one) + WOWARM = 1 << 4, // non-native (Windows-on-Windows, ARM variety) + WOWx86 = 1 << 5, // non-native (Windows-on-Windows, x86 variety) + + // build type (choose one) + Preview = 1 << 6, // preview version + }; + + struct PowerShellInstance + { + int majorVersion; // 0 = we don't know, sort last. + PowerShellFlags flags; + std::filesystem::path executablePath; + + constexpr bool operator<(const PowerShellInstance& second) const; + std::wstring Name() const; + }; + std::wstring_view GetNamespace() const noexcept override; std::wstring_view GetDisplayName() const noexcept override; std::wstring_view GetIcon() const noexcept override; - void GenerateProfiles(std::vector>& profiles) const override; + void GenerateProfiles(std::vector>& profiles) override; + std::vector GetPowerShellInstances() noexcept; + + private: + std::vector _powerShellInstances; }; }; diff --git a/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp new file mode 100644 index 0000000000..3ad7e795b1 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" + +#include "PowershellInstallationProfileGenerator.h" +#include "DynamicProfileUtils.h" + +#include + +static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" }; +static constexpr std::wstring_view POWERSHELL_ICON_64{ L"ms-appx:///ProfileIcons/Powershell_black_64.png" }; + +namespace winrt::Microsoft::Terminal::Settings::Model +{ + std::wstring_view PowershellInstallationProfileGenerator::Namespace{ L"Windows.Terminal.InstallPowerShell" }; + + std::wstring_view PowershellInstallationProfileGenerator::GetNamespace() const noexcept + { + return Namespace; + } + + std::wstring_view PowershellInstallationProfileGenerator::GetDisplayName() const noexcept + { + return RS_(L"PowerShellInstallationProfileGeneratorDisplayName"); + } + + std::wstring_view PowershellInstallationProfileGenerator::GetIcon() const noexcept + { + return POWERSHELL_ICON_64; + } + + void PowershellInstallationProfileGenerator::GenerateProfiles(std::vector>& profiles) + { + auto profile{ CreateDynamicProfile(RS_(L"PowerShellInstallationProfileName")) }; + profile->Commandline(winrt::hstring{ fmt::format(FMT_COMPILE(L"cmd /k winget install --interactive --id Microsoft.PowerShell & echo. & echo {} & exit"), RS_(L"PowerShellInstallationInstallerGuidance")) }); + profile->Icon(winrt::hstring{ POWERSHELL_ICON }); + profile->CloseOnExit(CloseOnExitMode::Never); + + profiles.emplace_back(std::move(profile)); + } +} diff --git a/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.h b/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.h new file mode 100644 index 0000000000..6a3fc2ffff --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- PowershellInstallationProfileGenerator + +Abstract: +- This is the dynamic profile generator for a PowerShell stub. Checks if pwsh is + installed, and if it is NOT installed, creates a profile that installs the + latest PowerShell. + +Author(s): +- Carlos Zamora - March 2025 + +--*/ + +#pragma once + +#include "IDynamicProfileGenerator.h" + +namespace winrt::Microsoft::Terminal::Settings::Model +{ + class PowershellInstallationProfileGenerator final : public IDynamicProfileGenerator + { + public: + static std::wstring_view Namespace; + std::wstring_view GetNamespace() const noexcept override; + std::wstring_view GetDisplayName() const noexcept override; + std::wstring_view GetIcon() const noexcept override; + void GenerateProfiles(std::vector>& profiles) override; + }; +}; diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 37d9b2629b..07758f4967 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -748,6 +748,14 @@ PowerShell Profile Generator The display name of a dynamic profile generator for PowerShell + + PowerShell Installation Generator + The display name of a dynamic profile generator that installs the latest PowerShell + + + Install Latest PowerShell + The display name of a profile generated by the PowerShellInstallationProfileGenerator. This profile installs the latest PowerShell. + Azure Cloud Shell Profile Generator The display name of a dynamic profile generator for Azure Cloud Shell @@ -760,4 +768,11 @@ SSH Host Profile Generator The display name of a dynamic profile generator for SSH hosts - \ No newline at end of file + + Restart Windows Terminal to apply the new profile. + Guidance displayed by the installer directing the user to restart the app. + + + This profile only appears if PowerShell is not installed + + diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp index 45711c711e..8fcb64591f 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp @@ -150,7 +150,7 @@ std::wstring_view SshHostGenerator::GetIcon() const noexcept // - // Return Value: // - -void SshHostGenerator::GenerateProfiles(std::vector>& profiles) const +void SshHostGenerator::GenerateProfiles(std::vector>& profiles) { std::wstring sshExePath; if (_tryFindSshExePath(sshExePath)) diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h index d83e62535b..2bce047161 100644 --- a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h @@ -26,7 +26,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model std::wstring_view GetNamespace() const noexcept override; std::wstring_view GetDisplayName() const noexcept override; std::wstring_view GetIcon() const noexcept override; - void GenerateProfiles(std::vector>& profiles) const override; + void GenerateProfiles(std::vector>& profiles) override; private: static const std::wregex _configKeyValueRegex; diff --git a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp index 69feb6174e..45b90eadc7 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.cpp @@ -28,7 +28,7 @@ std::wstring_view VisualStudioGenerator::GetIcon() const noexcept return IconPath; } -void VisualStudioGenerator::GenerateProfiles(std::vector>& profiles) const +void VisualStudioGenerator::GenerateProfiles(std::vector>& profiles) { const auto instances = VsSetupConfiguration::QueryInstances(); diff --git a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h index c89e7c6301..48252a5c5c 100644 --- a/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h +++ b/src/cascadia/TerminalSettingsModel/VisualStudioGenerator.h @@ -30,7 +30,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model std::wstring_view GetNamespace() const noexcept override; std::wstring_view GetDisplayName() const noexcept override; std::wstring_view GetIcon() const noexcept override; - void GenerateProfiles(std::vector>& profiles) const override; + void GenerateProfiles(std::vector>& profiles) override; class IVisualStudioProfileGenerator { diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index 2e0c81e5a5..19ad2977fc 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -239,7 +239,7 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey, // - // Return Value: // - A list of WSL profiles. -void WslDistroGenerator::GenerateProfiles(std::vector>& profiles) const +void WslDistroGenerator::GenerateProfiles(std::vector>& profiles) { auto wslRootKey{ openWslRegKey() }; if (wslRootKey) diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h index 123734523f..77cda13f35 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.h @@ -26,6 +26,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model std::wstring_view GetNamespace() const noexcept override; std::wstring_view GetDisplayName() const noexcept override; std::wstring_view GetIcon() const noexcept override; - void GenerateProfiles(std::vector>& profiles) const override; + void GenerateProfiles(std::vector>& profiles) override; }; }; From a7d6e27a48b104696f88236a5d09055a4ea1bb06 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 16 Apr 2025 17:52:06 -0700 Subject: [PATCH 2/5] apply feedback --- .../ProfileIcons/Powershell_black_64.png | Bin 3287 -> 0 bytes .../TerminalSettingsModel/CascadiaSettings.h | 4 ++-- .../CascadiaSettingsSerialization.cpp | 22 ++++++++++++------ ...PowershellInstallationProfileGenerator.cpp | 6 ++--- 4 files changed, 20 insertions(+), 12 deletions(-) delete mode 100644 src/cascadia/CascadiaPackage/ProfileIcons/Powershell_black_64.png diff --git a/src/cascadia/CascadiaPackage/ProfileIcons/Powershell_black_64.png b/src/cascadia/CascadiaPackage/ProfileIcons/Powershell_black_64.png deleted file mode 100644 index 53bbbee10b75a1932cf557acbe256a921ccc0b63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3287 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 Json; private: winrt::guid _profileGuid; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index fee3645e3a..b46e7e414a 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -179,8 +179,21 @@ SettingsLoader::SettingsLoader(const std::string_view& userJSON, const std::stri // (meaning profiles specified by the application rather by the user). void SettingsLoader::GenerateProfiles() { - PowershellCoreProfileGenerator powerShellGenerator{}; - _executeGenerator(powerShellGenerator); + { + PowershellCoreProfileGenerator powerShellGenerator{}; + _executeGenerator(powerShellGenerator); + + const auto isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty(); + if (!isPowerShellInstalled) + { + // Only generate the installer stub profile if PowerShell isn't installed. + PowershellInstallationProfileGenerator pwshInstallationGenerator{}; + _executeGenerator(pwshInstallationGenerator); + } + + // Regardless of running the installer's generator, we need to do some cleanup still. + _cleanupPowerShellInstaller(isPowerShellInstalled); + } WslDistroGenerator wslGenerator{}; _executeGenerator(wslGenerator); @@ -195,11 +208,6 @@ void SettingsLoader::GenerateProfiles() SshHostGenerator sshGenerator{}; _executeGenerator(sshGenerator); #endif - - PowershellInstallationProfileGenerator pwshInstallationGenerator{}; - _executeGenerator(pwshInstallationGenerator); - - _cleanupPowerShellInstaller(!powerShellGenerator.GetPowerShellInstances().empty()); } // Retrieve the "Install Latest PowerShell" profile and... diff --git a/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp index 3ad7e795b1..27d0985e7e 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellInstallationProfileGenerator.cpp @@ -9,7 +9,7 @@ #include static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" }; -static constexpr std::wstring_view POWERSHELL_ICON_64{ L"ms-appx:///ProfileIcons/Powershell_black_64.png" }; +static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.png" }; namespace winrt::Microsoft::Terminal::Settings::Model { @@ -27,13 +27,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model std::wstring_view PowershellInstallationProfileGenerator::GetIcon() const noexcept { - return POWERSHELL_ICON_64; + return GENERATOR_POWERSHELL_ICON; } void PowershellInstallationProfileGenerator::GenerateProfiles(std::vector>& profiles) { auto profile{ CreateDynamicProfile(RS_(L"PowerShellInstallationProfileName")) }; - profile->Commandline(winrt::hstring{ fmt::format(FMT_COMPILE(L"cmd /k winget install --interactive --id Microsoft.PowerShell & echo. & echo {} & exit"), RS_(L"PowerShellInstallationInstallerGuidance")) }); + profile->Commandline(winrt::hstring{ fmt::format(FMT_COMPILE(L"cmd /k winget install --interactive --id Microsoft.PowerShell --source winget & echo. & echo {} & exit"), RS_(L"PowerShellInstallationInstallerGuidance")) }); profile->Icon(winrt::hstring{ POWERSHELL_ICON }); profile->CloseOnExit(CloseOnExitMode::Never); From cc1d6325987648d7f8f0dda8f23826b72c6aeb86 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 18 Apr 2025 14:57:00 -0700 Subject: [PATCH 3/5] fix ARM64 --- .../TerminalSettingsModel/PowershellCoreProfileGenerator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp index 047cbd9ef2..fa872fd38e 100644 --- a/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/PowershellCoreProfileGenerator.cpp @@ -229,7 +229,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model #endif #if defined(_M_ARM64) // no point in looking for WOA if we're not on ARM64 - _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowerShellFlags::WOWARM, versions); + _accumulateTraditionalLayoutPowerShellInstancesInDirectory(L"%ProgramFiles(Arm)%\\PowerShell", PowershellCoreProfileGenerator::PowerShellFlags::WOWARM, versions); #endif _accumulateStorePowerShellInstances(versions); From 0fe444e07afa71461beb9d0192ab416585bfc3ae Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 18 Apr 2025 16:14:23 -0700 Subject: [PATCH 4/5] add feature flag --- .../CascadiaSettingsSerialization.cpp | 19 +++++++++++-------- src/features.xml | 12 ++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index b46e7e414a..9156243ccb 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -183,16 +183,19 @@ void SettingsLoader::GenerateProfiles() PowershellCoreProfileGenerator powerShellGenerator{}; _executeGenerator(powerShellGenerator); - const auto isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty(); - if (!isPowerShellInstalled) + if (Feature_PowerShellInstallerProfileGenerator::IsEnabled()) { - // Only generate the installer stub profile if PowerShell isn't installed. - PowershellInstallationProfileGenerator pwshInstallationGenerator{}; - _executeGenerator(pwshInstallationGenerator); + const auto isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty(); + if (!isPowerShellInstalled) + { + // Only generate the installer stub profile if PowerShell isn't installed. + PowershellInstallationProfileGenerator pwshInstallationGenerator{}; + _executeGenerator(pwshInstallationGenerator); + } + + // Regardless of running the installer's generator, we need to do some cleanup still. + _cleanupPowerShellInstaller(isPowerShellInstalled); } - - // Regardless of running the installer's generator, we need to do some cleanup still. - _cleanupPowerShellInstaller(isPowerShellInstalled); } WslDistroGenerator wslGenerator{}; diff --git a/src/features.xml b/src/features.xml index 569acb9d6e..a384cf8662 100644 --- a/src/features.xml +++ b/src/features.xml @@ -197,4 +197,16 @@ + + Feature_PowerShellInstallerProfileGenerator + Enables the PowerShell Installer Dynamic Profile Generator + 18639 + AlwaysDisabled + + Dev + Canary + Preview + + + From 36d28e2ba99445a8255d3102f9a3e008a565a439 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 21 Apr 2025 15:35:27 -0700 Subject: [PATCH 5/5] fix rebase --- src/cascadia/TerminalSettingsModel/CascadiaSettings.h | 2 +- .../TerminalSettingsModel/CascadiaSettingsSerialization.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index be474bbbaa..be81e4de57 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -266,7 +266,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation public: FragmentSettings(hstring source, hstring json, hstring filename) : _source{ source }, - _json{ json }, + _Json{ json }, _filename{ filename } {} hstring Source() const noexcept { return _source; } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 9156243ccb..191ecbae0e 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -192,7 +192,7 @@ void SettingsLoader::GenerateProfiles() PowershellInstallationProfileGenerator pwshInstallationGenerator{}; _executeGenerator(pwshInstallationGenerator); } - + // Regardless of running the installer's generator, we need to do some cleanup still. _cleanupPowerShellInstaller(isPowerShellInstalled); }