Compare commits

...

8 Commits

Author SHA1 Message Date
Christian Gaarden Gaardmark
13e0d10048 Merge branch 'microsoft:main' into New+feature-37545-option-to-disable-existing-new 2025-08-15 21:13:36 -07:00
Christian Gaarden Gaardmark
4a129bbc6a Merge remote-tracking branch 'upstream/main' into New+feature-37545-option-to-disable-existing-new 2025-08-15 07:02:39 -07:00
Christian Gaarden Gaardmark
e2a376f637 Merge branch 'microsoft:main' into New+feature-37545-option-to-disable-existing-new 2025-06-13 09:22:36 +02:00
Christian Gaarden Gaardmark
1a3d19b23a Update src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-11 13:26:53 +02:00
Christian Gaarden Gaardmark
79c0946d34 More changes 2025-06-01 18:41:38 +02:00
Christian Gaarden Gaardmark
8b6ce32a6d Merge remote-tracking branch 'upstream/main' into New+feature-37545-option-to-disable-existing-new 2025-06-01 13:30:48 +02:00
Christian Gaarden Gaardmark
3e6eba342e Minor 2025-06-01 11:00:06 +02:00
Christian Gaarden Gaardmark
d29e01f188 Initial version
Tested on Windows 11 x64
2025-05-30 18:13:25 +02:00
6 changed files with 205 additions and 1 deletions

View File

@@ -206,6 +206,11 @@
<!-- Clean Video Conference Mute registry keys that might be around from previous installations. We've deprecated this utility since then. -->
<Custom Action="CleanVideoConferenceRegistry" Before="InstallFinalize">NOT Installed</Custom>
<!-- User may have disabled the built-in New context menu via New+ -->
<Custom Action="RestoreBuiltInNewContextMenu" Before="RemoveFiles">
Installed AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
<CustomAction Id="SetLaunchPowerToysParam"
@@ -462,6 +467,14 @@
DllEntry="InstallCmdPalPackageCA"
/>
<CustomAction Id="RestoreBuiltInNewContextMenu"
Return="ignore"
Impersonate="yes"
Execute="deferred"
BinaryKey="PTCustomActions"
DllEntry="RestoreBuiltInNewContextMenuCA"
/>
<!-- Close 'PowerToys.exe' before uninstall-->
<Property Id="MSIRESTARTMANAGERCONTROL" Value="DisableShutdown" />
<Property Id="MSIFASTINSTALL" Value="DisableShutdown" />

View File

@@ -1153,6 +1153,65 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
return WcaFinalize(er);
}
UINT __stdcall RestoreBuiltInNewContextMenuCA(MSIHANDLE hInstall)
{
// 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<HKEY, decltype(regDeleter)> 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<LPBYTE>(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<const BYTE*>(builtInNewEnabledValue.c_str()), static_cast<DWORD>((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)
{
HRESULT hr = S_OK;

View File

@@ -28,3 +28,4 @@ EXPORTS
UninstallCommandNotFoundModuleCA
UpgradeCommandNotFoundModuleCA
UnsetAdvancedPasteAPIKeyCA
RestoreBuiltInNewContextMenuCA

View File

@@ -84,6 +84,15 @@
<TextBlock x:Uid="NewPlus_Hide_Starting_Digits_Description" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="NewPlus_Hide_BuiltIn_New_Toggle" IsEnabled="{x:Bind ViewModel.IsDisableBuiltInNewSettingsCardEnabled, Mode=OneWay}">
<ToggleSwitch x:Uid="DisableBuiltInNewToggle" IsOn="{x:Bind ViewModel.HideBuiltInNew, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="Elevation_Required"
IsClosable="True"
IsOpen="{x:Bind ViewModel.IsEnabledAndNotElevated, Mode=OneWay}"
Severity="Informational" />
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="NewPlus_behavior" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">

View File

@@ -4434,6 +4434,10 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Ignores digits, spaces, and dots at the start of filenames—useful for sorting templates without showing those characters</value>
<comment>Template filename starting digits settings toggle</comment>
</data>
<data name="NewPlus_Hide_BuiltIn_New_Toggle.Header" xml:space="preserve">
<value>Hide the built-in New context menu</value>
<comment>Localize New in accordance with Windows New</comment>
</data>
<data name="NewPlus_behavior.Header" xml:space="preserve">
<value>Behavior</value>
<comment>New+ behavior related settings label</comment>
@@ -5127,4 +5131,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="KeyBack" xml:space="preserve">
<value>Back key</value>
</data>
<data name="Elevation_Required.Title" xml:space="preserve">
<value>To change this setting you'll need to run PowerToys as administrator. You can restart PowerToys as administrator on the General page.</value>
</data>
</root>

View File

@@ -6,6 +6,7 @@ using System;
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.Interfaces;
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 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
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<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc)
{
@@ -51,6 +55,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
InitializeEnabledValue();
InitializeGpoValues();
_disableBuiltInNew = !IsBuiltInNewEnabled();
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
@@ -96,6 +102,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(IsHideFileExtSettingGPOConfigured));
OnPropertyChanged(nameof(IsReplaceVariablesSettingGPOConfigured));
OnPropertyChanged(nameof(IsReplaceVariablesSettingsCardEnabled));
OnPropertyChanged(nameof(IsDisableBuiltInNewSettingsCardEnabled));
OnPropertyChanged(nameof(IsEnabledAndNotElevated));
OutGoingGeneralSettings outgoingMessage = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoingMessage.ToString());
@@ -110,6 +118,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private bool IsElevated()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
public bool IsWin10OrLower
{
get => !OSVersionHelper.IsWindows11();
@@ -164,6 +179,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public bool IsReplaceVariablesSettingGPOConfigured => _isNewPlusEnabled && _replaceVariablesIsGPOConfigured;
public bool IsDisableBuiltInNewSettingsCardEnabled => _isNewPlusEnabled && IsElevated();
public bool HideStartingDigits
{
get => _hideStartingDigits;
@@ -206,11 +223,44 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public bool HideBuiltInNew
{
get
{
return _disableBuiltInNew;
}
set
{
if (_disableBuiltInNew != value)
{
if (_disableBuiltInNew)
{
EnableBuiltInNew();
}
else
{
DisableBuiltInNew();
}
_disableBuiltInNew = value;
OnPropertyChanged(nameof(HideBuiltInNew));
NotifySettingsChanged();
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public bool IsEnabledAndNotElevated
{
get => _isNewPlusEnabled && !IsElevated();
}
public ButtonClickCommand OpenCurrentNewTemplateFolder => new ButtonClickCommand(OpenNewTemplateFolder);
public ButtonClickCommand PickAnotherNewTemplateFolder => new ButtonClickCommand(PickNewTemplateFolder);
@@ -271,6 +321,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _hideFileExtension;
private bool _hideStartingDigits;
private bool _replaceVariables;
private bool _disableBuiltInNew;
private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured;
@@ -317,5 +368,69 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
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);
}
}
}
}