Skip to content

Commit deec76f

Browse files
committed
PRE-MERGE #18639 Introduce PowerShell installer stub
2 parents 21e1e15 + abee0c6 commit deec76f

21 files changed

+518
-296
lines changed

src/cascadia/TerminalApp/TabManagement.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ namespace winrt::TerminalApp::implementation
7777
}
7878
const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) };
7979

80+
if (profile.Source() == L"Windows.Terminal.InstallPowerShell")
81+
{
82+
TraceLoggingWrite(
83+
g_hTerminalAppProvider,
84+
"InstallPowerShellStubInvoked",
85+
TraceLoggingDescription("Event emitted when the 'Install Latest PowerShell' stub was invoked"),
86+
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
87+
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
88+
}
89+
8090
// Try to handle auto-elevation
8191
if (_maybeElevate(newTerminalArgs, settings, profile))
8292
{

src/cascadia/TerminalSettingsEditor/Extensions.xaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@
170170
x:Uid="Extensions_NavigateToProfileButton"
171171
Click="NavigateToProfile_Click"
172172
Style="{StaticResource SettingContainerResetButtonStyle}"
173-
Tag="{x:Bind Profile.Guid}">
173+
Tag="{x:Bind Profile.Guid}"
174+
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.Deleted)}">
174175
<FontIcon Glyph="&#xE8A7;"
175176
Style="{StaticResource SettingContainerFontIconStyle}" />
176177
</Button>

src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ std::wstring_view AzureCloudShellGenerator::GetIcon() const noexcept
3838
// - <none>
3939
// Return Value:
4040
// - a vector with the Azure Cloud Shell connection profile, if available.
41-
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
41+
void AzureCloudShellGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles)
4242
{
4343
if (AzureConnection::IsAzureConnectionAvailable())
4444
{

src/cascadia/TerminalSettingsModel/AzureCloudShellGenerator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model
2727
std::wstring_view GetNamespace() const noexcept override;
2828
std::wstring_view GetDisplayName() const noexcept override;
2929
std::wstring_view GetIcon() const noexcept override;
30-
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
30+
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) override;
3131
};
3232
};

src/cascadia/TerminalSettingsModel/CascadiaSettings.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
130130
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
131131
void _addUserProfileParent(const winrt::com_ptr<implementation::Profile>& profile);
132132
bool _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
133-
static void _executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
133+
static void _executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
134+
void _patchInstallPowerShellProfile(bool isPowerShellInstalled);
134135
winrt::com_ptr<implementation::ExtensionPackage> _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope);
135136
Json::StreamWriterBuilder _getJsonStyledWriter();
136137

@@ -258,14 +259,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
258259
public:
259260
FragmentProfileEntry(winrt::guid profileGuid, hstring json) :
260261
_profileGuid{ profileGuid },
261-
_json{ json } {}
262+
Json{ json } {}
262263

263264
winrt::guid ProfileGuid() const noexcept { return _profileGuid; }
264-
hstring Json() const noexcept { return _json; }
265+
til::property<hstring> Json;
265266

266267
private:
267268
winrt::guid _profileGuid;
268-
hstring _json;
269269
};
270270

271271
struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT<FragmentColorSchemeEntry>
@@ -288,27 +288,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
288288
public:
289289
FragmentSettings(hstring source, hstring json, hstring filename) :
290290
_source{ source },
291-
_json{ json },
291+
_Json{ json },
292292
_filename{ filename } {}
293293

294294
hstring Source() const noexcept { return _source; }
295-
hstring Json() const noexcept { return _json; }
296295
hstring Filename() const noexcept { return _filename; }
297296
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> ModifiedProfiles() const noexcept { return _modifiedProfiles; }
298297
void ModifiedProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; }
299298
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> NewProfiles() const noexcept { return _newProfiles; }
300299
void NewProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& newProfiles) noexcept { _newProfiles = newProfiles; }
301300
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> ColorSchemes() const noexcept { return _colorSchemes; }
302301
void ColorSchemes(const Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry>& colorSchemes) noexcept { _colorSchemes = colorSchemes; }
302+
WINRT_PROPERTY(hstring, Json);
303303

304+
public:
304305
// views
305306
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; }
306307
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; }
307308
Windows::Foundation::Collections::IVectorView<Model::FragmentColorSchemeEntry> ColorSchemesView() const noexcept { return _colorSchemes ? _colorSchemes.GetView() : nullptr; }
308309

309310
private:
310311
hstring _source;
311-
hstring _json;
312312
hstring _filename;
313313
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _modifiedProfiles;
314314
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _newProfiles;

src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
2020
#include "SshHostGenerator.h"
2121
#endif
22+
#include "PowershellInstallationProfileGenerator.h"
2223

2324
#include "ApplicationState.h"
2425
#include "DefaultTerminal.h"
@@ -210,28 +211,58 @@ Json::StreamWriterBuilder SettingsLoader::_getJsonStyledWriter()
210211
// (meaning profiles specified by the application rather by the user).
211212
void SettingsLoader::GenerateProfiles()
212213
{
213-
auto generateProfiles = [&](const IDynamicProfileGenerator& generator) {
214+
auto generateProfiles = [&]<typename T>() {
215+
T generator{};
214216
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
215217
{
216218
_executeGenerator(generator, inboxSettings.profiles);
217219
}
220+
return generator;
218221
};
219222

223+
bool isPowerShellInstalled;
224+
{
225+
auto powerShellGenerator = generateProfiles.template operator()<PowershellCoreProfileGenerator>();
226+
isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty();
227+
}
228+
229+
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
230+
{
231+
if (isPowerShellInstalled)
232+
{
233+
// PowerShell is installed, mark the installer profile for deletion (if found)
234+
const winrt::guid profileGuid{ L"{965a10f2-b0f2-55dc-a3c2-2ddbf639bf89}" };
235+
for (const auto& profile : userSettings.profiles)
236+
{
237+
if (profile->Guid() == profileGuid)
238+
{
239+
profile->Deleted(true);
240+
break;
241+
}
242+
}
243+
}
244+
else
245+
{
246+
// PowerShell isn't installed --> generate the installer stub profile
247+
generateProfiles.template operator()<PowershellInstallationProfileGenerator>();
248+
}
249+
}
250+
220251
// Generate profiles for each generator and add them to the inbox settings.
221252
// Be sure to update the same list below.
222-
generateProfiles(PowershellCoreProfileGenerator{});
223-
generateProfiles(WslDistroGenerator{});
224-
generateProfiles(AzureCloudShellGenerator{});
225-
generateProfiles(VisualStudioGenerator{});
253+
generateProfiles.template operator()<WslDistroGenerator>();
254+
generateProfiles.template operator()<AzureCloudShellGenerator>();
255+
generateProfiles.template operator()<VisualStudioGenerator>();
226256
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
227-
generateProfiles(SshHostGenerator{});
257+
generateProfiles.template operator()<SshHostGenerator>();
228258
#endif
229259
}
230260

231261
// Generate ExtensionPackage objects from the profile generators.
232262
void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
233263
{
234-
auto generateExtensionPackages = [&](const IDynamicProfileGenerator& generator) {
264+
auto generateExtensionPackages = [&]<typename T>() {
265+
T generator{};
235266
std::vector<winrt::com_ptr<implementation::Profile>> profilesList;
236267
_executeGenerator(generator, profilesList);
237268

@@ -256,19 +287,69 @@ void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
256287
auto extPkg = _registerFragment(std::move(*generatorExtension), FragmentScope::Machine);
257288
extPkg->DisplayName(hstring{ generator.GetDisplayName() });
258289
extPkg->Icon(hstring{ generator.GetIcon() });
290+
return generator;
259291
};
260292

293+
bool isPowerShellInstalled;
294+
{
295+
auto powerShellGenerator = generateExtensionPackages.template operator()<PowershellCoreProfileGenerator>();
296+
isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty();
297+
}
298+
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
299+
{
300+
generateExtensionPackages.template operator()<PowershellInstallationProfileGenerator>();
301+
_patchInstallPowerShellProfile(isPowerShellInstalled);
302+
}
303+
261304
// Generate extension package objects for each generator.
262305
// Be sure to update the same list above.
263-
generateExtensionPackages(PowershellCoreProfileGenerator{});
264-
generateExtensionPackages(WslDistroGenerator{});
265-
generateExtensionPackages(AzureCloudShellGenerator{});
266-
generateExtensionPackages(VisualStudioGenerator{});
306+
generateExtensionPackages.template operator()<WslDistroGenerator>();
307+
generateExtensionPackages.template operator()<AzureCloudShellGenerator>();
308+
generateExtensionPackages.template operator()<VisualStudioGenerator>();
267309
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
268-
generateExtensionPackages(SshHostGenerator{});
310+
generateExtensionPackages.template operator()<SshHostGenerator>();
269311
#endif
270312
}
271313

314+
// Retrieve the "Install Latest PowerShell" profile and add a comment to the JSON to indicate it's conditionally applied.
315+
// If PowerShell is installed, delete the profile from the extension package.
316+
void SettingsLoader::_patchInstallPowerShellProfile(bool isPowerShellInstalled)
317+
{
318+
const hstring pwshInstallerNamespace{ PowershellInstallationProfileGenerator::Namespace };
319+
if (extensionPackageMap.contains(pwshInstallerNamespace))
320+
{
321+
if (const auto& fragExtList = extensionPackageMap[pwshInstallerNamespace]->Fragments(); fragExtList.Size() > 0)
322+
{
323+
auto fragExt = get_self<FragmentSettings>(fragExtList.GetAt(0));
324+
325+
// We want the comment to be the first thing in the object,
326+
// "closeOnExit" is the first property, so target that.
327+
auto fragExtJson = _parseJSON(til::u16u8(fragExt->Json()));
328+
fragExtJson[JsonKey(ProfilesKey)][0]["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
329+
fragExt->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), fragExtJson)) });
330+
331+
if (const auto& profileEntryList = fragExt->NewProfiles(); profileEntryList.Size() > 0)
332+
{
333+
if (isPowerShellInstalled)
334+
{
335+
// PowerShell is installed, so the installer profile was marked for deletion in GenerateProfiles().
336+
// Remove the profile object from the fragment so it doesn't show up in the settings UI.
337+
profileEntryList.RemoveAt(0);
338+
}
339+
else
340+
{
341+
// We want the comment to be the first thing in the object,
342+
// "closeOnExit" is the first property, so target that.
343+
auto profileEntry = get_self<FragmentProfileEntry>(profileEntryList.GetAt(0));
344+
auto profileJson = _parseJSON(til::u16u8(profileEntry->Json()));
345+
profileJson["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
346+
profileEntry->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) });
347+
}
348+
}
349+
}
350+
}
351+
}
352+
272353
// A new settings.json gets a special treatment:
273354
// 1. The default profile is a PowerShell 7+ one, if one was generated,
274355
// and falls back to the standard PowerShell 5 profile otherwise.
@@ -1095,7 +1176,7 @@ bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementat
10951176

10961177
// As the name implies it executes a generator.
10971178
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
1098-
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
1179+
void SettingsLoader::_executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
10991180
{
11001181
const auto generatorNamespace = generator.GetNamespace();
11011182
const auto previousSize = profilesList.size();
@@ -1679,7 +1760,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
16791760
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
16801761
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
16811762
{
1682-
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
1763+
const auto& profile = _activeProfiles.GetAt(profileIndex);
1764+
if (!profile.Deleted())
1765+
{
1766+
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
1767+
}
16831768
}
16841769

16851770
// We keep track of the "remaining profiles" - those that have not yet been resolved

src/cascadia/TerminalSettingsModel/IDynamicProfileGenerator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model
3232
virtual std::wstring_view GetNamespace() const noexcept = 0;
3333
virtual std::wstring_view GetDisplayName() const noexcept = 0;
3434
virtual std::wstring_view GetIcon() const noexcept = 0;
35-
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const = 0;
35+
virtual void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) = 0;
3636
};
3737
};

src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
9191
</ClInclude>
9292
<ClInclude Include="PowershellCoreProfileGenerator.h" />
93+
<ClInclude Include="PowershellInstallationProfileGenerator.h" />
9394
<ClInclude Include="Profile.h">
9495
<DependentUpon>Profile.idl</DependentUpon>
9596
</ClInclude>
@@ -167,6 +168,7 @@
167168
<DependentUpon>KeyChordSerialization.idl</DependentUpon>
168169
</ClCompile>
169170
<ClCompile Include="PowershellCoreProfileGenerator.cpp" />
171+
<ClCompile Include="PowershellInstallationProfileGenerator.cpp" />
170172
<ClCompile Include="Profile.cpp">
171173
<DependentUpon>Profile.idl</DependentUpon>
172174
</ClCompile>

src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
<ClCompile Include="PowershellCoreProfileGenerator.cpp">
1616
<Filter>profileGeneration</Filter>
1717
</ClCompile>
18+
<ClCompile Include="PowershellInstallationProfileGenerator.cpp">
19+
<Filter>profileGeneration</Filter>
20+
</ClCompile>
1821
<ClCompile Include="WslDistroGenerator.cpp">
1922
<Filter>profileGeneration</Filter>
2023
</ClCompile>
@@ -57,6 +60,9 @@
5760
<ClInclude Include="PowershellCoreProfileGenerator.h">
5861
<Filter>profileGeneration</Filter>
5962
</ClInclude>
63+
<ClInclude Include="PowershellInstallationProfileGenerator.h">
64+
<Filter>profileGeneration</Filter>
65+
</ClInclude>
6066
<ClInclude Include="WslDistroGenerator.h">
6167
<Filter>profileGeneration</Filter>
6268
</ClInclude>

0 commit comments

Comments
 (0)