Search code examples
c++windowswinapiregistrywindows-shell

Remove the "Include in library" item from the menu that returns IContextMenu::QueryContextMenu


I need to Remove Include in library item from menu returned by IContextMenu::QueryContextMenu for folder. The problem is that Include in library menu has no verb and I can't compare it with anything to find it in hMenu list and remove it.

Is there a way to exclude Include in library from IContextMenu::QueryContextMenu result?

One solution I found is to delete the HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\ShellEx\ContextMenuHandlers\Library Location key before QueryContextMenu and restore it after, however accessing HKEY_LOCAL_MACHINE requires admin rights.

My question is, how can I get rid of the Include in library item without admin rights?

For testing you can run the following code. You can specify your folder in "path" variable value.


#include <windows.h>
#include <shlobj.h>

LPCWSTR path = L"c:\\users\\currentuser\\folder";

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    const int buttonId = 1;
    const int buttonX = 10;
    const int buttonY = 10;
    switch (message)
    {
    case WM_CREATE:
        CreateWindow(L"BUTTON", L"Show Context menu", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, buttonX, buttonY, 200, 30, hwnd, (HMENU)(long)buttonId, nullptr, nullptr);
        break;

    case WM_COMMAND:
        if (LOWORD(wParam) == buttonId)
        {
            IShellItem* item;

            SHCreateItemFromParsingName(path, NULL, IID_PPV_ARGS(&item));
            if (item)
            {
                IContextMenu* cm;
                item->BindToHandler(nullptr, BHID_SFUIObject, IID_PPV_ARGS(&cm));
                if (cm)
                {

                    auto menu = CreatePopupMenu();
                    const int firstId = 1;
                    cm->QueryContextMenu(menu, 0, firstId, 0x7FFF, CMF_NORMAL);
                    POINT pt{ buttonX, buttonY };
                    ClientToScreen(hwnd, &pt);
                    auto cmd = TrackPopupMenu(menu, TPM_RETURNCMD, pt.x, pt.y, 0, hwnd, nullptr);
                    if (cmd)
                    {
                        CMINVOKECOMMANDINFO cmi{};
                        cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
                        cmi.lpVerb = (LPSTR)MAKEINTRESOURCE(cmd - firstId);
                        cmi.nShow = SW_SHOWNORMAL;
                        cm->InvokeCommand(&cmi);
                    }

                    cm->Release();
                    DestroyMenu(menu);
                }
                item->Release();
            }
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    auto hmod = LoadLibrary(L"shell32.dll");
    auto fileIconInit = (BOOL(WINAPI*)(BOOL))GetProcAddress(hmod, MAKEINTRESOURCEA(660));
    if (fileIconInit)
    {
        fileIconInit(TRUE);
    }

    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"MyWindowClass";
    RegisterClass(&wc);

    auto hwnd = CreateWindow(wc.lpszClassName, L"Context Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

Solution

  • Most of the Windows Shell registry calls test HKEY_CURRENT_USER before going to HKEY_LOCAL_MACHINE.

    And when they access the Software\Classes sub key, the usually do it through the HKEY_CLASSES_ROOT root key which is a merged view or both HKCU and HKLM.

    So in this case, we can use the HKEY_CURRENT_USER\Software\Classes\Folder\ShellEx\ContextMenuHandlers\Library Location key and put something invalid for the Shell in the default value. This value should not be a CLSID, or if it is, it should be a CLSID that doesn't match any real COM object's CLSID, like a random guid for example.