Skip to content

[New+] Hide existing new - remake#44979

Merged
niels9001 merged 36 commits intomicrosoft:mainfrom
cgaarden:Hide-existing-new-(remake)
Mar 1, 2026
Merged

[New+] Hide existing new - remake#44979
niels9001 merged 36 commits intomicrosoft:mainfrom
cgaarden:Hide-existing-new-(remake)

Conversation

@cgaarden
Copy link
Copy Markdown
Contributor

@cgaarden cgaarden commented Jan 22, 2026

Summary of the Pull Request

  • Add the ability for users and admins (GPO) to control whether to display built in New on the context menu.
  • Changes to the setting are immediately reflected in the experience.
  • Built-in New is restored on uninstall.

PR Checklist

Note: Supersedes #39843

Detailed Description of the Pull Request / Additional comments

Added the ability for users' admins' to display Windows built-in New or not

I'm NOT aware of an official supported way to do this, so I'm achieving this by adding an invalid context menu handler in place of New in the Computer\HKEY_CURRENT_USER\Software\Classes\Directory\background\ShellEx\ContextMenuHandlers\New

Changes are immediate, after applying the change, built-in New is shown/hidden accordingly

Updates to New+ Settings UI
	New setting introduced to track user' preference (saved to newplus/settings.json) 
	GPO setting introduced for control New visibility via GPO (GPO wins over user preference)

Updates to New+ power_module.cpp
	When runner is running new plus will also apply built-in New admin GPO and user preference (GPO wins over user preference) to ensure correct behavior on setting restore and GPO application.
	
Updates to installer 
	Uninstall always reenable built-in "New" context menu 

Updated DevDoc
	Added a note on how to manually restore built-in New

Validation Steps Performed

Windows 11 x64
Settings UI
New+ enabled
New+ disabled
GPO setting enabled
GPO settings disabled
Manually updating newplus/settings.json

Windows 11 ARM64
I tested the reg hack manually, but didn't go through a full pass.

Windows 10 x64
NOT tested.

Windows 11, Settings, New+ Disabled and no GPO
image

Windows 11, Settings, New+ Enabled and no GPO
image

Hide built-in New: Off (the default)
image

Hide built-in New: On
image

Modern
image

Classic
image

Disabling New+ also unhide New
image

image

Windows 11, Settings, New+ Enabled and with GPO

Hide built-in New: GPO enabled
image

Hide built-in New: GPO disabled
image

POPUPWINDOW
POSITIONITEM
POWERRENAMECONTEXTMENU
@cgaarden
Copy link
Copy Markdown
Contributor Author

cgaarden commented Feb 1, 2026

Hi @niels9001 and @yeelam-gordon, do you have feedback, suggestions, ideas on how I'm hiding/restoring the built-in New? The approach? Things missing? GPO policy changes (Am I using the correct version numbers etc.) Thank you!

@yeelam-gordon
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@cgaarden
Copy link
Copy Markdown
Contributor Author

Hi @yeelam-gordon

Super n00b - is there somewhere I can see why the "azp run" failed so that I can help address any outstanding issues? And other things that you want me to address?

Thank you!

cc: @niels9001

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds functionality to New+ that allows users and admins to hide the Windows built-in "New" context menu when New+ is enabled. This addresses user requests to avoid duplicate "New" entries in the context menu. The implementation uses a registry manipulation approach to disable the built-in handler by writing an invalid value to its registry key.

Changes:

  • Added a toggle setting in New+ Settings UI to hide/show the built-in "New" context menu
  • Implemented GPO policy to allow administrators to control this behavior
  • Added installer custom action to restore the built-in "New" menu on uninstall

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/settings-ui/Settings.UI/ViewModels/NewPlusViewModel.cs Adds HideBuiltInNew property with registry manipulation methods to enable/disable built-in New context menu
src/settings-ui/Settings.UI/Strings/en-us/Resources.resw Adds localized string for the new toggle setting
src/settings-ui/Settings.UI/SettingsXAML/Views/NewPlusPage.xaml Adds UI control for hiding built-in New with GPO support
src/settings-ui/Settings.UI.Library/NewPlusProperties.cs Adds BuiltInNewHidePreference property to persist user setting
src/modules/NewPlus/NewShellExtensionContextMenu/settings.h Declares methods for getting/setting hide built-in New preference
src/modules/NewPlus/NewShellExtensionContextMenu/settings.cpp Implements settings serialization for hide built-in New preference with GPO support
src/modules/NewPlus/NewShellExtensionContextMenu/powertoys_module.cpp Applies hide built-in New setting when module initializes
src/modules/NewPlus/NewShellExtensionContextMenu/new_utilities.h Implements C++ registry manipulation functions to disable/enable built-in New
src/modules/NewPlus/NewShellExtensionContextMenu/constants.h Adds constant for settings JSON key
src/gpo/assets/en-US/PowerToys.adml Adds GPO policy descriptions for hiding built-in New
src/gpo/assets/PowerToys.admx Adds GPO policy definition for hiding built-in New
src/common/utils/gpo.h Adds GPO policy constant and getter method
src/common/GPOWrapper/GPOWrapper.idl Adds WinRT method declaration for GPO wrapper
src/common/GPOWrapper/GPOWrapper.h Adds GPO wrapper method declaration
src/common/GPOWrapper/GPOWrapper.cpp Implements GPO wrapper method
installer/PowerToysSetupVNext/Product.wxs Adds custom action to restore built-in New on uninstall
installer/PowerToysSetupCustomActionsVNext/CustomAction.def Exports new custom action function
installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp Implements custom action to restore built-in New registry value
doc/devdocs/modules/newplus.md Adds documentation on manually restoring built-in New context menu

Comment on lines +419 to +432
using (var newKey = Registry.CurrentUser.OpenSubKey(BuiltInNewRegistryPath, writable: true))
{
string builtInNewHandlerValue = newKey.GetValue(null, null) as string;

if (!string.IsNullOrEmpty(builtInNewHandlerValue) && builtInNewHandlerValue.StartsWith(NewDisabledValuePrefix, StringComparison.OrdinalIgnoreCase))
{
// Already disabled
return;
}

// Any string value will disable the built-in New handler
string newDisabledValue = NewDisabledValuePrefix + builtInNewHandlerValue;
newKey.SetValue(string.Empty, newDisabledValue);
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenSubKey can return null if the registry key doesn't exist, but the code doesn't check for this before calling GetValue and SetValue on newKey. This will cause a NullReferenceException if the registry key doesn't exist. Add a null check after OpenSubKey.

Copilot uses AI. Check for mistakes.
Comment on lines 172 to 191
void init_settings()
{
powertoy_new_enabled = NewSettingsInstance().GetEnabled();

UpdateRegistration(powertoy_new_enabled);

if (powertoy_new_enabled)
{
// NOTE: This requires that the runner is running and have loaded the new plus module.
// It's not enough for user to just invoke the context menu.
if (NewSettingsInstance().GetHideBuiltInNew())
{
newplus::utilities::disable_built_in_new_via_registry();
}
else
{
newplus::utilities::enable_built_in_new_via_registry();
}
}
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When New+ is enabled (enable() method at line 111-124), the built-in New context menu state is not updated according to user preference. The enable() method registers New+ but doesn't call the logic to hide/show the built-in New based on settings. This means if a user previously set HideBuiltInNew=true, then disabled New+, then re-enabled New+, the built-in New won't be hidden until init_settings() is called again. Consider checking NewSettingsInstance().GetHideBuiltInNew() and calling the appropriate registry function in the enable() method.

Copilot uses AI. Check for mistakes.
}

// Null key default value enables built-in New handler
newKey.DeleteValue(null, true);
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DeleteValue call passes 'true' for throwOnMissingValue, which will throw an exception if the value doesn't exist. However, you already check if the value is null at line 452 and return early, so the value should exist. The 'true' parameter seems unnecessary and could be changed to 'false' for more defensive coding, or removed entirely since false is the default.

Suggested change
newKey.DeleteValue(null, true);
newKey.DeleteValue(null);

Copilot uses AI. Check for mistakes.
private const string ModuleName = NewPlusSettings.ModuleName;

private const string BuiltInNewRegistryPath = @"Software\Classes\Directory\Background\ShellEx\ContextMenuHandlers\New";
private const string BuiltNewCOMGuid = "{D969A300-E7FF-11d0-A93B-00A0C90F2719}";
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant name "BuiltNewCOMGuid" should be "BuiltInNewCOMGuid" to match the naming convention used elsewhere (BuiltInNewRegistryPath, BuiltInNewHidePreference, etc.) and for consistency with "built-in" terminology.

Suggested change
private const string BuiltNewCOMGuid = "{D969A300-E7FF-11d0-A93B-00A0C90F2719}";
private const string BuiltInNewCOMGuid = "{D969A300-E7FF-11d0-A93B-00A0C90F2719}";

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +262
if (_disableBuiltInNew)
{
EnableBuiltInNewViaRegistry();
}
else
{
DisableBuiltInNewViaRegistry();
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for updating registry based on the toggle is inverted. The code checks the OLD value (_disableBuiltInNew) to decide which registry function to call, but it should check the NEW value (value parameter).

Currently:

  • If _disableBuiltInNew is true (currently hidden), it calls EnableBuiltInNewViaRegistry()
  • If _disableBuiltInNew is false (currently shown), it calls DisableBuiltInNewViaRegistry()

This is backwards. It should be:

  • If value is true (user wants to hide), call DisableBuiltInNewViaRegistry()
  • If value is false (user wants to show), call EnableBuiltInNewViaRegistry()

Change line 256 from "if (_disableBuiltInNew)" to "if (value)" and swap the method calls.

Suggested change
if (_disableBuiltInNew)
{
EnableBuiltInNewViaRegistry();
}
else
{
DisableBuiltInNewViaRegistry();
if (value)
{
DisableBuiltInNewViaRegistry();
}
else
{
EnableBuiltInNewViaRegistry();

Copilot uses AI. Check for mistakes.

If you enable this policy, then the built-in New context menu will be hidden, and user can only create new files and folders using New+ and the explorer toolbar New button.

If you disable this policy, then the build-in New context menu will be displayed as normal in Windows.
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "build-in" should be "built-in".

Suggested change
If you disable this policy, then the build-in New context menu will be displayed as normal in Windows.
If you disable this policy, then the built-in New context menu will be displayed as normal in Windows.

Copilot uses AI. Check for mistakes.
Comment on lines +453 to +485
inline bool disable_built_in_new_via_registry()
{
// This is implemented to support where New+ GPO is configured to
// hide the built-in New context menu but Settings UI hasn't been launched
// Mirrors the logic in DisableBuiltInNewViaRegistry in .cs

HKEY key{};

if (RegCreateKeyExW(HKEY_CURRENT_USER,
built_in_new_registry_path,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
nullptr,
&key,
nullptr) != ERROR_SUCCESS)
{
return false;
}

const auto built_in_new_registry_disabled_value_prefix_len = lstrlenW(built_in_new_registry_disabled_value_prefix);

if (RegSetValueExW(key, nullptr, 0, REG_SZ, reinterpret_cast<const BYTE*>(&built_in_new_registry_disabled_value_prefix), built_in_new_registry_disabled_value_prefix_len) != ERROR_SUCCESS)
{
RegCloseKey(key);
return true;
}

RegCloseKey(key);
return false;

}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value logic is inverted. The function returns true on RegSetValueExW failure (line 479) and false on success (line 483). This contradicts the boolean return type semantics where true typically indicates success. This should return false on RegSetValueExW failure and true on success to be consistent with standard C++ conventions and the enable_built_in_new_via_registry function which also has inverted return logic.

Copilot uses AI. Check for mistakes.
Comment on lines +448 to +460
using (var newKey = Registry.CurrentUser.OpenSubKey(BuiltInNewRegistryPath, writable: true))
{
string builtInNewHandlerValue = newKey.GetValue(null, null) as string;

if (builtInNewHandlerValue is null)
{
// Already enabled
return;
}

// Null key default value enables built-in New handler
newKey.DeleteValue(null, true);
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenSubKey can return null if the registry key doesn't exist, but the code doesn't check for this before calling GetValue and DeleteValue on newKey. This will cause a NullReferenceException if the registry key doesn't exist. Add a null check after OpenSubKey.

Copilot uses AI. Check for mistakes.
InitializeEnabledValue();
InitializeGpoValues();

_disableBuiltInNew = !IsBuiltInNewEnabled();
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization at line 59 reads the current state from the registry via IsBuiltInNewEnabled(), but doesn't consider the saved user preference in Settings.Properties.BuiltInNewHidePreference. This could cause a mismatch where the UI shows a different state than what the user last saved if the registry was modified externally or during uninstall/reinstall. Consider reading from Settings.Properties.BuiltInNewHidePreference.Value first, or reconciling the two sources during initialization. Note that the C++ module (powertoys_module.cpp line 182) uses GetHideBuiltInNew() which reads from settings, not registry.

Suggested change
_disableBuiltInNew = !IsBuiltInNewEnabled();
_disableBuiltInNew = Settings.Properties.BuiltInNewHidePreference.Value;

Copilot uses AI. Check for mistakes.
Comment on lines +394 to +405
using (var newKey = Registry.CurrentUser.OpenSubKey(BuiltInNewRegistryPath, writable: false))
{
string builtInNewHandlerValue = newKey.GetValue(null, null) as string;

if (builtInNewHandlerValue is null || string.Equals(builtInNewHandlerValue, BuiltNewCOMGuid, StringComparison.OrdinalIgnoreCase))
{
// If no default value for key, or GUID is BuiltNewCOMGuid then built-in New is enabled
return true;
}

return false;
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenSubKey can return null if the registry key doesn't exist, but the code doesn't check for this before calling methods on newKey. This will cause a NullReferenceException if the registry key "Software\Classes\Directory\Background\ShellEx\ContextMenuHandlers\New" doesn't exist. Add a null check after OpenSubKey, similar to the pattern used in KeyboardManagerViewModel.cs line 322-329.

Copilot uses AI. Check for mistakes.
@niels9001 niels9001 added the 0.98 label Feb 27, 2026
Copy link
Copy Markdown
Contributor

@yeelam-gordon yeelam-gordon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great

@yeelam-gordon
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@niels9001
Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@niels9001
Copy link
Copy Markdown
Collaborator

@cgaarden can you run xaml styler and push the updated files? That should make the ci pass

@cgaarden cgaarden changed the title [New+] Hide existing new (remake) [New+] Hide existing new - remake Feb 28, 2026
Copy link
Copy Markdown
Contributor Author

@cgaarden cgaarden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated XAML style using (note had to rename the branch as the script didn't like "(remake)"):

..pipelines\applyXamlStyling.ps1 -Main

@cgaarden
Copy link
Copy Markdown
Contributor Author

@cgaarden can you run xaml styler and push the updated files? That should make the ci pass

Ran ..pipelines\applyXamlStyling.ps1 -Main

Thanks for the pointer Niels!


- For development and testing, using the Windows 10 handler can be easier since it doesn't require signing.

## Restoring Built-in Windows New context menu
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cgaarden Would you mind opening a PR in the MS Learn docs too with this troubleshooting paragraph?

https://github.com/MicrosoftDocs/windows-dev-docs/blob/docs/hub/powertoys/newplus.md

@niels9001 niels9001 enabled auto-merge (squash) February 28, 2026 18:34
@niels9001
Copy link
Copy Markdown
Collaborator

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@niels9001 niels9001 merged commit 90e81cb into microsoft:main Mar 1, 2026
11 checks passed
@zateutsch zateutsch added this to the PowerToys 0.98 milestone Mar 4, 2026
@zateutsch zateutsch added the Product-New+ Refers to the New+ PowerToys Utility label Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.98 Product-New+ Refers to the New+ PowerToys Utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants