Grouping custom scripts in windows right click contextmenu
A simple way to organize your scripts as a group in windows contextmenu.
Last modified:
The problem
One fine boring day, I was cleaning unnecessary files in my PC and realized that there are too many empty folders. Digging around my python scripts folders, I realized that once upon a time I wrote a script to clean empty folders. The script takes a directory path as an argument. But copying the directory path, launching the cmd prompt, and passing the directory path to the script is a waste of time. While I was thinking about a solution, I right-clicked on the folder and thought if only there is a menu item "Clean empty folders" that can run my script, that will solve my problem.
Finding the solution
The next moment, I stopped deleting files and instead started searching on google about "Adding python scripts to the Windows context menu". I found a few tutorials and understood that adding items to the context menu requires creating keys in the windows registry. But thanks to the complexity of the Windows, the solutions I found seemed to be quite complex or less informative. While most of the tutorials focused on adding a single item to the context menu, grouping items into a folder like structure is still not an intuitive step. It took me some experimentation and time to figure out how Windows actually deals with the registry keys when it comes to the context menu.
There are atleast 15 registry keys where you can add context menu items.
{BASE_ROOT}\\Directory\\shell\\
- to displays items when right click on a directory{BASE_ROOT}\\Directory\\Background\\shell\\
- to displays items when right click on empty space in the file explorer{BASE_ROOT}\\DesktopBackground\\shell\\
- to displays items when right click on desktop background{BASE_ROOT}\\Drive\\shell\\
- to displays items when right click on a drive{BASE_ROOT}\\*\\shell\\
- to displays items when right click on any file(s){BASE_ROOT}\\SystemFileAssociations\\{FILE_TYPE}\\shell\\
- to displays items when right click on aFILE_TYPE
file type
where BASE_ROOT
can be HKEY_CLASSES_ROOT
,
HKEY_CURRENT_USER\\Software\\Classes
or
HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes
. For adding items to all users
HKEY_CLASSES_ROOT
is used, and for adding items to the current user
HKEY_CURRENT_USER\\Software\\Classes
is used.
Now, each item in the context menu is associated with the following information.
- Description of the item to be displayed on the context menu
- Registry key associated with the item
- Command to be executed
- [Optional] Path to an icon
For grouping items into a folder like structure, the above details are required for each item along with
- Name of the group
- Registry key associated with the group
- [Optional] Path to an icon
The structure of the registry keys and values for a single item is shown below.
ROOT
ITEM_REG_KEY
= (Default) = Name of the item
= Icon = Path to an icon
command
= (Default) = Command
For a group of context menu items, the registry structure looks as follows.
ROOT
GROUP_REG_KEY
= MUIVerb = Group name
= Icon = Path to an icon
= Subcommands =
Shell
ITEM1_REG_KEY
= (Default) = Name of the item1
= Icon = Path to an icon
command
= (Default) = Command1
ITEM2_REG_KEY
= (Default) = Name of the item2
= Icon = Path to an icon
command
= (Default) = Command2
SUBGROUP_REG_KEY
= MUIVerb = Subgroup name
= Icon = Path to an icon
= Subcommands =
Shell
ITEM1_REG_KEY
= (Default) = Name of the item1
= Icon = Path to an icon
command
= (Default) = Command1
...
...
Implementation
Test implementation
Let us first implement a simple script to add a menu item group "Group 1" and a
two items "Item 1" and "Item 2" to the group. For adding registry keys we use
python's winreg
library. First, we import the required functions from the
winreg
library.
# Import required functions
from winreg import (
CreateKey, # for creating/opening keys
SetValue, # for setting a key value
SetValueEx, # for storing key/value data
HKEY_CURRENT_USER,
REG_SZ, # to set value as a string
CloseKey
)
Then we create the group registry key for the current user.
# create the group registry key
group_reg_key_str = r"Software\\Classes\\Directory\\Background\\shell\\Group1\\"
group_reg_key = CreateKey(HKEY_CURRENT_USER, group_reg_key_str)
# Create a key 'MUIVerb' and add 'Group 1' as value.
# 'Group 1' is shown in the conextmenu
SetValueEx(group_reg_key, 'MUIVerb', 0, REG_SZ, "Group 1")
# Create a key 'SubCommands'
SetValueEx(group_reg_key, 'SubCommands', 0, REG_SZ, '')
# Create subkey "shell" for adding items
subcommands_key = CreateKey(group_reg_key, 'shell')
When you run this script it will create the group named "Group 1" in the context menu. But we haven't added any items to that list. Let's continue and add two items.
# Create item1 key
item1_key = CreateKey(subcommands_key, 'item1')
# Set item name
SetValue(item1_key, '', REG_SZ, "Item 1")
# Create command key and add the command
SetValue(item1_key, 'command', REG_SZ, 'cmd.exe')
CloseKey(item1_key)
# Create item2 key
item2_key = CreateKey(subcommands_key, 'item2')
# Set item name
SetValue(item2_key, '', REG_SZ, "Item 2")
# Create command key and add the command
SetValue(item2_key, 'command', REG_SZ, 'cmd.exe')
CloseKey(item2_key)
CloseKey(subcommands_key)
CloseKey(group_reg_key)
Now, after running the complete script you'll get the following output.
Open the registry editor and go to
HKEY_CURRENT_USER\\Software\\Classes\\Directory\\Background\\shell\\
. You'll
see the following structure corresponding to the context menu group we've
created.
Congratulations, 😃 👏 🌟 we did it. Run the complete test script from the gist.
test script gist
General purpose implementation
For adding the groups and menu items to the context menu, first let us define
two classes ContextMenuItem
and ContextMenuGroup
for representing a menu
item and a menu group respectively.
class ContextMenuItem(object):
def __init__(self, item_name, command, item_reg_key = "", icon = ""):
self.item_name = item_name
self.item_reg_key = item_name if item_reg_key == "" else item_reg_key
self.icon = icon
self.command = command
class ContextMenuGroup(object):
def __init__(self, group_name, group_reg_key = "", icon = ""):
self.group_name = group_name
self.group_reg_key = group_name if group_reg_key == "" else group_reg_key
self.icon = icon
self.items = []
def add_item(self, item):
assert isinstance(
item, (ContextMenuItem, ContextMenuGroup)
), "Please pass instance of ContextMenuItem or ContextMenuGroup"
self.items.append(item)
Consider the following script which creates some nested groups and adds items to them.
group1 = ContextMenuGroup("Group 1")
group2 = ContextMenuGroup("Group 2")
group3 = ContextMenuGroup("Group 3")
group1.add_item(ContextMenuItem("Item 1", "cmd.exe"))
group1.add_item(ContextMenuItem("Item 2", "cmd.exe"))
group2.add_item(ContextMenuItem("Item 3", "cmd.exe"))
group2.add_item(ContextMenuItem("Item 4", "cmd.exe"))
group3.add_item(ContextMenuItem("Item 5", "cmd.exe"))
group3.add_item(ContextMenuItem("Item 6", "cmd.exe"))
group2.add_item(group3)
group1.add_item(group2)
Now to add the details of ContextMenuItem
and ContextMenuGroup
to the
registry, define two functions create_item
and create_group
. The steps of
the functions are taken from the test implementation.
from winreg import (
CreateKey,
SetValue,
SetValueEx,
CloseKey,
HKEY_CURRENT_USER,
REG_SZ
)
def create_item(root_key, item: ContextMenuItem):
item_key = CreateKey(root_key, item.item_reg_key)
SetValue(item_key, '', REG_SZ, item.item_name)
SetValue(item_key, 'command', REG_SZ, item.command)
if item.icon != "":
SetValueEx(item_key, 'Icon', 0, REG_SZ, item.icon)
CloseKey(item_key)
def create_group(root_key, group: ContextMenuGroup):
group_key = CreateKey(root_key, group.group_reg_key)
SetValueEx(group_key, 'MUIVerb', 0, REG_SZ, group.group_name)
SetValueEx(group_key, 'SubCommands', 0, REG_SZ, '')
if group.icon != "":
SetValueEx(group_key, 'Icon', 0, REG_SZ, group.icon)
subcommands_key = CreateKey(group_key, "shell")
for item in group.items:
if isinstance(item, ContextMenuItem):
create_item(subcommands_key, item)
elif isinstance(item, ContextMenuGroup):
create_group(subcommands_key, item)
CloseKey(subcommands_key)
CloseKey(group_key)
Now open the root key for directory background and create the group
user_key = CreateKey(
HKEY_CURRENT_USER,
r"Software\\Classes\\Directory\\Background\\shell\\"
)
create_group(user_key, group1)
Great, that's a nice nested context menu groups with items.
But wait, you haven't told me about how I can remove them and how I can add python scripts?
¯\(ツ)/¯
This post is a note to myself during the development of a pypi package. Please checkout the pypi package and the source code.
There is happiness in doing random things at random times with random people.