Skip to content

Commit 1fe3c4a

Browse files
committed
PRE-MERGE #18639 Introduce PowerShell installer stub
2 parents fe3edca + 2445500 commit 1fe3c4a

21 files changed

+515
-298
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
@@ -156,7 +156,8 @@
156156
x:Uid="Extensions_NavigateToProfileButton"
157157
Click="NavigateToProfile_Click"
158158
Style="{StaticResource SettingContainerResetButtonStyle}"
159-
Tag="{x:Bind Profile.Guid}">
159+
Tag="{x:Bind Profile.Guid}"
160+
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(Profile.Deleted)}">
160161
<FontIcon Glyph="&#xE8A7;"
161162
Style="{StaticResource SettingContainerFontIconStyle}" />
162163
</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();
133134
winrt::com_ptr<implementation::ExtensionPackage> _registerFragment(const winrt::Microsoft::Terminal::Settings::Model::FragmentSettings& fragment, FragmentScope scope);
134135
Json::StreamWriterBuilder _getJsonStyledWriter();
135136

@@ -247,14 +248,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
247248
public:
248249
FragmentProfileEntry(winrt::guid profileGuid, hstring json) :
249250
_profileGuid{ profileGuid },
250-
_json{ json } {}
251+
Json{ json } {}
251252

252253
winrt::guid ProfileGuid() const noexcept { return _profileGuid; }
253-
hstring Json() const noexcept { return _json; }
254+
til::property<hstring> Json;
254255

255256
private:
256257
winrt::guid _profileGuid;
257-
hstring _json;
258258
};
259259

260260
struct FragmentColorSchemeEntry : FragmentColorSchemeEntryT<FragmentColorSchemeEntry>
@@ -277,27 +277,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
277277
public:
278278
FragmentSettings(hstring source, hstring json, hstring filename) :
279279
_source{ source },
280-
_json{ json },
280+
_Json{ json },
281281
_filename{ filename } {}
282282

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

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

298299
private:
299300
hstring _source;
300-
hstring _json;
301301
hstring _filename;
302302
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _modifiedProfiles;
303303
Windows::Foundation::Collections::IVector<Model::FragmentProfileEntry> _newProfiles;

src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp

Lines changed: 96 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,58 @@ 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 = [&](IDynamicProfileGenerator& generator) {
213214
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
214215
{
215216
_executeGenerator(generator, inboxSettings.profiles);
216217
}
217218
};
218219

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

228261
// Generate ExtensionPackage objects from the profile generators.
229262
void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
230263
{
231-
auto generateExtensionPackages = [&](const IDynamicProfileGenerator& generator) {
264+
auto generateExtensionPackages = [&](IDynamicProfileGenerator& generator) {
232265
std::vector<winrt::com_ptr<implementation::Profile>> profilesList;
233266
_executeGenerator(generator, profilesList);
234267

@@ -255,17 +288,60 @@ void SettingsLoader::GenerateExtensionPackagesFromProfileGenerators()
255288
extPkg->Icon(hstring{ generator.GetIcon() });
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+
PowershellCoreProfileGenerator powerShellGenerator{};
292+
generateExtensionPackages(powerShellGenerator);
293+
294+
if (Feature_PowerShellInstallerProfileGenerator::IsEnabled())
295+
{
296+
PowershellInstallationProfileGenerator pwshInstallationGenerator{};
297+
generateExtensionPackages(pwshInstallationGenerator);
298+
_patchInstallPowerShellProfile();
299+
}
300+
301+
WslDistroGenerator wslGenerator{};
302+
generateExtensionPackages(wslGenerator);
303+
304+
AzureCloudShellGenerator acsGenerator{};
305+
generateExtensionPackages(acsGenerator);
306+
307+
VisualStudioGenerator vsGenerator{};
308+
generateExtensionPackages(vsGenerator);
264309
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
265-
generateExtensionPackages(SshHostGenerator{});
310+
SshHostGenerator sshGenerator{};
311+
generateExtensionPackages(sshGenerator);
266312
#endif
267313
}
268314

315+
// Retrieve the "Install Latest PowerShell" profile and add a comment to the JSON to indicate it's conditionally applied
316+
void SettingsLoader::_patchInstallPowerShellProfile()
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->NewProfilesView(); profileEntryList.Size() > 0)
332+
{
333+
auto profileEntry = get_self<FragmentProfileEntry>(profileEntryList.GetAt(0));
334+
335+
// We want the comment to be the first thing in the object,
336+
// "closeOnExit" is the first property, so target that.
337+
auto profileJson = _parseJSON(til::u16u8(profileEntry->Json()));
338+
profileJson["closeOnExit"].setComment(til::u16u8(fmt::format(FMT_COMPILE(L"// {}"), RS_(L"PowerShellInstallationProfileJsonComment"))), Json::CommentPlacement::commentBefore);
339+
profileEntry->Json(hstring{ til::u8u16(Json::writeString(_getJsonStyledWriter(), profileJson)) });
340+
}
341+
}
342+
}
343+
}
344+
269345
// A new settings.json gets a special treatment:
270346
// 1. The default profile is a PowerShell 7+ one, if one was generated,
271347
// and falls back to the standard PowerShell 5 profile otherwise.
@@ -1084,7 +1160,7 @@ bool SettingsLoader::_addOrMergeUserColorScheme(const winrt::com_ptr<implementat
10841160

10851161
// As the name implies it executes a generator.
10861162
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
1087-
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
1163+
void SettingsLoader::_executeGenerator(IDynamicProfileGenerator& generator, std::vector<winrt::com_ptr<implementation::Profile>>& profilesList)
10881164
{
10891165
const auto generatorNamespace = generator.GetNamespace();
10901166
const auto previousSize = profilesList.size();
@@ -1648,7 +1724,11 @@ void CascadiaSettings::_resolveNewTabMenuProfiles() const
16481724
auto activeProfileCount = gsl::narrow_cast<int>(_activeProfiles.Size());
16491725
for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
16501726
{
1651-
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
1727+
const auto& profile = _activeProfiles.GetAt(profileIndex);
1728+
if (!profile.Deleted())
1729+
{
1730+
remainingProfilesMap.emplace(profileIndex, _activeProfiles.GetAt(profileIndex));
1731+
}
16521732
}
16531733

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