Skip to content

About

Configurable Windows Explorer context-menu extension (C++17/COM): JSON menus, gates/executors, SVG icons, transactional registration | 可配置的 Windows 资源管理器右键菜单扩展(C++17/COM):JSON 菜单、Gate/Executor、SVG 图标与事务式注册

Topics

Resources

Contributing

Security policy

Stars

19 stars

Watchers

1 watching

Forks

Latest commit

 

History

55 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ShellExtContextMenuHandler

English · 简体中文 · 日本語 · 한국어

A configurable Windows Explorer context-menu extension derived from Microsoft's C++ Shell extension sample. Menu items, Shell registration targets, filters, gates, executors, and icons are configurable; complex behavior stays in C++.

Disclaimer

This project was developed primarily with the assistance of AI Agents. Parts of the code, design, and documentation have not undergone sufficient, rigorous, or comprehensive testing. The project is provided for learning, research, and technical reference only; correctness, stability, and security are not guaranteed for all systems, environments, or use cases.

Before using this project in an important project, commercial software, or production environment, perform appropriate code review, customization, security assessment, and compatibility testing. The project author assumes no responsibility for data loss, system failures, business interruptions, or any other direct or indirect losses resulting from the use of this project.

Features

  • Registers five Shell target types by default: file, directory, directory background, drive, and file-system object.
  • Configures menu items, target filters, selection filters, gate/executor chains, and actions in config/menu.json.
  • Configures COM/Shell registration targets in config/registration.json.
  • Validates the whole configuration strictly: wrong field types, unknown gates/executors, unknown actionType, and duplicate ids fall back to the built-in menu instead of silently widening filters.
  • Validates registration.json before touching the registry; an invalid file leaves an existing installation unchanged. Successful register/unregister calls notify the Shell.
  • Supports messageBox and launch actions plus custom executors.
  • Supports SVG/BMP icons; SVG files are loaded and rasterized by the existing NanoSVG provider with DPI scaling.
  • Three gate stages: Extension, Item, and Presentation.
  • Built-in gates: extensionPass, jsonFilter, presentationPass, demo:hideTemp, demo:readOnlyDisable; built-in executors: messageBox, launch, demo:actionLog.
  • Build output includes double-clickable register.bat and unregister.bat scripts with UAC elevation; the scripts select System32 or SysWOW64 regsvr32 to match the DLL bitness.
  • Debug logging through OutputDebugString with the [ShellExt] prefix.
  • Ships CTest unit tests covering config parsing, placeholder expansion, context building, launch command resolution, registration config parsing, and COM exception boundaries; CI runs them automatically.

Quick Start

Requirements

  • Windows x64
  • Visual Studio 2026 C++ toolchain
  • CMake 3.20 or later
  • Python 3.9 or later for configuration validation

Windows 7 SP1 and later are the design targets. Validate the final DLL on the Windows version and Explorer bitness you intend to support.

Build

cmake -S . -B build -G "Visual Studio 18 2026" -A x64 -DSHELLEXT_BUILD_TESTS=ON
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure
python tools/validate_menu_json.py

Unit tests are off by default (SHELLEXT_BUILD_TESTS=ON enables them). They live in tests/ and compile the real sources instead of copies.

The DLL links the MSVC CRT statically (/MT), so the target machine does not need a separate VC++ runtime; the DLL is slightly larger.

tools/generate-vs.ps1 can also be used to generate the Visual Studio project.

The output is in build/bin/Release/:

CppShellExtContextMenuHandler.dll
menu.json
registration.json
register.bat
unregister.bat
icons/

Register, Test, and Unregister

Double-click register.bat in the DLL output directory. It uses the DLL beside the batch file and requests elevation before calling regsvr32. Double-click unregister.bat to remove the extension.

Alternatively, use an elevated PowerShell:

.\tools\register.ps1 -Action register
.\tools\register.ps1 -Action unregister

register.ps1 validates the JSON syntax and registration targets (and runs the full validator when Python is available), copies registration.json, calls regsvr32, and copies menu.json and config/icons/ only after a successful registration; a failed registration restores the previous registration.json. After changing the DLL or configuration, unregister and register again; successful operations send SHChangeNotify, so an Explorer restart is normally unnecessary. An invalid registration.json fails registration without modifying the existing registry.

register.bat/unregister.bat and register.ps1 inspect the DLL's PE machine type and select the matching regsvr32 (System32 for x64, SysWOW64 for x86).

Manual smoke test

  1. Run unregister.bat, then run register.bat.
  2. Right-click a regular file and verify Demo -F.
  3. Right-click a directory and verify Demo -D.
  4. Right-click empty space in a directory and verify Demo -DB.
  5. Right-click a drive root and verify Demo -DR.
  6. Select both a file and a directory and verify Demo -FS.
  7. Invoke an item and verify that the MessageBox shows the target name.

Repository rules and checks

Mechanical rules come from RepoRules (checker vendored as scripts/repo_rules.py, configured by repo-rules.json) and run in CI via .github/workflows/hygiene.yml:

  • C/C++ is UTF-8 BOM + CRLF; Markdown is CRLF; CMake, scripts, JSON and YAML are LF (see .gitattributes / .editorconfig).
  • C/C++ lines stay within 140 columns; headers start with #pragma once; no #pragma comment(lib) - link Windows libraries through CMake.
  • CMakeLists.txt, src/resources/Version.rc and include/shell_ext/common.h must agree on the version; CHANGELOG.md keeps an [Unreleased] section.
  • Relative Markdown links and the Chinese/English document pairs must resolve.
python scripts/repo_rules.py check --config repo-rules.json   # check
python scripts/repo_rules.py fix   --config repo-rules.json   # repair BOM/line endings
python -m unittest discover -s scripts/tests -v

Optionally install the pre-commit hook with git config core.hooksPath .githooks. See AGENTS.md for the rules and review priorities, CONTRIBUTING.en.md for the workflow, and SECURITY.en.md for private reporting.

Configuration

Shell registration targets

config/registration.json:

{
  "schemaVersion": 1,
  "shellRegistrations": [
    "file",
    "directory",
    "directoryBackground",
    "drive",
    "fileSystemObject"
  ]
}
Target Registry location Runtime context
file * File-only selection
directory Directory Directory-only selection
directoryBackground Directory\\Background Empty selection in a directory
drive Drive Drive root
fileSystemObject AllFilesystemObjects Mixed file-and-directory selection

If registration.json is missing, registration defaults to file. If the file exists but has an invalid schema, unknown target, duplicate target, or invalid array, registration fails instead of silently changing the registry. Unregistration removes all known targets so old registrations can be cleaned up.

Root-level menu.json fields

{
  "schemaVersion": 1,
  "extensionGates": ["extensionPass"],
  "itemGates": ["jsonFilter"],
  "presentationGates": ["presentationPass"],
  "executors": ["messageBox"],
  "menuItems": []
}
Field Description
schemaVersion Optional; currently only 1 is accepted, so future format changes can reject incompatible files
extensionGates Gate 1 chain; decides whether the extension participates
itemGates Gate 2 chain; decides whether an item becomes a candidate; gates is an alias
presentationGates Gate 3 chain; returns hidden, disabled, or enabled
executors Default action executor chain
menuItems Menu item definitions

menu.json must be UTF-8 (BOM tolerated) and is parsed with third_party/nlohmann/json. Wrong field types, unknown gates/executors, unknown actionType, duplicate ids, and other document-level errors fall back to the built-in menu. Unknown chain names are not silently ignored.

Chain override semantics: an explicitly configured per-item chain replaces the root default entirely instead of appending to it. Root defaults apply only when the chain is missing or empty.

Per-item fields

Field Description
id Unique identifier
label Menu text; & marks an accelerator
verb String command name; InvokeCommand matches on it or canonicalName
helpText Explorer status-bar help text
canonicalName Canonical command name; accepted interchangeably with verb for matching
icon SVG/BMP path relative to the DLL directory
targets file, directory, directoryBackground, drive, or fileSystemObject
separatorAfter Insert a separator after the item
extensions Allowed extensions such as [".cpp", ".h"]
excludeExtensions Extensions to reject
minSelection / maxSelection Selection count; maxSelection: 0 means unlimited
filesOnly / foldersOnly Restrict selected object types
actionType messageBox or launch
actionTitle / actionTemplate MessageBox title and content
actionCommand / actionShowWindow Launch command and window option
extensionGates / itemGates / presentationGates / executors Per-item overrides of root chains; explicit chains replace root defaults

An empty targets array preserves compatibility with the generic selection filters. New items should set targets explicitly.

Placeholders

Token Meaning
%1 Full path of the first selected object, or current directory
%* All selected paths, quoted and joined
%D Parent directory of the first selected object, or current directory
%N Name of the first selected object or current directory, without its parent path

If menu.json is missing or invalid, the extension falls back to the built-in item in include/shell_ext/common.h and the default gate chain.

Placeholder expansion is single-pass: the replacement text is not rescanned, so selected file names containing tokens such as %1 or %N are safe.

Launch command resolution

The launch action explicitly resolves the executable in actionCommand before starting it:

  • Quoted paths are used as-is.
  • Unquoted paths containing spaces follow the CreateProcess documented prefix-progressive search.
  • Bare command names (such as notepad.exe) are resolved from the system directories and absolute PATH entries only — the current directory is never searched, preventing same-name executables in it from being hijacked.
  • Unresolvable commands are logged and rejected instead of launched.

Long paths

Selected object paths are not limited by MAX_PATH. %1, %*, %N, and %D are all processed as dynamic std::wstring values. Directory-background paths try SHGetPathFromIDListEx first and fall back to SHGetPathFromIDListW; file attribute reads retry with a \\?\ extended path when the plain path fails.

Actual paths longer than MAX_PATH still require the system long-path policy and a long-path-aware Explorer; otherwise the Shell will not hand such paths to the extension.

Extending the project

Add a menu item

  1. Add an object to menuItems in config/menu.json.
  2. Set a unique id, verb, targets, filters, and action fields.
  3. Put SVG/BMP assets in config/icons/ and reference them with a relative path.
  4. Run python tools/validate_menu_json.py.

Add a gate

  1. Add an implementation of IExtensionGate, IMenuItemGate, or IMenuItemPresentationGate under src/gates/.
  2. Register its name in GateRegistry::RegisterBuiltInGates.
  3. Use the name in the appropriate chain in menu.json.

Extension gates decide whether the extension participates, item gates decide candidate membership, and presentation gates return Hidden, Disabled, or Enabled.

Add an executor

  1. Add an IActionExecutor implementation under src/actions/.
  2. Register it in ExecutorRegistry::RegisterBuiltInExecutors.
  3. Add its name to a root or per-item executors array.
  4. Extend MenuAction and MenuConfig if the executor needs new configuration fields.

Add a Shell target

  1. Add an enum value to ShellTargetType.
  2. Update ParseShellTargetType and GetShellTargetRegistryFileType.
  3. Add runtime recognition in ContextBuilder.
  4. Update target matching and the known registration/unregistration lists.
  5. Update tools/validate_menu_json.py, README, and architecture docs.

Change COM registration

Registration entry points are in src/extension/dllmain.cpp; registry helpers are in src/registry/Reg.cpp. Preserve rollback on partial registration, idempotent unregistration, and cleanup of legacy targets. Delete only this project's CLSID subkey, never an entire ContextMenuHandlers parent key.

Debugging and troubleshooting

  • Use DebugView to inspect [ShellExt] logs.
  • Match the DLL bitness to Explorer.
  • Re-register after changing the DLL or configuration; restart Explorer when necessary.
  • If a menu is missing, check registration targets, item targets, filesOnly/foldersOnly, and gate chains together.
  • If registration fails, check elevation, registration.json, and the regsvr32 bitness.

Project layout

config/                 Runtime menu, registration targets, and icons
src/extension/          COM DLL entry point, ClassFactory, FileContextMenuExt, and ComGuard
src/context/            Selection context and Shell target recognition
src/gates/              Gate interfaces, registry, and built-in gates
src/actions/            Executor interfaces, registry, and actions
src/icons/              SVG/BMP icon providers
src/menu/               Configuration, filtering, insertion, and invocation
src/registry/           COM/Shell registry helpers
tools/                  Build helpers, registration scripts, and validation
scripts/                Repository rules checker and its tests
tests/                  CTest unit tests
third_party/            Third-party libraries (nlohmann/json, NanoSVG)
docs/                   Architecture and validation documentation

Notes

  • A 64-bit Explorer requires a 64-bit DLL; use the matching bitness for 32-bit Explorer. The registration scripts select System32/SysWOW64 regsvr32 automatically.
  • COM registration requires administrator privileges.
  • Windows 7 support is a design target; complete the Win7 SP1 x64 regression before release. The current VS 2026 toolchain and static CRT need separate validation on Win7; if unsupported, rebuild with a supported older toolchain.
  • The DLL links the MSVC CRT statically; re-evaluate the runtime requirements if you add new dependencies.
  • Do not edit generated files under build/, build-*, or third-party directories.

COM identity and license

When shipping a fork, change CLSID_FileContextMenuExt in src/extension/dllmain.cpp and the friendly names in include/shell_ext/common.h.

This project is derived from Microsoft's sample. Source files retain the original Microsoft headers and use the Ms-PL. The full license text is in LICENSE; third-party notices for nlohmann/json and NanoSVG are in THIRD_PARTY_NOTICES.md.

About

Configurable Windows Explorer context-menu extension (C++17/COM): JSON menus, gates/executors, SVG icons, transactional registration | 可配置的 Windows 资源管理器右键菜单扩展(C++17/COM):JSON 菜单、Gate/Executor、SVG 图标与事务式注册

Topics

Resources

Contributing

Security policy

Stars

19 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages