Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
· Updated: · Go Komura · Windows, Shell Extensions, Context Menu, File Association, COM, Windows 11, File Explorer, MSIX, Windows Development
Revision history (first version, published Aug 20, 2026)
- First published
Cite this article(DOI (registered archive): 10.5281/zenodo.22170871)
The DOIs below refer to previously archived versions and may not match the current text. Use this page’s URL to reference the current text.
Go Komura (2026). Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11. KomuraSoft LLC. https://comcomponent.com/en/blog/windows-shell-integration-context-menu-file-association/
- DOI (registered archive)
- 10.5281/zenodo.22170871
- DOI (last registered version)
- 10.5281/zenodo.22170872
“We moved to Windows 11 and can no longer find our app’s context-menu item.” On inspection, the item has not disappeared: open “Show more options” at the end of the menu and it appears. What is happening in this consultation is not a defect but a Windows 11 design change to the menu. In the field it leads to inquiries such as “it takes one more click” and “I cannot find the item”.
However, “opening a file with this app” and “putting a custom command on the new context menu” are two different tasks. If you start writing a shell extension DLL without separating them, you make a requirement that only needs an association registration more complicated than it has to be.
This article is an introduction for IT staff at small and midsize companies and for Windows developers who look after business apps. It connects the foundation of file associations, the difference between the new and old menus, the choice of implementation method, registration and removal in the installer, and troubleshooting.
1. The Bottom Line First
The foundation of associations and verbs is unchanged; on Windows 11, how the menu is presented split into new and old. First decide what you want to achieve, then choose only the mechanisms that goal requires.
If All You Need Is “Open”, Start from an Association and a Static Verb
The foundation of file association is the three-layer registry structure “extension key → ProgID → verb”. The extension key points at a ProgID, and shell\<verb>\command under the ProgID holds the command line to launch. If all you want is “open with this app”, this registration and a static verb are still enough, and no shell extension DLL is needed. Microsoft, too, advises starting from the simplest static verb that meets the requirements.12
Register under HKLM for all users or under HKCU for a single user. HKCR is a merged view that overlays the Classes of both, so name HKLM or HKCU explicitly as the write destination and treat HKCR as a place to check.3
Note that registering as a candidate and being chosen as the default app are separate things. The user chooses the default app that opens on double-click, and the OS protects that choice. The app’s job ends at registering itself correctly as a candidate, not at having the installer seize the default.4
Putting a Custom Command on the New Menu Requires IExplorerCommand and Package Identity
On Windows 11, classic IContextMenu extensions are relegated to the old menu that opens with “Show more options” (Shift+F10). Putting a custom command on the new menu requires an IExplorerCommand implementation and package identity.56
The official route is to register a native DLL that implements IExplorerCommand in an MSIX manifest (desktop4:FileExplorerContextMenus). If an existing app cannot be fully converted to MSIX, a sparse package (MSIX with external location) can grant identity alone.67
Think Through DLL Safety and Cleanup at Uninstall
A classic shell extension is a COM DLL loaded into the process of Explorer and other hosts. A crash or delay in the extension spreads to the whole host. A 64-bit host needs a 64-bit DLL, and implementing an in-process extension in managed code is unsupported.89
After registering, changing, or deleting an association, notify with SHChangeNotify(SHCNE_ASSOCCHANGED). At uninstall, the official guidance is to delete your own ProgID and related keys but leave the extension key’s default value. Shell integration is not just showing a menu; it includes applying the change and cleaning up afterward.110
Where to Read, by Goal
| What you want to know, or the symptom you see | Where to read |
|---|---|
| Which implementation method to choose | The decision table in Chapter 6. It separates association-only from adding a custom command to the new menu |
| Support double-click and “Open with” | Associations in Chapter 2 and static verbs in Chapter 3 |
| Registered, but the app does not become the default | UserChoice in Section 2.4. It separates candidate registration from the user’s choice |
| The menu moved one level deeper on Windows 11 | The new and old menus in Chapter 5. The fix is in Section 5.2, and Section 5.3 if you cannot convert to MSIX |
| What the installer should register and remove | Chapter 7. Especially per-user registration in Section 7.3 and cleanup in Section 7.4 |
| The menu is missing, appears twice, or Explorer is slow | Isolation in Chapter 8. DLL constraints are in Chapter 4 |
To learn the mechanism, read from Chapter 2 in order; to decide a policy for an existing app, you can start at Chapter 6.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (16 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. How File Associations Work — The Three-Layer Structure Extension Key → ProgID → Verb
This chapter goes in the order file-side registration → write destination → app-side registration → the user’s choice. The foundation has not changed in more than twenty years: an extension points at a ProgID, a verb holds a command line, and more involved extensions run as in-process COM DLLs.
2.1. Reading the Three-Layer Structure from One Example
First, the basic shape of an association in one example. The extension key points at a ProgID, and a verb inside the ProgID holds the command to launch.1 The precedence that applies when the user has chosen a default app is explained in Section 2.4.
HKEY_CLASSES_ROOT
.kmrpt ← (1) Extension key
(Default) = KomuraSoft.Report.1 ← A pointer that only names the ProgID
OpenWithProgids
KomuraSoft.Report.1 ← Candidate under "Open with"
KomuraSoft.Report.1 ← (2) ProgID (the substance of the association)
(Default) = Komura Report Document
DefaultIcon
(Default) = "C:\Program Files\KomuraSoft\Report.exe",0
shell ← (3) List of verbs
open
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
- (1) The extension key (
.kmrpt) only names a ProgID in its default value. Writing a command directly here is a mistake. - (2) The ProgID (
KomuraSoft.Report.1) is the substance of the association; it holds the display name, the icon, and the list of verbs. - (3) A verb is an action such as “open” or “print”, and the default value of
shell\open\commandis the command line that is actually launched.
This separation is what lets you point several extensions (.kmrpt and .kmrpt-file, for example) at the same ProgID, or swap in a new ProgID when the app is upgraded.
flowchart TB
accTitle: The three-layer structure of file association
accDescr: The extension key is a pointer whose default value only names a ProgID, the ProgID is the substance that holds the display name, icon, and list of verbs, and the default value of command under a verb is the command line that is actually launched
ext["Extension key .kmrpt"] -->|default value names the ProgID| pid["ProgID KomuraSoft.Report.1"]
pid --> vb["verb (open and others under shell)"]
vb --> cmd["Default value of command"]
cmd --> exe["Report.exe is launched"]
pid -.-> attr["Also holds the display name and DefaultIcon"]
Figure 1: The extension key is a pointer, the ProgID is the substance, and the verb’s command is the command line that is actually launched.
2.2. HKCR Is a “Merged View” — Where You Write Changes the Meaning
The example above is shown under HKEY_CLASSES_ROOT (HKCR), but HKCR is not a physical storage location; it is a merged view that overlays HKLM\Software\Classes and HKCU\Software\Classes. If the same key exists in both, the HKCU side wins.3
flowchart TB
accTitle: HKCR is a merged view
accDescr: HKCR overlays the Classes of HKLM and HKCU, the HKCU side takes precedence when the same key exists in both, registrations are written to HKLM or HKCU explicitly, and HKCR is treated as read-only
hklm["HKLM\Software\Classes (all users)"] --> hkcr["HKCR (merged view)"]
hkcu["HKCU\Software\Classes (per user)"] --> hkcr
hkcu -.-> win["If the same key exists, HKCU wins"]
hkcr -.-> ro["Treat it as read-only (for checking)"]
Figure 2: HKCR is how the Classes of HKLM and HKCU look when overlaid; always name one or the other as the write destination.
| Write destination | Meaning | Rights required |
|---|---|---|
HKLM\Software\Classes |
Registration shared by all users | Administrator |
HKCU\Software\Classes |
Registration for that user only | None |
Writing directly to HKCR |
Routed depending on where the existing key lives | Depends |
In practice, the safe rule is to always name either HKLM or HKCU explicitly when writing a registration, and treat HKCR as read-only (for checking).
Do Not Confuse Association Data with the Bitness of COM Registration
Under WOW64 registry redirection, association data and COM registration are treated differently. Association data directly under HKLM\Software\Classes, such as extension keys and ProgIDs, has been shared between the 32-bit and 64-bit registry views since Windows 7, so it does not slip into Wow6432Node even when a 32-bit installer writes it.
On the other hand, some COM-registration subkeys such as Classes\CLSID are redirected, and the 32-bit versus 64-bit write split matters when you register the shell extensions (in-process COM) described later. Details are in “Registry 32-bit/64-bit Redirection and Virtualization Pitfalls”.
2.3. Registration on the App Side — App Paths, Applications, RegisteredApplications
Paired with the file side (extension and ProgID), there are also three kinds of registration on the app side.11
- App Paths (
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths): a registration that letsShellExecuteExlaunch the app by executable name alone. Microsoft recommends it because it avoids polluting the PATH environment variable. - Applications (
HKCR\Applications\<app.exe>): defines the default way the app opens an arbitrary file handed to it through “Open with”, and the app’s display name (FriendlyAppName). - RegisteredApplications + Capabilities: declares the extensions and MIME types the app can handle, so the app appears as a candidate on the Windows default apps settings page.
Most consultations of the form “our app does not appear in the default apps list” are cases where the ProgID was registered but this Capabilities registration was left out.
flowchart TB
accTitle: The three kinds of app-side registration
accDescr: App-side registration comes in three kinds, App Paths, Applications, and RegisteredApplications, which respectively provide launching by executable name alone, the default way of opening through Open with, and a listing on the default apps settings page
app["App-side registration"] --> ap["App Paths"]
app --> apps["Applications"]
app --> ra["RegisteredApplications"]
ap --> r1["Launch by file name alone"]
apps --> r2["Default for Open with"]
ra --> r3["Listed on the default apps page"]
r3 -.-> cap["Requires a Capabilities declaration"]
Figure 3: There are three kinds of app-side registration, and appearing as a candidate on the default apps page requires a Capabilities registration.
2.4. The Default App Belongs to the User — UserChoice Protection
Writing a ProgID into the extension key’s default value does not by itself make your app the default. The result of the user’s explicit choice through “Open with” and similar is kept in HKCU\...\Explorer\FileExts\<extension>\UserChoice, and association resolution gives it precedence.
Do Not Design Around Rewriting UserChoice Directly
Windows does not support programmatic changes to the default app. Default app settings are designed to be made by the user through the system Settings UI; the UserChoice data is obfuscated, and a filter driver (UCPD.sys) blocks writes from apps. In managed environments, Group Policy and MDM policy are the official means.4
That tools such as SetUserFTA, which “mimic the hash and rewrite the value”, have been used in the past is the flip side of this protection.
The Installer Prepares the App to Be Chosen
What you build into your app’s installer is these three things.
- Register the ProgID and verbs correctly.
- Add the app to OpenWithProgIds.
- If needed, steer the user to the default apps settings page.
Rather than seizing the default, you set things up so that the user can choose.
flowchart TB
accTitle: Default app resolution and UserChoice protection
accDescr: The result of the user's explicit choice is kept in UserChoice and takes precedence in association resolution, and UCPD.sys blocks rewrites from apps, so what an installer can do ends at registering as a candidate and steering to the settings page
uc["UserChoice (the user's choice)"] -->|takes precedence| res["Association resolution"]
ext["Extension key default value"] --> res
wr["Rewrite from an app"] -.->|blocked by UCPD.sys| uc
res ~~~ inst["The installer's job"]
inst --> a1["Register the ProgID and verbs"]
inst --> a2["Add to OpenWithProgIds"]
inst --> a3["Steer to the settings page"]
Figure 4: Association resolution gives precedence to the user’s choice (UserChoice), and the OS protects it against rewrites from apps.
3. Verbs Other Than “Open” — print, edit, runas, and Custom Verbs
3.1. Standard Verbs and Custom Verbs
Verbs are not limited to open. The standard verbs whose meaning the OS knows include edit, print, play, and preview in addition to open, and standard verbs automatically get a display name that matches the OS locale. The default verb used on double-click is decided in this order: the default value of the shell key → the first verb in the registry → open → openwith.12
flowchart TB
accTitle: How the default verb is decided
accDescr: The default verb used on double-click is the first one found in the order of the shell key's default value, the first verb in the registry, open, and openwith
s1["Default value of the shell key"] -->|if absent| s2["First verb in the registry"]
s2 -->|if absent| s3["open"]
s3 -->|if absent| s4["openwith"]
Figure 5: The default verb on double-click is the first one found in this order.
To add an action of your own, register a custom verb.
KomuraSoft.Report.1
shell
open
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
print
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" /print "%1"
verify ← custom verb
(Default) = Verify Report (&V) ← display name on the menu
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" /verify "%1"
3.2. Elevation, Shift-Only Display, and Legacy DDE
Verbs also support the following.
- Registering a verb named runas defines an elevated launch equivalent to “Run as administrator”, and it is also used when a
ShellExecute-family API is called withrunasas the verb. - Placing an empty value named
Extendedon the verb key makes it an extended verb that is shown only when you hold Shift while right-clicking. It is handy for hiding a rarely used, risky operation.12 - Associations of older apps sometimes still contain a configuration that sends a document into an already running process through DDE (the
ddeexeckey), but launching a verb through DDE is already deprecated legacy. There is no reason to write it anew.12
3.3. Quote the EXE Path and the Selected File’s Path
A frequent source of trouble is quoting on the command line. If any element of the command string can contain a space, it must be wrapped in quotes. That applies to an EXE path such as C:\Program Files\..., of course, and %1 (the path of the selected file) should always be written as "%1", because you cannot guarantee that a user’s file path contains no spaces. An unquoted My Program.exe is interpreted as “launch My with the argument Program.exe”.13
flowchart TB
accTitle: Quoting mistakes on the command line
accDescr: An unquoted command is split at the spaces and misread as launching My with the argument Program.exe, so an EXE path that can contain spaces and %1, which stands for the selected file's path, are always wrapped in quotes
c1["command without quotes"] -->|split at spaces| bad["Misread as launching a different EXE"]
c2["command with quotes"] --> good["Launches as intended"]
c2 -.-> q1["Wrap the EXE path in quotes"]
q1 -.-> q2["Always wrap %1 in quotes too"]
Figure 6: An unquoted command is wrongly split at spaces, so always wrap the EXE path and %1 in quotes.
3.4. Before Writing a DLL, Check Whether a Static Verb Is Enough
Everything so far is a registry-only mechanism (a static verb): it works without writing a single DLL, and it carries no risk of destabilizing Explorer. Microsoft itself says repeatedly that before writing a shell extension, you should consider whether the simplest static verb that meets the requirements will do.2
4. Classic Shell Extensions — DLLs That Run Inside Explorer
The point to take from this chapter is that a classic extension DLL runs inside the process of Explorer and other hosts. Once you understand where it runs, the constraints on stability, bitness, and implementation language all connect.
4.1. Kinds of Shell Extension
For requirements a static verb cannot meet, such as “change the menu dynamically based on the selection” or “replace the icon or the property sheet”, you use a shell extension handler. The representative kinds are as follows.8
| Handler | Main interfaces | What it can do |
|---|---|---|
| Context menu handler | IContextMenu + IShellExtInit | Add and control menu items dynamically |
| Icon handler / icon overlay | IExtractIcon / IShellIconOverlayIdentifier | Per-file icons and overlays |
| Property sheet handler | IShellPropSheetExt | Add a tab to the property sheet |
| Thumbnail / infotip | IThumbnailProvider / IQueryInfo | Thumbnails and hover descriptions |
| Drag-and-drop / copy hook handler | IDropTarget / ICopyHook | Intervene on drop or on copy and move |
All of these are implemented as COM classes, with their CLSID registered in the registry. For COM itself, see “What Are COM, ActiveX, and OCX?”.
4.2. What Being an In-Process COM Server Means
The essence of a classic shell extension is that it is an in-process COM server (DLL) loaded into the process of Explorer (or of any app that opened a common file dialog). Every caveat follows from this.8
- If the extension crashes, Explorer crashes with it. If it hangs, right-clicking freezes for several seconds. And the damage is not limited to Explorer; it reaches every app that displayed a file-open dialog.
- Menu construction happens on the UI thread, so slow work such as network access or file I/O must not be done while the menu is being displayed.
- As a rule, register the threading model as
Apartment.
flowchart TB
accTitle: How an in-process extension takes its host down
accDescr: A shell extension DLL is loaded not only into Explorer but also into the process of any app that opens a file dialog, so a crash or hang in the extension spreads to the whole host process
dll["Shell extension DLL"] -->|loaded in-process| exp["Explorer"]
dll -->|loaded in-process| any["Any app that opens a dialog"]
exp --> dmg["Crash or hang spreads"]
any --> dmg
dmg -.-> rule["No slow work at display time"]
Figure 7: The extension DLL runs inside the host process, so a crash or hang spreads to the whole host.
When we investigate consultations such as “Explorer freezes when I open a particular folder” or “right-clicking takes five seconds”, it is not unusual for the cause to be a third-party shell extension rather than the customer’s own app. Chapter 8 covers how to isolate it.
4.3. Matching Bitness — A 64-bit Environment Requires a 64-bit DLL
An in-process DLL must match the bitness of the process that loads it. Explorer on 64-bit Windows is a 64-bit process, so a shell extension DLL built only for 32-bit is never loaded at all and never appears on the menu. Because there is no error either, this is a classic cause of “I registered it but it does not appear”.
The App Itself Does Not Have to Become 64-bit
Combining a 32-bit app with a 64-bit shell extension DLL is a legitimate configuration, but be aware that COM registration is split by bitness (Wow6432Node). Note also that what a verb’s command launches is an EXE in a separate process, so it is not subject to this constraint (a 32-bit EXE is fine as it is).
flowchart TB
accTitle: Bitness matching for a shell extension DLL
accDescr: The only shell extension DLL a 64-bit Explorer can load is a 64-bit one, a 32-bit-only DLL produces no error and never appears on the menu, and an EXE launched from a verb's command is a separate process and is not subject to the constraint
exp["64-bit Explorer"] -->|can load| d64["64-bit shell extension DLL"]
exp -.->|cannot load| d32["32-bit-only DLL"]
d32 -.-> sym["Missing from the menu with no error"]
exe["EXE launched from a verb"] -->|separate process| ok32["Fine as 32-bit"]
Figure 8: The only DLL loaded into 64-bit Explorer is a 64-bit DLL; an EXE launched from a verb is not subject to this constraint.
4.4. Why You Must Not Write It in Managed Code
We often get the question “can’t we write the shell extension in C#?”, but Microsoft explicitly does not recommend writing in-process shell extensions in managed code (.NET) and declares them unsupported.9
The reason lies in the nature of an extension being loaded into arbitrary processes. There are three main factors that destabilize the host app.
- CLR version conflicts. These are a problem especially below .NET Framework 4.
- Reentrancy. The CLR can reenter the message loop while waiting on a lock.
- Object lifetime. The non-deterministic lifetime imposed by garbage collection conflicts with COM’s reference-counting contract.
Some of these have been mitigated in .NET Framework 4 and later and in modern .NET, but the official position has not changed.
The practical guideline is simple. Write in-process extensions in native C++. If you want to use managed code, make it a normal EXE launched from a verb’s command, or an out-of-process extension that runs in a separate process (a preview handler, for example).9
flowchart TB
accTitle: Deciding whether managed code is acceptable
accDescr: An in-process extension that runs inside the Explorer process is written in native C++ as a rule, and if you want managed code, make it a normal EXE launched from a verb's command or an out-of-process extension that runs in a separate process
q1{"Runs in-process?"} -->|yes| cpp["Write it in native C++"]
q1 -->|no| mg["Managed code is fine"]
cpp -.-> why["CLR conflicts and reentrancy destabilize the host"]
mg --> e1["Normal EXE launched from a verb"]
mg --> e2["Separate-process extension such as preview"]
Figure 9: In-process extensions are native C++ as a rule; managed code is limited to configurations that run in a separate process.
5. The Windows 11 New Context Menu — A Menu Split in Two
This chapter goes in the order why items moved to the old menu → registering on the new menu → how to keep an existing installer. The difference between an association that only does “open with this app” and adding a custom command is covered in Section 5.4.
5.1. What Happened
Windows 11 redesigned the File Explorer context menu. Cut, copy, and the like became a row of icons at the top; “Open” and “Open with” are grouped together at the top; and commands added by apps are grouped below the shell’s standard commands. When one app adds several commands, they are collected into a flyout (submenu) labeled with the app’s name.5
And here is the crucial point. Classic IContextMenu-based shell extensions were not removed; they were relegated to the old menu, which opens with “Show more options” (Shift+F10) and loads the Windows 10 menu as it was.5 The “hidden menu” in the opening consultation is this split.
flowchart TB
accTitle: The context menu split in two on Windows 11
accDescr: A right-click first opens the new menu, which shows only commands registered with IExplorerCommand and package identity, while classic IContextMenu extensions are relegated to the old menu that opens with Show more options
rc["Right-click a file"] --> newm["New menu (Windows 11)"]
newm --> newi["IExplorerCommand + identity commands"]
newm -->|Show more options Shift+F10| oldm["Old menu (the Windows 10 menu)"]
oldm --> oldi["Classic IContextMenu extensions"]
newi -.-> fly["Several commands are collected into a flyout"]
Figure 10: The only commands on the new menu are IExplorerCommand + identity commands; classic extensions are relegated to the old menu.
5.2. The Official Route onto the New Menu — IExplorerCommand + Manifest Registration
The official route for putting a custom command on the new menu is a native DLL that implements IExplorerCommand, registered in an MSIX manifest.6
The Manifest Needs Two Declarations
In the example below, the first half declares the COM server (the CLSID and the implementing DLL) and the second half declares the context menu extension (the target and the command). The same CLSID ties the two together.
<!-- Package manifest (excerpt) -->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Komura commands">
<com:Class Id="01234567-89AB-CDEF-0123-456789ABCDEF"
Path="KomuraCommand.dll" ThreadingModel="STA" />
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type=".kmrpt">
<desktop5:Verb Id="VerifyReport"
Clsid="01234567-89AB-CDEF-0123-456789ABCDEF" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
The Type of ItemType can be a specific extension, or * (all files), Directory (folders), or Directory\Background (the folder background). The DLL must match Explorer’s architecture (64-bit or ARM64).6
Menu-Building Methods Must Return Quickly
IExplorerCommand itself is an interface that dates from the Windows 7 era; you implement the title (GetTitle), the icon (GetIcon), the enabled, disabled, or hidden state (GetState), and execution (Invoke). The methods are called from the UI thread, so access to network resources is prohibited, and the menu-building methods must return quickly. Do heavy work after Invoke.146
flowchart TB
accTitle: Manifest structure for a new-menu registration
accDescr: The COM server declaration in the MSIX manifest maps the CLSID to the DLL, and the context menu extension declaration ties the target and the implementation together through ItemType and Verb, so the custom command appears on the new menu
man["MSIX manifest"] --> com["COM server declaration"]
man --> ctx["Menu extension declaration"]
com -->|maps CLSID to DLL| impl["IExplorerCommand implementation DLL"]
ctx -->|specified by ItemType and Verb| impl
impl --> shown["Command shown on the new menu"]
ctx -.-> tgt["Target is an extension, all files, etc."]
Figure 11: The two declarations in the manifest tie the implementation DLL to its target, and the command appears on the new menu.
5.3. The Option for Non-Packaged Apps — Gaining Identity Alone with a Sparse Package
When “our app can only be distributed as an MSI, and MSIX is out of the question”, the way out is a sparse package (MSIX with external location). You build and sign a small MSIX that holds only a manifest and no app files, and register it at the end of the existing installer. The app thereby acquires package identity, and the manifest registration above (that is, appearing on the new menu) becomes possible.
It is available on Windows 10 version 2004 and later, and the package must be signed with a certificate trusted on the target machine.7 The order of registration and removal, and the caveat that the registration is per user, are covered in Section 7.3.
flowchart TB
accTitle: How a sparse package grants identity
accDescr: After the existing installer places the app files, registering a manifest-only sparse package with an external location gives the app package identity and makes manifest registration on the new menu possible
inst["Existing installer"] --> files["Place the app files"]
sp["Sparse package"] -.-> only["Manifest only, no app files"]
files --> reg["Register with an external location"]
sp --> reg
reg --> id["Acquire package identity"]
id --> ok["New-menu registration becomes possible"]
sp -.-> sign["Requires a trusted signature"]
Figure 12: Register a sparse package that contains no app files, with an external location, and the app acquires package identity.
Its greatest advantage is that you do not have to replace the installer, which makes it the realistic answer for an app with existing MSI/EXE installer assets. For a comparison with a full move to MSIX, see also “Choosing a Windows App Distribution Method”.
5.4. How Association Verbs Appear on the New Menu
This is easy to misunderstand: the associations from Chapters 2 and 3 (ProgID and verbs) are still alive on the new menu. The default verb on double-click, “Open”, and the “Open with” candidates are resolved from the association and shown at the top of the new menu. In other words, if all you want is “make files openable with this app”, Windows 11 requires no additional work.
On the other hand, an association is not a general-purpose menu extension, so if you want an arbitrary custom command on the first level of the new menu, you need IExplorerCommand plus identity. That is the division of roles.6
flowchart TB
accTitle: Division of roles between associations and the new menu
accDescr: A ProgID-and-verb association is still used on the new menu to resolve the default verb on double-click, Open, and Open with, and is shown at the top, while putting an arbitrary custom command on the first level of the new menu requires IExplorerCommand and identity
assoc["Association (ProgID and verbs)"] --> sol["Resolve the default verb and Open"]
sol --> top["Shown at the top of the new menu"]
assoc -.-> keep["No extra work on Windows 11"]
cmd["Arbitrary custom command"] --> need["IExplorerCommand + identity"]
need --> first["Shown on the first level of the new menu"]
Figure 13: Associations still handle “Open”-type resolution on the new menu; only custom commands require IExplorerCommand plus identity.
6. A Practical Decision Table — Which of the Three Options to Take
First check whether (a) is enough; if you need a custom command on the new menu, choose (b). Keeping an existing classic extension for the time being is (c).
| What you want to achieve | Recommended means | How it appears on Windows 11 | Work and cost required |
|---|---|---|---|
| (a) Launch your app on double-click or “Open” | Association + static verb (registry registration only) | Integrated into “Open” and “Open with” on the new menu | Registry registration by the installer only. No DLL, no additional signing requirement |
| (b) Put a custom command for the selected file or folder on the new menu | IExplorerCommand implementation + MSIX manifest registration. A non-packaged app gains identity with a sparse package | First level of the new menu (several commands are collected into a flyout labeled with the app name) | Native C++ DLL + package identity + code signing |
| (c) Keep using an existing classic IContextMenu extension | Keep it as is for now (do not choose it for new development) | Only on the old menu under “Show more options” (Shift+F10) | Maintain the 64-bit build and COM registration. Plan a future move to (b) |
Decision 1: Choose the Simplest Method That Meets the Requirement
The first rule is not to bring in (b) or (c) for a requirement that (a) satisfies. The moment you write a shell extension, you take on responsibility for Explorer’s stability.
Decision 2: Separate Keeping the Old Menu from Improving Usability
(c) is merely “not broken”; as a user experience it stays permanently one step lower. The more often a command is used in daily work, the greater the return on investing in a move to (b).
flowchart TB
accTitle: How to choose among the three options
accDescr: If all you want is launching on double-click or Open, an association and a static verb suffice, to put a custom command on the new menu use IExplorerCommand and MSIX manifest registration, if you cannot convert to MSIX grant identity with a sparse package, and keep an existing classic IContextMenu extension on the old menu for now
q1{"Only need Open?"} -->|yes| pa["Association + static verb"]
q1 -->|no| q2{"Custom command on the new menu?"}
q2 -->|yes| q3{"Can you convert to MSIX?"}
q3 -->|yes| pb1["IExplorerCommand+MSIX"]
q3 -->|no| pb2["Identity via sparse package"]
q2 -->|no| pc["Keep the classic one for now"]
pc -.-> old["Shown only on the old menu"]
pa -.-> dllfree["No DLL, low risk"]
Figure 14: Depending on the requirement, choose among a static verb, IExplorerCommand plus identity, and keeping the classic extension.
7. Deployment and Registration in Practice — Installer, Sparse Package, Cleanup
Once the implementation method is decided, build the write destination, change notification, package registration and removal, and cleanup at uninstall into the installer.
7.1. HKLM or HKCU
Match the write destination to the form of the installer. For all users (placed under Program Files, administrator rights), use HKLM\Software\Classes; for a per-user install (no elevation), use HKCU\Software\Classes. Mixing them produces inquiries of the form “it opens for user A but not for user B”.
For a shell extension that involves CLSID registration, Reg-Free COM, which removes the need for registry registration altogether, is a valid option for COM used inside your own app, but it cannot be applied to a shell extension that Explorer loads, so the conventional registration is required (see “What Is Reg-Free COM?”).
7.2. Notify After a Change — SHChangeNotify
After registering, changing, or deleting an association, notify the SHCNE_ASSOCCHANGED event with SHChangeNotify. If you skip this, Explorer may not recognize the change until a restart.110
// Call once after changing associations, for example from an installer custom action
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
7.3. Registering and Removing a Sparse Package
Registering and removing a sparse package is the installer’s job. Register after placing the files, and remove before deleting the files.7
# At install time: after placing the files, register the install folder as the external location
Add-AppxPackage -Path "C:\Program Files\KomuraSoft\KomuraReport.identity.msix" `
-ExternalLocation "C:\Program Files\KomuraSoft"
# At uninstall time: remove the package registration before deleting the files
Remove-AppxPackage <package full name>
Separate Registration for the Running User from Deployment to All Users
Add-AppxPackage registers the package for the user who runs it. If it runs as LocalSystem from a custom action of a per-machine MSI, the user who performed the installation is not granted identity. So configure it to run under user impersonation.
However, even with impersonation, the registration applies only to the user who ran that installation. When one PC is shared by several users, the other users and any users created later have no package identity, and the command does not appear on their new menu.
If every user must be able to use it, provide per-user registration, for example by having the app check its own package registration at first launch and register if it is missing. Include removal from each registered user in the uninstall plan as well.
If the Menu Does Not Reflect the Registration
For a manifest registration to take effect, an Explorer restart (or a sign-out) may be required.6
flowchart TB
accTitle: Order of registering and removing a sparse package
accDescr: At install time register the sparse package after placing the files, at uninstall time remove the registration before deleting the files, and note that the registration applies only to the user who ran it
i1["Install"] --> i2["Place the files"]
i2 --> i3["Register the sparse package"]
u1["Uninstall"] --> u2["Remove the package registration"]
u2 --> u3["Delete the files"]
i3 -.-> pu["Registration applies only to the running user"]
Figure 15: Register after placing the files, remove before deleting them, and note that registration is per running user.
7.4. Cleanup at Uninstall — What to Delete and What to Leave
The official guide gives clear direction on cleanup at uninstall.1
- Delete: your own ProgID keys in full, the Capabilities/RegisteredApplications registration, the shell extension’s CLSID registration, and the sparse package (Remove-AppxPackage).
- Leave: the default value of the extension key (
.kmrpt). The official recommendation is not to delete it even if it still points at your ProgID. It is hard to determine whether another app has taken the default since the installation, and Windows simply ignores a default-value ProgID that is not registered, so leaving it does no harm. - Call SHChangeNotify(SHCNE_ASSOCCHANGED) at the end of the cleanup as well.
Most “we uninstalled it, but leftovers still show on the menu” problems are gaps in this cleanup design.
flowchart TB
accTitle: Cleanup design at uninstall
accDescr: At uninstall, delete your own ProgID keys, CLSID registration, and sparse package, leave the extension key's default value because an unregistered ProgID is ignored, and notify the change with SHChangeNotify at the end of the cleanup
un["Uninstall"] --> del["Delete"]
un --> keep["Leave"]
del --> d1["ProgID and CLSID registration"]
del --> d2["Sparse package"]
keep --> k1["Extension key default value"]
k1 -.-> why["An unregistered ProgID is ignored"]
d1 --> fin["Notify with SHChangeNotify at the end"]
k1 --> fin
Figure 16: Delete your own registrations, leave the extension key’s default value, and notify the change at the end of the cleanup.
8. Troubleshooting — Missing, Duplicate, Slow
Investigate problems in three groups: “missing”, “appears twice or will not go away”, and “slow or crashing”. Finally, we explain how to verify registration and cleanup in a clean environment.
8.1. It Does Not Appear on the Menu
First, check which menu you are looking at, new or old. Then isolate in the following order.
- Which menu you are looking at: a classic-style registration appears only on the old menu under Shift+F10. Check both first.
- Bitness: a 32-bit-only shell extension DLL is not loaded into 64-bit Explorer (Section 4.3).
- Write destination: a mix-up between HKLM and HKCU, or Wow6432Node. Check the actual key with
reg query. - Package registration: for the new menu, check with
Get-AppxPackagewhether the package is registered, whether the signing certificate is trusted, and the-ExternalLocationpath, then restart Explorer.6 - Missing notification: if SHChangeNotify was forgotten, you can tell by whether an Explorer restart makes the change take effect.
flowchart TB
accTitle: Isolation order when an item does not appear on the menu
accDescr: Start by checking which menu you are looking at, then isolate in order the DLL bitness, the registry write destination, the package registration and signature, and a missing SHChangeNotify notification
c1["Check which menu, new or old"] --> c2["Check the DLL bitness"]
c2 --> c3["Check the HKLM or HKCU write destination"]
c3 --> c4["Check package registration and signature"]
c4 --> c5["Detect a missing notification by restarting"]
Figure 17: When an item is “missing”, isolate in this order: which menu you are looking at, bitness, write destination, package registration, missing notification.
8.2. It Appears Twice, or Will Not Go Away
First, narrow it down by which menu has the duplicate.
- Duplicate only on the old menu: suspect a cleanup gap at uninstall (Section 7.4) or leftovers of an old version’s ProgID.
- Appears on both new and old: suspect a classic registry registration coexisting with an MSIX manifest registration.
Both are typical starting points for isolating the cause; confirm by checking what is actually registered.
flowchart TB
accTitle: Isolating a duplicate entry
accDescr: A duplicate only on the old menu points to leftovers such as a cleanup gap or an old ProgID, while an entry on both new and old menus points to a classic registry registration coexisting with a manifest registration
q{"Which menu has the duplicate?"} -->|old menu only| zan["Leftovers"]
q -->|both new and old| hei["Coexistence"]
zan -.-> z1["Cleanup gap or an old ProgID left behind"]
hei -.-> h1["Classic registry registration coexisting with the new registration"]
Figure 18: A duplicate only on the old menu points to leftovers; an entry on both new and old points to coexistence.
8.3. Explorer Is Slow or Crashes
When right-clicking is slow or a particular folder crashes Explorer, start by taking stock of the installed shell extensions.
- List the extensions. Use a tool such as NirSoft’s ShellExView to check the non-Microsoft extensions.
- Disable temporarily to narrow down. Binary-search the suspicious ones and identify the DLL responsible. For a crash, the “Faulting module” in Event Viewer is another clue.
- If it is your own extension, examine the menu-building path. Suspect synchronous I/O or network access (Sections 4.2 and 5.2).
flowchart TB
accTitle: Identifying the responsible DLL when Explorer is slow or crashes
accDescr: List non-Microsoft shell extensions with ShellExView, temporarily disable suspicious ones while binary-searching to identify the responsible DLL, and for a crash also use the faulting module in Event Viewer as a clue
s1["Take stock of shell extensions"] --> s2["List the non-Microsoft ones"]
s2 --> s3["Disable temporarily and binary-search"]
s3 --> s4["Identify the responsible DLL"]
crash["In case of a crash"] -.-> ev["Check the faulting module"]
ev -.-> s4
Figure 19: Binary-search by temporarily disabling non-Microsoft extensions, and for a crash also use Event Viewer.
8.4. Windows Sandbox Is Handy for Verification
The basic verification of shell integration is to confirm “install in a clean environment → operate → uninstall → zero leftovers”. Windows Sandbox (Pro, Enterprise, or Education) is handy here: every launch brings up a pristine, disposable Windows in a few seconds, so you can run the installer’s registration and cleanup tests as many times as you like. Closing it discards everything, which also makes it suitable for investigating registry leftovers.15
9. Summary
When a move to Windows 11 brings the complaint that “the menu got hidden”, first check what you want to achieve against the decision table in Chapter 6. Think in these three stages.
1. Decide Whether an Association Is Enough
If all you need is “open with this app”, an association and a static verb are still enough. The foundation is extension key → ProgID → verb, and HKCR is a view for checking that overlays the Classes of HKLM and HKCU. Name the write destination explicitly, and always wrap %1 in quotes.
The user, however, is the one who chooses the default app. The installer registers the app correctly as a candidate rather than seizing the default.
2. Decide Which Menu Your Custom Command Goes On
Classic IContextMenu extensions run on the old menu that opens with “Show more options”. Putting a custom command on the new menu requires IExplorerCommand and registration in an MSIX manifest. For an app that cannot be fully converted to MSIX, gaining package identity with a sparse package is the realistic answer.
A classic extension DLL runs inside the process of Explorer and other hosts. A crash or delay spreads to the whole host, and a 64-bit host needs a 64-bit DLL. Writing an in-process extension in managed code is unsupported; native C++ is the rule.
3. Verify Registration, Change Notification, and Removal as One Set
After registering, changing, or deleting an association, notify with SHChangeNotify. At uninstall, delete your own ProgID and related keys, but leave the extension key’s default value. A sparse package is registered per user, so in a multi-user environment include registration and removal for each user in the plan.
Use Windows Sandbox and repeat install in a clean environment → operate → uninstall → check for leftovers.
Choose the simplest means first, and move on to adding a custom command to the new menu only when required. Following this order lets you size the work and determine the scope of registrations and implementation you have to maintain.
Related Articles
- What Are COM, ActiveX, and OCX? - The Differences and Relationships Explained
- What Is Reg-Free COM? - Using COM Without Registration
- Registry 32-bit/64-bit Redirection and Virtualization Pitfalls — Wow6432Node and the “Value I Wrote Is Not There” Problem
- Choosing a Windows App Distribution Method - MSI/MSIX/ClickOnce/xcopy/Custom Updater
- DLL and COM Interface Backward Compatibility — A Decision Table for Which Changes Break Callers
- How Windows Application Compatibility Works — Keeping Old Apps Alive with Compatibility Mode, Shims, and Compatibility Administrator
Related Consulting Areas
KomuraSoft LLC handles the design and implementation of file associations, context menus, and shell extensions for business apps; support for the Windows 11 new context menu (migrating to IExplorerCommand, introducing a sparse package); reviewing the registration and cleanup of existing installers; and investigating the cause when Explorer is slow or crashes. You are welcome to start from deciding what to do about “the menu that got hidden under Show more options”.
- Windows Application Development
- Legacy Asset Migration
- Technical Consulting and Design Review
- Contact Us
References
-
Microsoft Learn, File Types. On the structure in which an extension key points at a ProgID, OpenWithProgIds, when to register under HKLM versus HKCU\Software\Classes, calling SHChangeNotify(SHCNE_ASSOCCHANGED) after an association change, and deleting the ProgID at uninstall while leaving the extension key’s default value. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Choosing a Static or Dynamic Shortcut Menu Method. On choosing the simplest static-verb method that meets the requirements, IContextMenu being the most powerful but also the most complex and classified on the not-recommended side, and IExplorerCommand/IExplorerCommandState being the recommended method. ↩ ↩2
-
Microsoft Learn, HKEY_CLASSES_ROOT Key. On HKEY_CLASSES_ROOT being a merged view of HKLM\Software\Classes and HKCU\Software\Classes, the user-side definition taking precedence over the machine-side one, and the routing rules for writes. ↩ ↩2
-
Microsoft Learn, Windows app defaults platform. On default app changes being designed to happen only through the system Settings UI, user settings data being obfuscated and write-protected by a filter driver (UCPD.sys), registry-based changes being unsupported, and using Group Policy or MDM policy in managed environments. ↩ ↩2
-
Windows Developer Blog, Extending the Context Menu and Share Dialog in Windows 11. On the design of the Windows 11 new context menu, extension through IExplorerCommand plus app identity, placing “Open” and “Open with” at the top, collecting several commands into a flyout labeled with the app name, and classic IContextMenu extensions being loaded as the Windows 10 menu under “Show more options” (Shift+F10). ↩ ↩2 ↩3
-
Microsoft Learn, Add a File Explorer context menu command to a packaged desktop app. On registration on the Windows 11 new context menu being done with an IExplorerCommand implementation plus windows.comServer and desktop4:FileExplorerContextMenus manifest declarations, ItemType accepting *, Directory, and Directory\Background, matching the DLL architecture, keeping menu-building methods fast, supporting non-packaged apps with a sparse package, an Explorer restart sometimes being required for the registration to take effect, and file associations not being a general-purpose menu extension. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Grant package identity by packaging with external location. On gaining package identity by registering a package with external location (sparse package) without changing the existing installer, availability on Windows 10 version 2004 and later, and Windows features that require identity (context menu registration, notifications, and so on) becoming usable. ↩ ↩2 ↩3
-
Microsoft Learn, Working with Shell Extensions. On the kinds of shell extension handler, an extension being an in-process COM DLL loaded into Explorer (and into processes that host the shell) so that a crash or hang spreads to all of Explorer, registration with ThreadingModel=Apartment, and considering simpler alternatives before a shell extension. ↩ ↩2 ↩3
-
Microsoft Learn, Guidance for Implementing In-Process Extensions. On Microsoft not recommending and not supporting in-process shell extensions implemented in managed code, the reasons including CLR version conflicts, reentrancy, and non-deterministic object lifetime, and managed code being acceptable for out-of-process extensions (preview handlers, or launching from shell\verb\command). ↩ ↩2 ↩3
-
Microsoft Learn, SHChangeNotify function. On how to raise the SHCNE_ASSOCCHANGED event that notifies the system of a file association change, and how to use it so that the shell recognizes the change. ↩ ↩2
-
Microsoft Learn, Application Registration. On registering an executable through the App Paths subkey being the recommended approach, the role of the Applications subkey, verb registration through SystemFileAssociations, and the precedence of the ProgID and related information when the default app changes. ↩
-
Microsoft Learn, Verbs and File Associations. On verbs being the actions also used by ShellExecuteEx, elements of a command string that can contain spaces needing to be wrapped in quotes and “%1” always being written quoted, and registering the default procedure under HKCR\Applications. ↩
-
Microsoft Learn, IExplorerCommand interface. On the method set of GetTitle, GetIcon, GetState, Invoke, EnumSubCommands, and others, the methods being called on the UI thread and therefore not being allowed to communicate with network resources, and availability from Windows Vista onward. ↩
-
Microsoft Learn, Windows Sandbox. On being able to start a disposable, isolated Windows environment in a few seconds, all changes being discarded when it is closed, its suitability for software testing and installer verification, and availability on Pro, Enterprise, and Education. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
End of Servicing for Windows Printer Drivers — How Business Apps Should Prepare Their Report and Label Printing
Microsoft is phasing out v3/v4 printer drivers. What Windows protected print mode removes, and how to inventory and prepare report and la...
WinRT Is COM — IInspectable, .winmd, Language Projections, and Why WinUI Still Rests on a Binary Contract
WinRT is not a managed runtime but an ABI built on COM plus .winmd metadata and language projections. Covers IUnknown vs. IInspectable, H...
What Is an OLE Object? — How Embedding and Linking Work and the Pitfalls in Business Documents
An OLE object is what embeds an Excel table in Word. Learn embedding vs. linking, compound files, In-Place Activation, broken links, bloa...
How the Clipboard and Drag & Drop Work — Handling OLE Data Transfer Correctly in Business Apps
Why Excel pastes break and paste fails once the source closes: clipboard formats, delayed rendering, OLE drag and drop, and clipboard his...
Windows App Outsourcing and Custom Software Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or custom software development, here is how to sort out existing software modification, devi...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Windows Software Maintenance & Modernization
We support staged upgrades, feature additions, 64-bit readiness, and maintainable restructuring for existing Windows software.
Frequently Asked Questions
Common questions about the topic of this article.
- Why does our app's context-menu item on Windows 11 appear only under "Show more options"?
- Because on Windows 11 the File Explorer context menu split into two layers, new and old. The only commands that can appear on the new menu are ones that implement the IExplorerCommand interface and are registered in an MSIX package manifest (that is, they have package identity). Classic IContextMenu-based shell extensions were moved to the old menu that opens with "Show more options" (Shift+F10). The extension itself is not broken, so it keeps working for now, but if you want it on the new menu you need to migrate to IExplorerCommand and gain identity either by packaging as MSIX or with a sparse package.
- Can our installer set our app as the default app for a file (the app that opens it on double-click)?
- No. Choosing the default app is designed to be the user's action, and Windows does not support changing the default app from anywhere other than the system Settings UI. The UserChoice information that holds each user's choice is obfuscated, and a filter driver (UCPD.sys) also protects it against writes from apps. What an installer can do is register a ProgID and verbs, add itself to OpenWithProgIds so it appears as a candidate under "Open with", and steer the user to the default apps settings page. The correct implementation is not to seize the default but to be ready to be chosen.
- May I write a shell extension in managed code such as C#?
- Microsoft states plainly that writing an in-process shell extension (a context menu handler, an icon handler, and the like) in managed code is not recommended and is unsupported. The extension is loaded into the process of Explorer or of any app that opens a common file dialog, so CLR version conflicts, reentrancy, and non-deterministic object lifetime destabilize the host app. Native C++ is the rule for the implementation. On the other hand, a normal EXE launched from a verb's command, or an out-of-process extension such as a preview handler that runs in a separate process, is fine in managed code.
- What is a sparse package (MSIX with external location)?
- A small MSIX package that contains no app files, only a manifest (identity information). For an app installed normally by an existing installer (MSI, Inno Setup, and so on), you register the package with Add-AppxPackage -ExternalLocation pointing at the install folder; the app then acquires package identity and can use features that require identity, such as registration on the Windows 11 new context menu and toast notifications. It is available on Windows 10 version 2004 and later, and the package needs a code signature trusted on the target machine. It is the realistic option when you want new-menu support without moving the whole distribution method to MSIX.
- What should I do when a context-menu item appears twice, or will not go away?
- First isolate the cause by checking which menu it appears on: the new menu or the old menu (Show more options). A double entry is typically either a classic registry registration coexisting with an MSIX manifest registration, or a ProgID or extension CLSID registration that was not cleaned up at uninstall. After changing associations, also suspect a missing SHChangeNotify(SHCNE_ASSOCCHANGED) notification; right after package registration, suspect a missing Explorer restart. If that still does not resolve it, temporarily disable non-Microsoft extensions in ShellExView and binary-search to identify the DLL responsible.