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).
210211void 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.
229260void 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
0 commit comments