如何无管理员权限移除IContextMenu中的Include in library菜单项
无管理员权限移除文件夹右键“Include in library”菜单项的方案
针对你遇到的问题——无法通过verb定位“Include in library”菜单项,且不想用管理员权限修改系统注册表,这里提供一个用户态的解决方案:枚举右键菜单的所有项,通过匹配菜单项文本直接移除,完全不需要系统级权限。
实现思路
Windows右键菜单由IContextMenu::QueryContextMenu生成,尽管该菜单项没有注册verb,但它会被添加到最终的菜单句柄(hMenu)中。我们只需要在菜单生成完成、弹出之前,执行以下步骤:
- 枚举目标菜单的所有菜单项
- 获取每个项的文本,匹配“Include in library”(中文系统对应“包含到库中”)
- 调用
RemoveMenu移除匹配项
可以通过两种方式实现:一是开发自定义Shell扩展附加到文件夹右键菜单,二是使用全局窗口钩子拦截所有右键菜单事件。下面提供Shell扩展的测试代码,更易于验证。
测试代码(C++)
以下是一个最小化的Shell扩展DLL实现,专门用于移除文件夹右键菜单中的目标项:
#include <windows.h> #include <shobjidl.h> #include <shlwapi.h> #include <strsafe.h> #pragma comment(lib, "shlwapi.lib") #pragma comment(lib, "ole32.lib") // 自定义CLSID,替换为你自己生成的GUID DEFINE_GUID(CLSID_FolderMenuRemover, 0x12345678, 0x1234, 0x1234, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef); class CFolderMenuRemover : public IContextMenu, IShellExtInit { private: LONG m_cRefCount; PIDLIST_ABSOLUTE m_pidlTargetFolder; public: CFolderMenuRemover() : m_cRefCount(1), m_pidlTargetFolder(nullptr) {} ~CFolderMenuRemover() { if (m_pidlTargetFolder) ILFree(m_pidlTargetFolder); } // IUnknown 接口实现 STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) { static const QITAB qit[] = { QITABENT(CFolderMenuRemover, IContextMenu), QITABENT(CFolderMenuRemover, IShellExtInit), { nullptr } }; return QISearch(this, qit, riid, ppvObject); } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRefCount); } STDMETHODIMP_(ULONG) Release() { ULONG refCount = InterlockedDecrement(&m_cRefCount); if (refCount == 0) delete this; return refCount; } // IShellExtInit 接口实现:初始化时记录目标文件夹 STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hKeyProgID) { if (pidlFolder) { m_pidlTargetFolder = ILClone(pidlFolder); } return S_OK; } // IContextMenu 接口实现:处理菜单生成 STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { // 跳过默认菜单请求 if (uFlags & CMF_DEFAULTONLY) return S_OK; // 枚举菜单,查找并移除目标项 UINT menuItemCount = GetMenuItemCount(hMenu); for (UINT i = 0; i < menuItemCount; i++) { WCHAR szMenuItemText[256] = {0}; if (GetMenuString(hMenu, i, szMenuItemText, ARRAYSIZE(szMenuItemText), MF_BYPOSITION)) { // 匹配英文文本,中文系统替换为 L"包含到库中" if (lstrcmpiW(szMenuItemText, L"Include in library") == 0) { RemoveMenu(hMenu, i, MF_BYPOSITION); break; // 假设仅存在一个目标项 } } } // 返回0表示未添加新菜单项,仅修改现有菜单 return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); } STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pInvokeInfo) { // 本扩展不需要执行命令,直接返回未实现 return E_NOTIMPL; } STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, LPSTR pszName, UINT cchMax) { return E_NOTIMPL; } }; // 类工厂:用于创建扩展实例 class CMenuRemoverFactory : public IClassFactory { private: LONG m_cRefCount; public: CMenuRemoverFactory() : m_cRefCount(1) {} ~CMenuRemoverFactory() {} STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) { if (riid == IID_IClassFactory || riid == IID_IUnknown) { *ppvObject = static_cast<IClassFactory*>(this); AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRefCount); } STDMETHODIMP_(ULONG) Release() { ULONG refCount = InterlockedDecrement(&m_cRefCount); if (refCount == 0) delete this; return refCount; } STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) { if (pUnkOuter) return CLASS_E_NOAGGREGATION; CFolderMenuRemover* pExtension = new CFolderMenuRemover(); HRESULT hr = pExtension->QueryInterface(riid, ppvObject); pExtension->Release(); return hr; } STDMETHODIMP LockServer(BOOL fLock) { return S_OK; } }; // DLL全局变量 HINSTANCE g_hDllInstance; // DLL入口函数 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hDllInstance = hModule; DisableThreadLibraryCalls(hModule); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } // 获取类对象 STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppvObject) { if (!IsEqualCLSID(rclsid, CLSID_FolderMenuRemover)) { return CLASS_E_CLASSNOTAVAILABLE; } CMenuRemoverFactory* pFactory = new CMenuRemoverFactory(); HRESULT hr = pFactory->QueryInterface(riid, ppvObject); pFactory->Release(); return hr; } // 判断是否可卸载 STDAPI DllCanUnloadNow() { return S_OK; }
验证步骤
- 编译DLL:使用Visual Studio编译上述代码为对应系统位数(32/64位)的DLL文件。
- 用户态注册扩展:
- 打开注册表编辑器(
regedit.exe),导航到HKCU\Software\Classes\Folder\ShellEx\ContextMenuHandlers - 新建一个项,命名为
FolderMenuRemover,将其默认值设置为代码中定义的CLSID({12345678-1234-1234-1234-567890ABCDEF})
- 打开注册表编辑器(
- 测试效果:右键点击任意文件夹,检查“Include in library”(或中文“包含到库中”)项是否已消失。
- 恢复原状:删除上述新建的注册表项即可,无需管理员权限。
关键注意事项
- 本地化适配:如果是中文系统,需将代码中的匹配文本改为
L"包含到库中";其他语言系统需对应修改为当地语言的菜单项文本。 - 菜单枚举时机:必须在
QueryContextMenu执行完成后、菜单弹出前执行移除操作,上述代码的时机是正确的。 - 全局钩子替代方案:如果需要对所有文件管理器(包括第三方)生效,可以使用全局
WH_GETMESSAGE钩子拦截WM_CONTEXTMENU消息,获取hMenu后执行相同的枚举移除逻辑,同样不需要管理员权限。
内容的提问来源于stack exchange,提问作者Joe J
相关产品推荐
相关产品推荐

