mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 18:43:54 -06:00
PRE-MERGE #18639 Introduce PowerShell installer stub
This commit is contained in:
commit
32252145f2
@ -77,6 +77,16 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
|
||||
|
||||
if (profile.Source() == L"Windows.Terminal.InstallPowerShell")
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider,
|
||||
"InstallPowerShellStubInvoked",
|
||||
TraceLoggingDescription("Event emitted when the 'Install Latest PowerShell' stub was invoked"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
}
|
||||
|
||||
// Try to handle auto-elevation
|
||||
if (_maybeElevate(newTerminalArgs, settings, profile))
|
||||
{
|
||||
|
||||
@ -170,7 +170,8 @@
|
||||
x:Uid="Extensions_NavigateToProfileButton"
|
||||
Click="NavigateToProfile_Click"
|
||||
Style="{StaticResource SettingContainerResetButtonStyle}"
|
||||
Tag="{x:Bind Profile.Guid}">
|
||||
Tag="{x:Bind Profile.Guid}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.Deleted)}">
|
||||
<FontIcon Glyph=""
|
||||
Style="{StaticResource SettingContainerFontIconStyle}" />
|
||||
</Button>
|
||||
|
||||
@ -38,7 +38,7 @@ std::wstring_view AzureCloudShellGenerator::GetIcon() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a vector with the Azure Cloud Shell connection profile, if available.
|
||||
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
if (AzureConnection::IsAzureConnectionAvailable())
|
||||
{
|
||||
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
};
|
||||
};
|
||||
|
||||
@ -129,7 +129,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
|
||||
void _addUserProfileParent(const winrt::com_ptr<implementation::Profile>& profile);
|
||||
bool _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
|
||||
static void _executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
|
||||
static void _executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
|
||||
void _patchInstallPowerShellProfile();
|
||||
winrt::com_ptr<implementation::ExtensionPackage> _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope);
|
||||
Json::StreamWriterBuilder _getJsonStyledWriter();
|
||||
|
||||
@ -247,14 +248,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; }
|
||||
til::property<hstring> Json;
|
||||
|
||||
private:
|
||||
winrt::guid _profileGuid;
|
||||
hstring _json;
|
||||
};
|
||||
|
||||
struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT<FragmentColorSchemeEntry>
|
||||
@ -277,11 +277,10 @@ 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; }
|
||||
hstring Json() const noexcept { return _json; }
|
||||
hstring Filename() const noexcept { return _filename; }
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> ModifiedProfiles() const noexcept { return _modifiedProfiles; }
|
||||
void ModifiedProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; }
|
||||
@ -289,7 +288,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void NewProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& newProfiles) noexcept { _newProfiles = newProfiles; }
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> ColorSchemes() const noexcept { return _colorSchemes; }
|
||||
void ColorSchemes(const Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry>& colorSchemes) noexcept { _colorSchemes = colorSchemes; }
|
||||
WINRT_PROPERTY(hstring, Json);
|
||||
|
||||
public:
|
||||
// views
|
||||
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; }
|
||||
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; }
|
||||
@ -297,7 +298,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
private:
|
||||
hstring _source;
|
||||
hstring _json;
|
||||
hstring _filename;
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _modifiedProfiles;
|
||||
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _newProfiles;
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
|
||||
#include "SshHostGenerator.h"
|
||||
#endif
|
||||
#include "PowershellInstallationProfileGenerator.h"
|
||||
|
||||
#include "ApplicationState.h"
|
||||
#include "DefaultTerminal.h"
|
||||
@ -209,26 +210,58 @@ Json::StreamWriterBuilder SettingsLoader::_getJsonStyledWriter()
|
||||
// (meaning profiles specified by the application rather by the user).
|
||||
void SettingsLoader::GenerateProfiles()
|
||||
{
|
||||
auto generateProfiles = [&](const IDynamicProfileGenerator& generator) {
|
||||
auto generateProfiles = [&](IDynamicProfileGenerator& generator) {
|
||||
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
|
||||
{
|
||||
_executeGenerator(generator, inboxSettings.profiles);
|
||||
}
|
||||
};
|
||||
|
||||
generateProfiles(PowershellCoreProfileGenerator{});
|
||||
generateProfiles(WslDistroGenerator{});
|
||||
generateProfiles(AzureCloudShellGenerator{});
|
||||
generateProfiles(VisualStudioGenerator{});
|
||||
{
|
||||
PowershellCoreProfileGenerator powerShellGenerator{};
|
||||
generateProfiles(powerShellGenerator);
|
||||
|
||||
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
|
||||
{
|
||||
if (!powerShellGenerator.GetPowerShellInstances().empty())
|
||||
{
|
||||
// If PowerShell is installed, mark the installer profile for deletion
|
||||
const winrt::guid profileGuid{ L"{965a10f2-b0f2-55dc-a3c2-2ddbf639bf89}" };
|
||||
for (const auto& profile : userSettings.profiles)
|
||||
{
|
||||
if (profile->Guid() == profileGuid)
|
||||
{
|
||||
profile->Deleted(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only generate the installer stub profile if PowerShell isn't installed.
|
||||
PowershellInstallationProfileGenerator pwshInstallationGenerator{};
|
||||
generateProfiles(pwshInstallationGenerator);
|
||||
}
|
||||
}
|
||||
}
|
||||
WslDistroGenerator wslGenerator{};
|
||||
generateProfiles(wslGenerator);
|
||||
|
||||
AzureCloudShellGenerator acsGenerator{};
|
||||
generateProfiles(acsGenerator);
|
||||
|
||||
VisualStudioGenerator vsGenerator{};
|
||||
generateProfiles(vsGenerator);
|
||||
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
|
||||
generateProfiles(SshHostGenerator{});
|
||||
SshHostGenerator sshGenerator{};
|
||||
generateProfiles(sshGenerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Generate ExtensionPackage objects from the profile generators.
|
||||
void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
|
||||
{
|
||||
auto generateExtensionPackages = [&](const IDynamicProfileGenerator& generator) {
|
||||
auto generateExtensionPackages = [&](IDynamicProfileGenerator& generator) {
|
||||
std::vector<winrt::com_ptr<implementation::Profile>> profilesList;
|
||||
_executeGenerator(generator, profilesList);
|
||||
|
||||
@ -255,17 +288,60 @@ void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
|
||||
extPkg->Icon(hstring{ generator.GetIcon() });
|
||||
};
|
||||
|
||||
// TODO CARLOS: is there a way to deduplicate this list?
|
||||
// Is it even worth it if we're adding special logic for the PwshInstallerGenerator PR?
|
||||
generateExtensionPackages(PowershellCoreProfileGenerator{});
|
||||
generateExtensionPackages(WslDistroGenerator{});
|
||||
generateExtensionPackages(AzureCloudShellGenerator{});
|
||||
generateExtensionPackages(VisualStudioGenerator{});
|
||||
PowershellCoreProfileGenerator powerShellGenerator{};
|
||||
generateExtensionPackages(powerShellGenerator);
|
||||
|
||||
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
|
||||
{
|
||||
PowershellInstallationProfileGenerator pwshInstallationGenerator{};
|
||||
generateExtensionPackages(pwshInstallationGenerator);
|
||||
_patchInstallPowerShellProfile();
|
||||
}
|
||||
|
||||
WslDistroGenerator wslGenerator{};
|
||||
generateExtensionPackages(wslGenerator);
|
||||
|
||||
AzureCloudShellGenerator acsGenerator{};
|
||||
generateExtensionPackages(acsGenerator);
|
||||
|
||||
VisualStudioGenerator vsGenerator{};
|
||||
generateExtensionPackages(vsGenerator);
|
||||
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
|
||||
generateExtensionPackages(SshHostGenerator{});
|
||||
SshHostGenerator sshGenerator{};
|
||||
generateExtensionPackages(sshGenerator);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Retrieve the "Install Latest PowerShell" profile and add a comment to the JSON to indicate it's conditionally applied
|
||||
void SettingsLoader::_patchInstallPowerShellProfile()
|
||||
{
|
||||
const hstring pwshInstallerNamespace{ PowershellInstallationProfileGenerator::Namespace };
|
||||
if (extensionPackageMap.contains(pwshInstallerNamespace))
|
||||
{
|
||||
if (const auto& fragExtList = extensionPackageMap[pwshInstallerNamespace]->Fragments(); fragExtList.Size() > 0)
|
||||
{
|
||||
auto fragExt = get_self<FragmentSettings>(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(_getJsonStyledWriter(), fragExtJson)) });
|
||||
|
||||
if (const auto& profileEntryList = fragExt->NewProfilesView(); profileEntryList.Size() > 0)
|
||||
{
|
||||
auto profileEntry = get_self<FragmentProfileEntry>(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(_getJsonStyledWriter(), profileJson)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A new settings.json gets a special treatment:
|
||||
// 1. The default profile is a PowerShell 7+ one, if one was generated,
|
||||
// and falls back to the standard PowerShell 5 profile otherwise.
|
||||
@ -1084,7 +1160,7 @@ bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementat
|
||||
|
||||
// As the name implies it executes a generator.
|
||||
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
|
||||
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
|
||||
void SettingsLoader::_executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
|
||||
{
|
||||
const auto generatorNamespace = generator.GetNamespace();
|
||||
const auto previousSize = profilesList.size();
|
||||
@ -1648,7 +1724,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
|
||||
auto activeProfileCount = gsl::narrow_cast<int>(_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
|
||||
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) const = 0;
|
||||
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) = 0;
|
||||
};
|
||||
};
|
||||
|
||||
@ -90,6 +90,7 @@
|
||||
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PowershellCoreProfileGenerator.h" />
|
||||
<ClInclude Include="PowershellInstallationProfileGenerator.h" />
|
||||
<ClInclude Include="Profile.h">
|
||||
<DependentUpon>Profile.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@ -167,6 +168,7 @@
|
||||
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PowershellCoreProfileGenerator.cpp" />
|
||||
<ClCompile Include="PowershellInstallationProfileGenerator.cpp" />
|
||||
<ClCompile Include="Profile.cpp">
|
||||
<DependentUpon>Profile.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
||||
@ -15,6 +15,9 @@
|
||||
<ClCompile Include="PowershellCoreProfileGenerator.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PowershellInstallationProfileGenerator.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WslDistroGenerator.cpp">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClCompile>
|
||||
@ -57,6 +60,9 @@
|
||||
<ClInclude Include="PowershellCoreProfileGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PowershellInstallationProfileGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WslDistroGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@ -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<PowerShellInstance>& out)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(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<PowershellCoreProfileGenerator::PowerShellInstance>& out)
|
||||
{
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(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<PowerShellInstance>& 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<PowershellCoreProfileGenerator::PowerShellInstance>& 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<int>(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<int>(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<PowerShellInstance>& out)
|
||||
{
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(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<PowershellCoreProfileGenerator::PowerShellInstance>& out)
|
||||
{
|
||||
out.emplace_back(PowerShellInstance{ 0 /* we can't tell */, flags, pwshPath });
|
||||
const std::filesystem::path root{ wil::ExpandEnvironmentStringsW<std::wstring>(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<PowerShellInstance> _collectPowerShellInstances()
|
||||
{
|
||||
std::vector<PowerShellInstance> 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<PowershellCoreProfileGenerator::PowerShellInstance> _collectPowerShellInstances()
|
||||
{
|
||||
std::vector<PowershellCoreProfileGenerator::PowerShellInstance> 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", PowershellCoreProfileGenerator::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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a vector with the PowerShell Core profile, if available.
|
||||
void PowershellCoreProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& 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:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a vector with the PowerShell Core profile, if available.
|
||||
void PowershellCoreProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& 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::PowerShellInstance> 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;
|
||||
}
|
||||
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
std::vector<PowerShellInstance> GetPowerShellInstances() noexcept;
|
||||
|
||||
private:
|
||||
std::vector<PowerShellInstance> _powerShellInstances;
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "PowershellInstallationProfileGenerator.h"
|
||||
#include "DynamicProfileUtils.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::wstring_view POWERSHELL_ICON{ L"ms-appx:///ProfileIcons/pwsh.png" };
|
||||
static constexpr std::wstring_view GENERATOR_POWERSHELL_ICON{ L"ms-appx:///ProfileGeneratorIcons/PowerShell.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 GENERATOR_POWERSHELL_ICON;
|
||||
}
|
||||
|
||||
void PowershellInstallationProfileGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
auto profile{ CreateDynamicProfile(RS_(L"PowerShellInstallationProfileName")) };
|
||||
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);
|
||||
|
||||
profiles.emplace_back(std::move(profile));
|
||||
}
|
||||
}
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
};
|
||||
};
|
||||
@ -1018,6 +1018,14 @@
|
||||
<value>PowerShell Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for PowerShell</comment>
|
||||
</data>
|
||||
<data name="PowershellInstallationProfileGeneratorDisplayName" xml:space="preserve">
|
||||
<value>PowerShell Installation Generator</value>
|
||||
<comment>The display name of a dynamic profile generator that installs the latest PowerShell</comment>
|
||||
</data>
|
||||
<data name="PowershellInstallationProfileName" xml:space="preserve">
|
||||
<value>Install Latest PowerShell</value>
|
||||
<comment>The display name of a profile generated by the PowerShellInstallationProfileGenerator. This profile installs the latest PowerShell.</comment>
|
||||
</data>
|
||||
<data name="AzureCloudShellGeneratorDisplayName" xml:space="preserve">
|
||||
<value>Azure Cloud Shell Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for Azure Cloud Shell</comment>
|
||||
@ -1030,4 +1038,11 @@
|
||||
<value>SSH Host Profile Generator</value>
|
||||
<comment>The display name of a dynamic profile generator for SSH hosts</comment>
|
||||
</data>
|
||||
<data name="PowerShellInstallationInstallerGuidance" xml:space="preserve">
|
||||
<value>Restart Windows Terminal to apply the new profile.</value>
|
||||
<comment>Guidance displayed by the installer directing the user to restart the app.</comment>
|
||||
</data>
|
||||
<data name="PowerShellInstallationProfileJsonComment" xml:space="preserve">
|
||||
<value>This profile only appears if PowerShell is not installed</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@ -150,7 +150,7 @@ std::wstring_view SshHostGenerator::GetIcon() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <A list of SSH host profiles.>
|
||||
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
std::wstring sshExePath;
|
||||
if (_tryFindSshExePath(sshExePath))
|
||||
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
|
||||
private:
|
||||
static const std::wregex _configKeyValueRegex;
|
||||
|
||||
@ -28,7 +28,7 @@ std::wstring_view VisualStudioGenerator::GetIcon() const noexcept
|
||||
return IconPath;
|
||||
}
|
||||
|
||||
void VisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void VisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
const auto instances = VsSetupConfiguration::QueryInstances();
|
||||
|
||||
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
|
||||
class IVisualStudioProfileGenerator
|
||||
{
|
||||
|
||||
@ -239,7 +239,7 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey,
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - A list of WSL profiles.
|
||||
void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
|
||||
void WslDistroGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
|
||||
{
|
||||
auto wslRootKey{ openWslRegKey() };
|
||||
if (wslRootKey)
|
||||
|
||||
@ -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<winrt::com_ptr<implementation::Profile>>& profiles) const override;
|
||||
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
|
||||
};
|
||||
};
|
||||
|
||||
@ -202,4 +202,16 @@
|
||||
<alwaysDisabledReleaseTokens/>
|
||||
</feature>
|
||||
|
||||
<feature>
|
||||
<name>Feature_PowerShellInstallerProfileGenerator</name>
|
||||
<description>Enables the PowerShell Installer Dynamic Profile Generator</description>
|
||||
<id>18639</id>
|
||||
<stage>AlwaysDisabled</stage>
|
||||
<alwaysEnabledBrandingTokens>
|
||||
<brandingToken>Dev</brandingToken>
|
||||
<brandingToken>Canary</brandingToken>
|
||||
<brandingToken>Preview</brandingToken>
|
||||
</alwaysEnabledBrandingTokens>
|
||||
</feature>
|
||||
|
||||
</featureStaging>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user