Skip to content

Commit e32956d

Browse files
committed
PRE-MERGE #18639 Introduce PowerShell installer stub
2 parents 07dc3cd + ed88c69 commit e32956d

21 files changed

+520
-299
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
@@ -129,7 +129,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
129129
void _appendProfile(winrt::com_ptr<Profile>&& profile, const winrt::guid& guid, ParsedSettings& settings);
130130
void _addUserProfileParent(const winrt::com_ptr<implementation::Profile>& profile);
131131
bool _addOrMergeUserColorScheme(const winrt::com_ptr<implementation::ColorScheme>& colorScheme);
132-
static void _executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
132+
static void _executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList);
133+
void _patchInstallPowerShellProfile(bool isPowerShellInstalled);
133134
winrt::com_ptr<implementation::ExtensionPackage> _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope);
134135
Json::StreamWriterBuilder _getJsonStyledWriter();
135136

@@ -248,14 +249,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
248249
public:
249250
FragmentProfileEntry(winrt::guid profileGuid, hstring json) :
250251
_profileGuid{ profileGuid },
251-
_json{ json } {}
252+
Json{ json } {}
252253

253254
winrt::guid ProfileGuid() const noexcept { return _profileGuid; }
254-
hstring Json() const noexcept { return _json; }
255+
til::property<hstring> Json;
255256

256257
private:
257258
winrt::guid _profileGuid;
258-
hstring _json;
259259
};
260260

261261
struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT<FragmentColorSchemeEntry>
@@ -278,27 +278,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
278278
public:
279279
FragmentSettings(hstring source, hstring json, hstring filename) :
280280
_source{ source },
281-
_json{ json },
281+
_Json{ json },
282282
_filename{ filename } {}
283283

284284
hstring Source() const noexcept { return _source; }
285-
hstring Json() const noexcept { return _json; }
286285
hstring Filename() const noexcept { return _filename; }
287286
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> ModifiedProfiles() const noexcept { return _modifiedProfiles; }
288287
void ModifiedProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& modifiedProfiles) noexcept { _modifiedProfiles = modifiedProfiles; }
289288
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> NewProfiles() const noexcept { return _newProfiles; }
290289
void NewProfiles(const Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry>& newProfiles) noexcept { _newProfiles = newProfiles; }
291290
Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry> ColorSchemes() const noexcept { return _colorSchemes; }
292291
void ColorSchemes(const Windows::Foundation::Collections::IVector<Model::FragmentColorSchemeEntry>& colorSchemes) noexcept { _colorSchemes = colorSchemes; }
292+
WINRT_PROPERTY(hstring, Json);
293293

294+
public:
294295
// views
295296
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> ModifiedProfilesView() const noexcept { return _modifiedProfiles ? _modifiedProfiles.GetView() : nullptr; }
296297
Windows::Foundation::Collections::IVectorView<Model::FragmentProfileEntry> NewProfilesView() const noexcept { return _newProfiles ? _newProfiles.GetView() : nullptr; }
297298
Windows::Foundation::Collections::IVectorView<Model::FragmentColorSchemeEntry> ColorSchemesView() const noexcept { return _colorSchemes ? _colorSchemes.GetView() : nullptr; }
298299

299300
private:
300301
hstring _source;
301-
hstring _json;
302302
hstring _filename;
303303
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _modifiedProfiles;
304304
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _newProfiles;

src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp

Lines changed: 100 additions & 16 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"
@@ -209,26 +210,57 @@ Json::StreamWriterBuilder SettingsLoader::_getJsonStyledWriter()
209210
// (meaning profiles specified by the application rather by the user).
210211
void SettingsLoader::GenerateProfiles()
211212
{
212-
auto generateProfiles = [&](const IDynamicProfileGenerator& generator) {
213+
auto generateProfiles = [&]<typename T>() {
214+
T generator{};
213215
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
214216
{
215217
_executeGenerator(generator, inboxSettings.profiles);
216218
}
219+
return generator;
217220
};
218221

219-
generateProfiles(PowershellCoreProfileGenerator{});
220-
generateProfiles(WslDistroGenerator{});
221-
generateProfiles(AzureCloudShellGenerator{});
222-
generateProfiles(VisualStudioGenerator{});
222+
bool isPowerShellInstalled;
223+
{
224+
auto powerShellGenerator = generateProfiles.template operator()<PowershellCoreProfileGenerator>();
225+
isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty();
226+
}
227+
228+
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
229+
{
230+
if (isPowerShellInstalled)
231+
{
232+
// PowerShell is installed, mark the installer profile for deletion (if found)
233+
const winrt::guid profileGuid{ L"{965a10f2-b0f2-55dc-a3c2-2ddbf639bf89}" };
234+
for (const auto& profile : userSettings.profiles)
235+
{
236+
if (profile->Guid() == profileGuid)
237+
{
238+
profile->Deleted(true);
239+
break;
240+
}
241+
}
242+
}
243+
else
244+
{
245+
// PowerShell isn't installed --> generate the installer stub profile
246+
generateProfiles.template operator()<PowershellInstallationProfileGenerator>();
247+
}
248+
}
249+
250+
generateProfiles.template operator()<WslDistroGenerator>();
251+
generateProfiles.template operator()<AzureCloudShellGenerator>();
252+
generateProfiles.template operator()<VisualStudioGenerator>();
253+
223254
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
224-
generateProfiles(SshHostGenerator{});
255+
generateProfiles.template operator()<SshHostGenerator>();
225256
#endif
226257
}
227258

228259
// Generate ExtensionPackage objects from the profile generators.
229260
void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
230261
{
231-
auto generateExtensionPackages = [&](const IDynamicProfileGenerator& generator) {
262+
auto generateExtensionPackages = [&]<typename T>() {
263+
T generator{};
232264
std::vector<winrt::com_ptr<implementation::Profile>> profilesList;
233265
_executeGenerator(generator, profilesList);
234266

@@ -253,19 +285,67 @@ void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
253285
auto extPkg = _registerFragment(std::move(*generatorExtension), FragmentScope::Machine);
254286
extPkg->DisplayName(hstring{ generator.GetDisplayName() });
255287
extPkg->Icon(hstring{ generator.GetIcon() });
288+
return generator;
256289
};
257290

258-
// TODO CARLOS: is there a way to deduplicate this list?
259-
// Is it even worth it if we're adding special logic for the PwshInstallerGenerator PR?
260-
generateExtensionPackages(PowershellCoreProfileGenerator{});
261-
generateExtensionPackages(WslDistroGenerator{});
262-
generateExtensionPackages(AzureCloudShellGenerator{});
263-
generateExtensionPackages(VisualStudioGenerator{});
291+
bool isPowerShellInstalled;
292+
{
293+
auto powerShellGenerator = generateExtensionPackages.template operator()<PowershellCoreProfileGenerator>();
294+
isPowerShellInstalled = !powerShellGenerator.GetPowerShellInstances().empty();
295+
}
296+
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
297+
{
298+
generateExtensionPackages.template operator()<PowershellInstallationProfileGenerator>();
299+
_patchInstallPowerShellProfile(isPowerShellInstalled);
300+
}
301+
302+
generateExtensionPackages.template operator()<WslDistroGenerator>();
303+
generateExtensionPackages.template operator()<AzureCloudShellGenerator>();
304+
generateExtensionPackages.template operator()<VisualStudioGenerator>();
264305
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
265-
generateExtensionPackages(SshHostGenerator{});
306+
generateExtensionPackages.template operator()<SshHostGenerator>();
266307
#endif
267308
}
268309

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

10851165
// As the name implies it executes a generator.
10861166
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
1087-
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
1167+
void SettingsLoader::_executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
10881168
{
10891169
const auto generatorNamespace = generator.GetNamespace();
10901170
const auto previousSize = profilesList.size();
@@ -1665,7 +1745,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
16651745
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
16661746
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
16671747
{
1668-
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
1748+
const auto& profile = _activeProfiles.GetAt(profileIndex);
1749+
if (!profile.Deleted())
1750+
{
1751+
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
1752+
}
16691753
}
16701754

16711755
// 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)