From d29e01f1888199c82903e319811c916102746e30 Mon Sep 17 00:00:00 2001 From: Christian Gaarden Gaardmark Date: Fri, 30 May 2025 18:13:25 +0200 Subject: [PATCH 1/4] Initial version Tested on Windows 11 x64 --- installer/PowerToysSetup/Product.wxs | 13 ++ .../CustomAction.cpp | 63 ++++++++++ .../CustomAction.def | 1 + .../SettingsXAML/Views/NewPlusPage.xaml | 9 ++ .../Settings.UI/Strings/en-us/Resources.resw | 7 ++ .../ViewModels/NewPlusViewModel.cs | 113 +++++++++++++++++- 6 files changed, 205 insertions(+), 1 deletion(-) diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index f15b8a471422..d353d455be72 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -206,6 +206,11 @@ NOT Installed + + + Installed AND (REMOVE="ALL") + + + + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index d1898eea32b6..027b71f69d9b 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -1153,6 +1153,69 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall) return WcaFinalize(er); } +UINT __stdcall RestoreBuiltInNewContextMenuCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "RestoreBuiltInNewContextMenuCA"); + + try + { + const std::wstring builtInNewRegistryPath = LR"(Directory\Background\shellex\ContextMenuHandlers\New)"; + const std::wstring newDisabledValuePrefix = L"0_"; + + HKEY newRegistryPath; + const LONG openStatus = RegOpenKeyExW(HKEY_CLASSES_ROOT, builtInNewRegistryPath.c_str(), 0, KEY_READ | KEY_WRITE, &newRegistryPath); + + if (openStatus != ERROR_SUCCESS) + { + throw std::runtime_error("Failed to open New context menu registry key."); + } + + wchar_t buffer[256]; + DWORD bufferSize = sizeof(buffer); + const LONG queryStatus = RegQueryValueExW(newRegistryPath, nullptr, nullptr, nullptr, reinterpret_cast(buffer), &bufferSize); + + if (queryStatus != ERROR_SUCCESS) + { + RegCloseKey(newRegistryPath); + throw std::runtime_error("Failed to read New context menu registry key."); + } + + const std::wstring builtInNewHandlerValue(buffer); + const bool startsWithPrefix = builtInNewHandlerValue.find(newDisabledValuePrefix) == 0; + + if (!startsWithPrefix) + { + RegCloseKey(newRegistryPath); + return ERROR_SUCCESS; + } + + std::wstring newEnabledValue = builtInNewHandlerValue.substr(newDisabledValuePrefix.length()); + LONG setStatus = RegSetValueExW(newRegistryPath, nullptr, 0, REG_SZ, reinterpret_cast(newEnabledValue.c_str()), static_cast((newEnabledValue.length() + 1)) * sizeof(wchar_t)); + + if (setStatus != ERROR_SUCCESS) + { + RegCloseKey(newRegistryPath); + throw std::runtime_error("Failed to update/restore the New context menu shell extension in the registry."); + } + + RegCloseKey(newRegistryPath); + } + catch (std::exception& e) + { + std::string errorMessage{ "Exception thrown while trying to restore built-in New: " }; + errorMessage += e.what(); + Logger::error(errorMessage); + + er = ERROR_INSTALL_FAILURE; + } + + er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; + return WcaFinalize(er); +} + UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def index 6e42da27c581..ee82df09e66d 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -28,3 +28,4 @@ EXPORTS UninstallCommandNotFoundModuleCA UpgradeCommandNotFoundModuleCA UnsetAdvancedPasteAPIKeyCA + RestoreBuiltInNewContextMenuCA diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml index 45c1856982c6..c1666251a15c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml @@ -84,6 +84,15 @@ + + + + + diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index f4d91465d0b7..67c512eecd60 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -4451,6 +4451,10 @@ Activate by holding the key for the character you want to add an accent to, then This option is useful when using digits, spaces and dots at the beginning of filenames to control the display order of templates Template filename starting digits settings toggle + + Hide Windows' built-in New context menu + Localize New in accordance with Windows New + Behavior New+ behavior related settings label @@ -5047,4 +5051,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Bug report package is being created + + To change this setting you'll need to run PowerToys as administrator. You can restart PowerToys as administrator on the General page. + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs index 0bd44b4fbfc2..4f048183ad97 100644 --- a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Security.Principal; using System.Text.Json; using System.Threading.Tasks; using System.Windows; @@ -17,7 +18,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Utilities; using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands; using Microsoft.PowerToys.Settings.UI.SerializationContext; - +using Microsoft.Win32; using static Microsoft.PowerToys.Settings.UI.Helpers.ShellGetFolder; namespace Microsoft.PowerToys.Settings.UI.ViewModels @@ -31,6 +32,9 @@ public partial class NewPlusViewModel : Observable private NewPlusSettings Settings { get; set; } private const string ModuleName = NewPlusSettings.ModuleName; + private const string BuiltInNewRegistryPath = @"HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\New"; + private const string NewDisabledValuePrefix = "0_"; + private const string BuiltNewCOMGuid = "{D969A300-E7FF-11d0-A93B-00A0C90F2719}"; public NewPlusViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc) { @@ -51,6 +55,11 @@ public NewPlusViewModel(ISettingsUtils settingsUtils, ISettingsRepository _isNewPlusEnabled && _replaceVariablesIsGPOConfigured; + public bool IsDisableBuiltInNewSettingsCardEnabled => _isNewPlusEnabled && _isElevated; + public bool HideStartingDigits { get => _hideStartingDigits; @@ -206,11 +217,44 @@ public bool ReplaceVariables } } + public bool HideBuiltInNew + { + get + { + return _disableBuiltInNew; + } + + set + { + if (_disableBuiltInNew != value) + { + if (_disableBuiltInNew) + { + EnableBuiltInNew(); + } + else + { + DisableBuiltInNew(); + } + + _disableBuiltInNew = value; + OnPropertyChanged(nameof(DisableBuiltInNew)); + + NotifySettingsChanged(); + } + } + } + public bool IsEnabledGpoConfigured { get => _enabledStateIsGPOConfigured; } + public bool IsNotElevated + { + get => !_isElevated; + } + public ButtonClickCommand OpenCurrentNewTemplateFolder => new ButtonClickCommand(OpenNewTemplateFolder); public ButtonClickCommand PickAnotherNewTemplateFolder => new ButtonClickCommand(PickNewTemplateFolder); @@ -271,6 +315,7 @@ public static void CopyTemplateExamples(string templateLocation) private bool _hideFileExtension; private bool _hideStartingDigits; private bool _replaceVariables; + private bool _disableBuiltInNew; private GpoRuleConfigured _enabledGpoRuleConfiguration; private bool _enabledStateIsGPOConfigured; @@ -278,6 +323,8 @@ public static void CopyTemplateExamples(string templateLocation) private bool _hideFileExtensionIsGPOConfigured; private bool _replaceVariablesIsGPOConfigured; + private bool _isElevated; + public void RefreshEnabledState() { InitializeEnabledValue(); @@ -317,5 +364,69 @@ private async Task PickFolderDialog() var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.GetSettingsWindow()); return await Task.FromResult(GetFolderDialogWithFlags(hwnd, FolderDialogFlags._BIF_NEWDIALOGSTYLE)); } + + private bool IsBuiltInNewEnabled() + { + try + { + string builtInNewHandlerValue = Registry.GetValue(BuiltInNewRegistryPath, string.Empty, null) as string; + + return IsValidRegistryCOMFormatGuid(builtInNewHandlerValue); + } + catch (Exception ex) + { + Logger.LogError("Failed to determine built-in New enablement status.", ex); + } + + return false; + } + + private static bool IsValidRegistryCOMFormatGuid(string input) + { + return Guid.TryParseExact(input, "B", out _); + } + + private void DisableBuiltInNew() + { + try + { + string builtInNewHandlerValue = Registry.GetValue(BuiltInNewRegistryPath, string.Empty, null) as string; + + if (builtInNewHandlerValue.StartsWith(NewDisabledValuePrefix, StringComparison.OrdinalIgnoreCase)) + { + // Already disabled + return; + } + + Debug.Assert(builtInNewHandlerValue == BuiltNewCOMGuid, "Unexpected GUID encountered while disabling built-in New"); + string newDisabledValue = NewDisabledValuePrefix + builtInNewHandlerValue; + Registry.SetValue(BuiltInNewRegistryPath, string.Empty, newDisabledValue); + } + catch (Exception ex) + { + Logger.LogError("Failed to disable built-in New in the registry.", ex); + MessageBox.Show(ex.Message); + } + } + + private void EnableBuiltInNew() + { + try + { + string builtInNewHandlerValue = Registry.GetValue(BuiltInNewRegistryPath, string.Empty, null) as string; + + if (builtInNewHandlerValue.StartsWith(NewDisabledValuePrefix, StringComparison.OrdinalIgnoreCase)) + { + string newEnabledValue = builtInNewHandlerValue.Substring(NewDisabledValuePrefix.Length); + Debug.Assert(newEnabledValue == BuiltNewCOMGuid, "Unexpected GUID encountered while reenabling built-in New"); + Registry.SetValue(BuiltInNewRegistryPath, string.Empty, newEnabledValue); + } + } + catch (Exception ex) + { + Logger.LogError("Failed to enable built-in New in the registry.", ex); + MessageBox.Show(ex.Message); + } + } } } From 3e6eba342eef33284c92dd357a9305d12325299d Mon Sep 17 00:00:00 2001 From: Christian Gaarden Gaardmark Date: Sun, 1 Jun 2025 11:00:06 +0200 Subject: [PATCH 2/4] Minor --- .../CustomAction.cpp | 114 +++++++++--------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 027b71f69d9b..18641c67daea 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -1155,65 +1155,61 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall) UINT __stdcall RestoreBuiltInNewContextMenuCA(MSIHANDLE hInstall) { - HRESULT hr = S_OK; - UINT er = ERROR_SUCCESS; - - hr = WcaInitialize(hInstall, "RestoreBuiltInNewContextMenuCA"); - - try - { - const std::wstring builtInNewRegistryPath = LR"(Directory\Background\shellex\ContextMenuHandlers\New)"; - const std::wstring newDisabledValuePrefix = L"0_"; - - HKEY newRegistryPath; - const LONG openStatus = RegOpenKeyExW(HKEY_CLASSES_ROOT, builtInNewRegistryPath.c_str(), 0, KEY_READ | KEY_WRITE, &newRegistryPath); - - if (openStatus != ERROR_SUCCESS) - { - throw std::runtime_error("Failed to open New context menu registry key."); - } - - wchar_t buffer[256]; - DWORD bufferSize = sizeof(buffer); - const LONG queryStatus = RegQueryValueExW(newRegistryPath, nullptr, nullptr, nullptr, reinterpret_cast(buffer), &bufferSize); - - if (queryStatus != ERROR_SUCCESS) - { - RegCloseKey(newRegistryPath); - throw std::runtime_error("Failed to read New context menu registry key."); - } - - const std::wstring builtInNewHandlerValue(buffer); - const bool startsWithPrefix = builtInNewHandlerValue.find(newDisabledValuePrefix) == 0; - - if (!startsWithPrefix) - { - RegCloseKey(newRegistryPath); - return ERROR_SUCCESS; - } - - std::wstring newEnabledValue = builtInNewHandlerValue.substr(newDisabledValuePrefix.length()); - LONG setStatus = RegSetValueExW(newRegistryPath, nullptr, 0, REG_SZ, reinterpret_cast(newEnabledValue.c_str()), static_cast((newEnabledValue.length() + 1)) * sizeof(wchar_t)); - - if (setStatus != ERROR_SUCCESS) - { - RegCloseKey(newRegistryPath); - throw std::runtime_error("Failed to update/restore the New context menu shell extension in the registry."); - } - - RegCloseKey(newRegistryPath); - } - catch (std::exception& e) - { - std::string errorMessage{ "Exception thrown while trying to restore built-in New: " }; - errorMessage += e.what(); - Logger::error(errorMessage); - - er = ERROR_INSTALL_FAILURE; - } - - er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; - return WcaFinalize(er); + // Must be run as administrator to open and modify the registry. + + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + hr = WcaInitialize(hInstall, "RestoreBuiltInNewContextMenuCA"); + + try + { + const std::wstring builtInNewRegistryPath = LR"(Directory\Background\shellex\ContextMenuHandlers\New)"; + const std::wstring newDisabledValuePrefix = L"0_"; + + auto regDeleter = [](HKEY* regKeyHandle) { if (regKeyHandle && *regKeyHandle) RegCloseKey(*regKeyHandle); delete regKeyHandle; }; + std::unique_ptr regKeyHandle(new HKEY(nullptr), regDeleter); + + const LONG openStatus = RegOpenKeyExW(HKEY_CLASSES_ROOT, builtInNewRegistryPath.c_str(), 0, KEY_READ | KEY_WRITE, regKeyHandle.get()); + if (openStatus != ERROR_SUCCESS) + { + throw std::runtime_error("Failed to open New context menu registry key."); + } + + wchar_t buffer[256]; + DWORD bufferSize = sizeof(buffer); + const LONG queryStatus = RegQueryValueExW(*regKeyHandle, nullptr, nullptr, nullptr, reinterpret_cast(buffer), &bufferSize); + if (queryStatus != ERROR_SUCCESS) + { + throw std::runtime_error("Failed to read New context menu registry key."); + } + + const std::wstring builtInNewHandlerValue(buffer); + const bool startsWithPrefix = builtInNewHandlerValue.find(newDisabledValuePrefix) == 0; + + if (!startsWithPrefix) + { + return ERROR_SUCCESS; + } + + const std::wstring builtInNewEnabledValue = builtInNewHandlerValue.substr(newDisabledValuePrefix.length()); + const LONG setStatus = RegSetValueExW(*regKeyHandle, nullptr, 0, REG_SZ, reinterpret_cast(builtInNewEnabledValue.c_str()), static_cast((builtInNewEnabledValue.length() + 1)) * sizeof(wchar_t)); + if (setStatus != ERROR_SUCCESS) + { + throw std::runtime_error("Failed to update/restore the New context menu shell extension in the registry."); + } + } + catch (const std::exception& e) + { + std::string errorMessage{ "Exception thrown while trying to restore built-in New: " }; + errorMessage += e.what(); + Logger::error(errorMessage); + + er = ERROR_INSTALL_FAILURE; + } + + er = er == ERROR_SUCCESS ? (SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE) : er; + return WcaFinalize(er); } UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) From 79c0946d34f066ee5412e7adac730cd7b5f7de93 Mon Sep 17 00:00:00 2001 From: Christian Gaarden Gaardmark Date: Sun, 1 Jun 2025 18:41:38 +0200 Subject: [PATCH 3/4] More changes --- .../SettingsXAML/Views/NewPlusPage.xaml | 2 +- .../Settings.UI/Strings/en-us/Resources.resw | 2 +- .../ViewModels/NewPlusViewModel.cs | 20 +++++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml index c1666251a15c..ccbcd76daf84 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml @@ -91,7 +91,7 @@ diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 67c512eecd60..1862f391f8bf 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -4452,7 +4452,7 @@ Activate by holding the key for the character you want to add an accent to, then Template filename starting digits settings toggle - Hide Windows' built-in New context menu + Hide the built-in New context menu Localize New in accordance with Windows New diff --git a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs index 4f048183ad97..22bb4d90a0be 100644 --- a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs @@ -55,9 +55,6 @@ public NewPlusViewModel(ISettingsUtils settingsUtils, ISettingsRepository !OSVersionHelper.IsWindows11(); @@ -173,7 +179,7 @@ public bool HideFileExtension public bool IsReplaceVariablesSettingGPOConfigured => _isNewPlusEnabled && _replaceVariablesIsGPOConfigured; - public bool IsDisableBuiltInNewSettingsCardEnabled => _isNewPlusEnabled && _isElevated; + public bool IsDisableBuiltInNewSettingsCardEnabled => _isNewPlusEnabled && IsElevated(); public bool HideStartingDigits { @@ -250,9 +256,9 @@ public bool IsEnabledGpoConfigured get => _enabledStateIsGPOConfigured; } - public bool IsNotElevated + public bool IsEnabledAndNotElevated { - get => !_isElevated; + get => _isNewPlusEnabled && !IsElevated(); } public ButtonClickCommand OpenCurrentNewTemplateFolder => new ButtonClickCommand(OpenNewTemplateFolder); @@ -323,8 +329,6 @@ public static void CopyTemplateExamples(string templateLocation) private bool _hideFileExtensionIsGPOConfigured; private bool _replaceVariablesIsGPOConfigured; - private bool _isElevated; - public void RefreshEnabledState() { InitializeEnabledValue(); From 1a3d19b23a9f15ab1f335917640ce897fda7df01 Mon Sep 17 00:00:00 2001 From: Christian Gaarden Gaardmark Date: Wed, 11 Jun 2025 13:26:53 +0200 Subject: [PATCH 4/4] Update src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs index 22bb4d90a0be..99a2bc33dd06 100644 --- a/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs @@ -244,7 +244,7 @@ public bool HideBuiltInNew } _disableBuiltInNew = value; - OnPropertyChanged(nameof(DisableBuiltInNew)); + OnPropertyChanged(nameof(HideBuiltInNew)); NotifySettingsChanged(); }