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).
211212void 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.
232262void 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
0 commit comments