Skip to content

Grouping custom scripts in windows right click contextmenu

A simple way to organize your scripts as a group in windows contextmenu.

Last modified:

#python#windows#contextmenu

Contextmenu group with sub groups

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.

  1. {BASE_ROOT}\\Directory\\shell\\ - to displays items when right click on a directory
  2. {BASE_ROOT}\\Directory\\Background\\shell\\ - to displays items when right click on empty space in the file explorer
  3. {BASE_ROOT}\\DesktopBackground\\shell\\ - to displays items when right click on desktop background
  4. {BASE_ROOT}\\Drive\\shell\\ - to displays items when right click on a drive
  5. {BASE_ROOT}\\*\\shell\\ - to displays items when right click on any file(s)
  6. {BASE_ROOT}\\SystemFileAssociations\\{FILE_TYPE}\\shell\\ - to displays items when right click on a FILE_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.

  1. Description of the item to be displayed on the context menu
  2. Registry key associated with the item
  3. Command to be executed
  4. [Optional] Path to an icon

For grouping items into a folder like structure, the above details are required for each item along with

  1. Name of the group
  2. Registry key associated with the group
  3. [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.

© Naveen Namani | 2022