-
Notifications
You must be signed in to change notification settings - Fork 7.5k
New+ Feature - Option to hide the built-in New context menu (37545 and 37946) #39843
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
d29e01f
3e6eba3
8b6ce32
79c0946
1a3d19b
e2a376f
4a129bb
13e0d10
ceb88ff
a3cca9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have code example how to know it is per-machine vs per-user installation here to make the right decision?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. setting-ui, we can use GetCurrentInstallScope()
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @lei9444 and thanks for the feedback I'm happy to make these changes but I don't see any "New" registry key under HKEY_CURRENT_USER\Software\Classes\Directory\Background\shellex\ContextMenuHandlers Question: Alternative implementation: If PerMachine If PerUser But not sure if this is an actual scenario? Any hints on how to reproduce so that I can verify? Kind regards, |
||
| 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 @@ public NewPlusViewModel(ISettingsUtils settingsUtils, ISettingsRepository<Genera | |
| InitializeEnabledValue(); | ||
| InitializeGpoValues(); | ||
|
|
||
| _disableBuiltInNew = !IsBuiltInNewEnabled(); | ||
|
|
||
| // set the callback functions value to handle outgoing IPC message. | ||
| SendConfigMSG = ipcMSGCallBackFunc; | ||
| } | ||
|
|
@@ -96,6 +102,8 @@ public bool IsEnabled | |
| 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 @@ public bool IsEnabled | |
| } | ||
| } | ||
|
|
||
| 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 @@ public bool HideFileExtension | |
|
|
||
| public bool IsReplaceVariablesSettingGPOConfigured => _isNewPlusEnabled && _replaceVariablesIsGPOConfigured; | ||
|
|
||
| public bool IsDisableBuiltInNewSettingsCardEnabled => _isNewPlusEnabled && IsElevated(); | ||
|
|
||
| public bool HideStartingDigits | ||
| { | ||
| get => _hideStartingDigits; | ||
|
|
@@ -206,11 +223,44 @@ public bool ReplaceVariables | |
| } | ||
| } | ||
|
|
||
| public bool HideBuiltInNew | ||
| { | ||
| get | ||
| { | ||
| return _disableBuiltInNew; | ||
| } | ||
|
|
||
| set | ||
| { | ||
| if (_disableBuiltInNew != value) | ||
| { | ||
| if (_disableBuiltInNew) | ||
| { | ||
| EnableBuiltInNew(); | ||
| } | ||
| else | ||
| { | ||
| DisableBuiltInNew(); | ||
| } | ||
|
|
||
| _disableBuiltInNew = value; | ||
| OnPropertyChanged(nameof(DisableBuiltInNew)); | ||
cgaarden marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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 @@ public static void CopyTemplateExamples(string templateLocation) | |
| private bool _hideFileExtension; | ||
| private bool _hideStartingDigits; | ||
| private bool _replaceVariables; | ||
| private bool _disableBuiltInNew; | ||
|
|
||
| private GpoRuleConfigured _enabledGpoRuleConfiguration; | ||
| private bool _enabledStateIsGPOConfigured; | ||
|
|
@@ -317,5 +368,69 @@ private async Task<string> 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); | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we separate the machine-level and per-user installers? I confirmed that the built-in New context menu is only in HKLM, but HKCU can override it. So the machine-level installer only needs to write to HKLM, and the per-user installer only needs to write to HKCU — both will work and can stay clean when uninstalled.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have code example how to know it is per-machine vs per-user installation here to make the right decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could check the msi InstallScope, in installer, we could do like this
hr = WcaGetProperty(L"InstallScope", ¤tScope);
if (std::wstring{currentScope} == L"perUser")