How to assign a icon for "Copy/Cut/Paste/Delete" Windows default context menu items?

12

5

Under Windows 8/8.1 x64, I would like to assign a custom icon for the default Windows context menu items such as Copy, Cut, Paste, Delete, Undo, Redo and Send To items, which by default has any icon:

enter image description here

Where I can locate the "reference" to those context menu items in the registry then add a "icon" registry value for them?

Or in other words, how to assign a icon to a shell extension menu like the SendTo shellex?.

Research


As commented by @Sk8erPeter, seems that:

"Adding the Icon string value to different context menu handlers doesn't work like when adding it to a custom item like e.g. HKEY_CLASSES_ROOT\*\shell\MYCUSTOMKEY"

ElektroStudios

Posted 2013-09-23T13:53:42.933

Reputation: 1 282

What icon are you referring to? Do you have a screenshot? – Raystafarian – 2015-12-29T12:18:07.047

@Raystafarian I've updated the question with a image. – ElektroStudios – 2015-12-29T12:51:57.687

1@Raystafarian: the question is how to add a custom icon to existing basic context menu items like "Cut", "Copy", "Delete", "Rename", etc. BTW when adding a new custom item to the context menu, it is very easy, because you only have to add the Icon String Value in a key like HKEY_CLASSES_ROOT\*\shell\MYCUSTOMITEM (and the value of the Icon would be like e.g. %SystemRoot%\System32\shell32.dll,-133 or sg. else). BUT adding the Icon string value to different context menu handlers doesn't work like when adding it to these custom items. – Sk8erPeter – 2015-12-29T12:53:27.013

Here is another screenshot to make it clear (the interesting part is in red borders): http://i.imgur.com/fmewg6L.png. BTW as you can see, I have some custom items in the context menu with custom icons (like "Open with Notepad++") - this is exactly what we would like to achieve with the existing system context menu items!

– Sk8erPeter – 2015-12-29T13:02:51.713

Right, so currently there is no icon for those items, correct? So we're not sure they have that particular attribute to be edited, it may need to be created? – Raystafarian – 2015-12-29T13:50:52.567

@Raystafarian: What's your point? Yes, there is no icon set for those items as we've already explained it and I even posted a screenshot of it (and ElektroStudios kindly pasted it into his question to make it clearer), and this is exactly what we would like to change... – Sk8erPeter – 2015-12-29T15:22:41.050

@Sk8erPeter My point was - there's no blank pixel or place-holder for an icon. I was just verifying this. – Raystafarian – 2015-12-29T15:34:59.763

1

@Sk8erPeter My best lead at the moment is the prospect of creating a shell context menu handler that uses SetMenuItemInfo in response to QueryContextMenu.

– Ben N – 2015-12-29T16:36:35.530

@BenN: if you had time, I'd appreciate a sample code. :) Thanks in advance. – Sk8erPeter – 2015-12-29T17:37:01.137

@ElektroStudios This here may have some samples per what Ben was referring to for looking over applicable logic perhaps Context Menu Samples. This also has some references to the Windows.UI.Popups API and correlated classes, etc. This seems a bit extreme just to add an icon to one of the default context menu options but I tried tracing with Process Monitor, etc. by doing a right-click copy and looking at various registry keys paths, stacks, etc. and I didn't have much luck with what little time I put into the task.

– Pimp Juice IT – 2016-01-04T04:43:07.847

@ElektroStudios Also, for string values of registry keys, etc. that may be of interest and perhaps a file name too for what I saw when tracing but I couldn't put the pieces together here is a list of those in case you find helpful any. . . Shell Copy Hook, Disk Copy Extension, Copy as Path Menu, CTXMENU_NOVERBS, Shell DRM Copy Object, and also diskcopy.dll. . . I ran out of time to do much further but that's my stab at it. I wasn't sure if adding the \Settings key and then the applicable values to a default registry location would do the trick so that's what I was hoping to test. – Pimp Juice IT – 2016-01-04T04:49:43.263

@Sk8erPeter I put the pieces together, more or less, into something that works for me. See my answer :) – Ben N – 2016-01-05T01:56:05.610

ElektroStudios: you can accept Ben N's answer, it really solves the problem! :) Thank you, Ben, you really deserve the bounty! And @LMFAO_A_JOKE, thanks for your efforts too!

– Sk8erPeter – 2016-01-05T22:48:53.850

@Sk8erPeter and to all who may be interested: my answer is now a GitHub project. I've also updated my answer with links and some extra technical details.

– Ben N – 2016-01-06T01:38:08.473

Answers

10

Affiliation notice: I am the author of the software mentioned in this answer.

First up, I'll have you know that I learned C++ and Win32 just for this question.

I have developed a 64-bit shell extension that gets registered as a context menu handler. When it's invoked, it rummages through the existing menu items, looking for interesting entries. If it finds one, it sticks an icon on it (which must have been loaded earlier). At the moment, it looks for Copy, Cut, Delete, Paste, Redo, Send to, and Undo. You can add your own by modifying the code; the procedure for this is described below. (Sorry, I'm not good enough at C++ to make it configurable.)

A screenshot of it in action, with the ugliest icons known to man:

in action

You can download these icons if you really want to.

Setting it up

Download it (from my Dropbox). Notice: this file is detected by one VirusTotal scanner as being some form of malware. This is understandable, given the kind of things it has to do to whack the existing entries. I give you my word that it does no intentional harm to your computer. If you're suspicious and/or you want to modify and extend it, see the code on GitHub!

Create a folder in your C drive: C:\shellicon. Create BMP files with the following titles: copy, cut, delete, paste, redo, sendto, undo. (Hopefully it's obvious which one does which thing.) These images should probably be 16 by 16 pixels (or however big your DPI settings make the menu margin), but I've had success with larger ones as well. If you want the icons to look transparent, you'll have to just make their background the same color as the context menu. (This trick is employed by Dropbox as well.) I made my terrible icons with MS Paint; other programs may or may not save in a manner compatible with LoadImageA. 16 by 16 at 24-bit color depth at 96 pixels per inch seems to be the most reliable set of image properties.

Put the DLL somewhere accessible to all users, that folder you just made is a good choice. Open an admin prompt in the folder containing the DLL and do regsvr32 ContextIcons.dll. This creates registration information for the shell types *, Drive, Directory, and Directory\Background. If you ever want to remove the shell extension, do regsvr32 /u ContextIcons.dll.

Relevant code

Basically, the extension just queries every context menu item's text with GetMenuItemInfo and, if appropriate, adjusts the icon with SetMenuItemInfo.

Visual Studio generates a lot of magic mysterious code for ATL projects, but this is the contents of IconInjector.cpp, which implements the context menu handler:

// IconInjector.cpp : Implementation of CIconInjector

#include "stdafx.h"
#include "IconInjector.h"
#include <string>

// CIconInjector

HBITMAP bmpCopy = NULL;
HBITMAP bmpCut = NULL;
HBITMAP bmpUndo = NULL;
HBITMAP bmpRedo = NULL;
HBITMAP bmpSendto = NULL;
HBITMAP bmpDel = NULL;
HBITMAP bmpPaste = NULL;
STDMETHODIMP CIconInjector::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT pDataObj, HKEY hProgID) {
    // Load the images
    bmpCopy = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\copy.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpCut = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\cut.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpUndo = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\undo.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpRedo = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\redo.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpSendto = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\sendto.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpDel = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\delete.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    bmpPaste = (HBITMAP)LoadImageA(NULL, "C:\\shellicon\\paste.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE);
    int err = GetLastError();
    return S_OK;
}
STDMETHODIMP CIconInjector::QueryContextMenu(HMENU hmenu, UINT uMenuIndex, UINT uidFirst, UINT uidLast, UINT flags) {
    using namespace std;
    if (flags & CMF_DEFAULTONLY) return S_OK; // Don't do anything if it's just a double-click
    int itemsCount = GetMenuItemCount(hmenu);
    for (int i = 0; i < itemsCount; i++) { // Iterate over the menu items
        MENUITEMINFO mii;
        ZeroMemory(&mii, sizeof(mii));
        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_FTYPE | MIIM_STRING;
        mii.dwTypeData = NULL;
        BOOL ok = GetMenuItemInfo(hmenu, i, TRUE, &mii); // Get the string length
        if (mii.fType != MFT_STRING) continue;
        UINT size = (mii.cch + 1) * 2; // Allocate enough space
        LPWSTR menuTitle = (LPWSTR)malloc(size);
        mii.cch = size;
        mii.fMask = MIIM_TYPE;
        mii.dwTypeData = menuTitle;
        ok = GetMenuItemInfo(hmenu, i, TRUE, &mii); // Get the actual string data
        mii.fMask = MIIM_BITMAP;
        bool chIcon = true;
        if (wcscmp(menuTitle, L"&Copy") == 0) {
            mii.hbmpItem = bmpCopy;
        }
        else if (wcscmp(menuTitle, L"Cu&t") == 0) {
            mii.hbmpItem = bmpCut;
        }
        else if (wcscmp(menuTitle, L"&Paste") == 0) {
            mii.hbmpItem = bmpPaste;
        } 
        else if (wcscmp(menuTitle, L"Se&nd to") == 0) {
            mii.hbmpItem = bmpSendto;
        }
        else if (wcsstr(menuTitle, L"&Undo") != NULL) {
            mii.hbmpItem = bmpUndo;
        }
        else if (wcsstr(menuTitle, L"&Redo") != NULL) {
            mii.hbmpItem = bmpRedo;
        }
        else if (wcscmp(menuTitle, L"&Delete") == 0) {
            mii.hbmpItem = bmpDel;
        }
        else {
            chIcon = false;
        }
        if (chIcon) SetMenuItemInfo(hmenu, i, TRUE, &mii);
        free(menuTitle);
    }
    return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0); // Same as S_OK (= 0) but is The Right Thing To Do [TM]
}
STDMETHODIMP CIconInjector::InvokeCommand(LPCMINVOKECOMMANDINFO info) {
    return S_OK;
}
STDMETHODIMP CIconInjector::GetCommandString(UINT_PTR, UINT, UINT*, LPSTR, UINT) {
    return S_OK;
}

Note that the HBITMAPs are never cleaned up, but this doesn't matter too much given that the DLL's stuff will go away when Explorer shuts down. The icons barely take any memory anyway.

If you're compiling for 32-bit, the first parameter to GetCommandString is just a UINT instead of a UINT_PTR.

If you really want transparent icons, you'll have to create a window with the desired icon and then set mii.hBmpItem to HBMMENU_SYSTEM and put the handle to the window in mii.dwItemData, as described at the bottom of the MSDN article on MENUITEMINFO. I wasn't able to figure out how to create windows from shell extensions. LR_LOADTRANSPARENT looks promising as a flag of LoadImageA, but it has its own pitfalls - specifically, not working unless you use 256-color bitmaps.

If you experience problems with image loading, try removing the LR_DEFAULTSIZE flag from the LoadImageA calls.

Somebody sufficiently skilled in C++ could probably grab resources out of other DLLs and convert them to HBITMAPs, but that somebody is not me.

Modifying it

I wrote this in Visual Studio, which I believe to be the best editor for Windows C++.

Load up the SLN file into Visual Studio 2015 after you install the C++ tools. In IconInjector.cpp, you can add HBITMAP entries at the top and LoadImageA calls in Initialize to add new icons. Down in the else if section, use a wcscmp call to look for an exact match, or a wcsstr call to look for the presence of a substring. In both cases, the & represents the position of the underline/accelerator when using Shift+F10. Set your mode to Release and your architecture to x64, and do BuildBuild Solution. You'll get an error about failing to register the output, but don't worry; you'd want to do this manually anyway. End Explorer, copy the new DLL (\x64\Release\ContextIcons.dll in the solution folder) to the place, then do the regsvr32 dance.

Attributions

Many thanks to the MSDN writers, and to the creator of "The Complete Idiot's Guide to Writing Shell Extensions", which I referenced heavily.

Eulogy

To the many Explorer instances that were killed in the production of this shell extension: you died for a great cause, that some people on the Internet can have icons next to their words.

Ben N

Posted 2013-09-23T13:53:42.933

Reputation: 32 973

Wow! I really appreciate your efforts, thank you very much! (+1) I tried my best but couldn't make the compiled version work on Windows 10 (Build 10240). I don't know what the problem is, all the bmp images exist in the right path (C:\shellicon\copy.bmp, etc. - these are 20x20 pixels icons in BMP format) and I registered the dll as an admin in command prompt with regsvr32 ContextIcons.dll which ran successfully, but I see no changes in the context menu. I even restarted the computer, unregistered and reregistered the dll again, but no changes. I'm trying to compile the source in VS2015! – Sk8erPeter – 2016-01-05T21:38:35.517

@Sk8erPeter MSDN said that the icons need to be 16x16, but 20x20 works for me. Maybe Windows 10 requires 16x16? Note that you do have to restart Explorer for the changes to take effect. – Ben N – 2016-01-05T22:14:12.050

I just tried the same with 16x16 BMP images (after unregistering and reregistering the DLL again), and unfortunately there's still no change. – Sk8erPeter – 2016-01-05T22:23:17.530

@Sk8erPeter Do you have a Windows 8 machine around to try it on? I'm working on getting a Windows 10 computer up for debugging. Also, check that an IconInjector entry appeared in HKCR\*\shellex\ContextMenuHandlers. Finally, maybe check your antivirus logs? It might be a little suspicious of the unsigned nature of the shell extension. Finally, maybe try adding a string entry to HKLM\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved whose name is {EA7B0CA1-BF70-406C-BD22-690E5F9E6618}. (Win10 might have a policy enabled that requires registration there too.) – Ben N – 2016-01-05T22:30:23.530

Yes, of course, I checked the new registry entries, and they do exist! I also added the new string entry under the Approved key like you mentioned. Still no change (unregistered and reregistered again after that too). Unfortunately I currently do not have a Windows 8 machine, but I'll try if I can get one. – Sk8erPeter – 2016-01-05T22:34:44.313

Anyway, I give you the bounty! :) I can not test all the other circumstances before midnight when it expires, and I believe that it really worked for you. Anyway, if you have a chance, I'd appreciate if you could test it under Windows 10 too. :) Thanks! – Sk8erPeter – 2016-01-05T22:36:27.977

BTW couldn't you upload your BMP files to Dropbox too? :) Just out of curiosity, I would like to test it with those images that were visible in the your context menu (although I don't think the images are the source of the problem but who knows...). It would be very nice if you could upload the source code to GitHub/Bitbucket. :) – Sk8erPeter – 2016-01-05T22:40:24.697

2

@Sk8erPeter Certainly, here you go. I'll see about putting the code up on GitHub. Working on downloading Windows 10 now...

– Ben N – 2016-01-05T22:41:27.010

2You will not believe it... IT WORKS with your images! :D :D It means I have some bmp files which Windows couldn't handle, don't know why (later I'll check that too). Anyway, thank you very much, your code really solves the problem! :) – Sk8erPeter – 2016-01-05T22:44:41.437

@BenN Quite the job here!! Great efforts man, nice work!! +1 from me – Pimp Juice IT – 2016-01-05T23:12:01.257

@Ben N A programming language answer is not the solution expectations that I have, anyways I'm a .Net guy so I could translate the unmanaged code to managed code in Vb.Net so I am most than grateful with your answer, THANKYOU, also maybe monitoring for registry changes when debugging the hbmpItem applied field maybe at that point we could discover a simple way to do this via registry changes to avoid having an application running in background, I still didn't examined what happens in the registry at that point, but if someone want I recommend X-RegShot v2.0 program. – ElektroStudios – 2016-01-06T06:17:54.877

@BenN: don't you know if even a resource icon can be used (e.g. the icons from shell32.dll or an exe) or not in this case? I've only found ways to load BMPs (HBITMAPs) because this is what MENUITEMINFO needs (hbmpItem) and it does not accept HICON instances. Is there any other method to do that or we need to accept the fact that it only works with BMPs? For example if I want to display an icon next to the "Open command window here" menu item, I only have to edit HKEY_CLASSES_ROOT\Directory\Background\shell\cmd, put a String Value called Icon here with the value cmd.exe,0. – Sk8erPeter – 2016-01-06T13:52:44.250

@Sk8erPeter I believe that DLL resource loading works because Explorer makes the menu item owner-drawn. Sadly, I don't see a way to impersonate the Explorer window from a shell extension (owner-drawing is initiated with a window message). It appears that the hBmpItem field only takes HBITMAPs. – Ben N – 2016-01-06T14:08:47.500

@ElektroStudios This program's icon injection makes no changes to the registry, directly or indirectly, it's all at runtime; there doesn't appear to be any registry setting that controls the icon for these entries. Since I've provided both the source and a compiled, I think translating to a different language/runtime doesn't serve much purpose. Also, it's generally inadvisable to write shell extensions in .NET. Finally, the DLL doesn't so much run in the background as become a part of Explorer - almost no performance hit.

– Ben N – 2016-01-06T14:13:10.613

1@BenN: OK, thanks! :) It would have been a bit more convenient. BTW in the meantime I realized that if I open my previously not working images in the legendary Paint, and I do a "Save as" > "24-bit Bitmap (.bmp;.dip)" (so save it to a BMP file again), and I use this new file as a source image, it WORKS. Of course, the bitmap's size have to be exactly 16x16 pixels. So Paint creates the expected bitmap format which is 24 bits per pixel (16.7 million colors), 96x96 DPI and 16x16 pixels in size. Previously I converted and resized .png files in IrfanView to .bmp files, these icons did not work. – Sk8erPeter – 2016-01-06T22:54:12.793

Maybe you could mention this in the post to avoid related problems and questions, and it can be an important information what kind of images are accepted. :) – Sk8erPeter – 2016-01-06T22:56:52.613

1

I don't have enough rep to leave a comment but it appears this info is contained inside shell32.dll. The files been compiled so it's hard to see what functions are in it, but it appears to be the one.

Of interest (registry export):

HKEY_CLASSES_ROOT\CLSID{3ad05575-8857-4850-9277-11b85bdb8e09}

(Default) REG_SZ Copy/Move/Rename/Delete/Link Object

AppID REG_SZ {3ad05575-8857-4850-9277-11b85bdb8e09}

LocalizedString REG_EXPAND_SZ @%SystemRoot%\system32\shell32.dll,-50176

Under the InProcServer32 key it references shell32.dll. There are a couple other ones as well with relevant sounding names. Possibly also of interest is windows.storage.dll

nijave

Posted 2013-09-23T13:53:42.933

Reputation: 308

1Interesting information. However, it seems to be a comment rather than an answer. You now have enough rep to comment everywhere :) – Ben N – 2016-01-05T16:42:59.373