Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11

· · Windows, Shell Extensions, Context Menu, File Association, COM, Windows 11, File Explorer, MSIX, Windows Development

I was consulted with “we replaced the PCs with Windows 11, and the context menu of the app you built for us years ago disappeared”. Listening more carefully, it had not disappeared. Right-click a file, choose “Show more options” at the bottom of the menu, and the familiar menu appears just as it always did. In other words, the in-house app’s menu items had been hidden one click further in. From the field we hear “it is one extra click” and “inquiries that they cannot find the item have increased”.

This is neither a failure nor a misconfiguration; it is a Windows 11 design change. File Explorer’s context menu became a two-layer structure, old and new, and the conditions for putting an item on the new menu became a completely different thing from before.

Meanwhile, the file-association and shell-extension machinery underneath is still the old world of COM and the registry. An extension key points at a ProgID, the ProgID’s verb holds a command line, and a more involved extension runs as an in-process COM server (DLL) loaded into Explorer — that structure has not changed in more than twenty years. If you do not know both the unchanged foundation and the menu that Windows 11 split in two, you cannot isolate “the menu does not appear”, “it is hidden”, or “it appears twice”.

This article is aimed at IT staff at small and midsize companies and at Windows developers who look after business apps. It ties together, in a single picture, the three-layer structure of file association, caveats of classic shell extensions, how to target the Windows 11 new context menu, and installer registration, cleanup, and troubleshooting.

1. The Bottom Line First

  • The foundation of the context menu and of file association is the three-layer registry structure “extension key → ProgID → verb”. The extension key is a pointer to a ProgID, the ProgID is the substance, and shell\<verb>\command under it holds the command line.1
  • HKEY_CLASSES_ROOT (HKCR) is not an independent hive; it is a merged view of HKLM\Software\Classes and HKCU\Software\Classes. Write all-user registration to HKLM and per-user registration to HKCU, and treat HKCR as read-only.2
  • The default app (the app that opens on double-click) is designed to be chosen by the user, and a program cannot steal it. The OS protects the user’s choice; what an installer can do is register as a candidate.3
  • A classic shell extension is an in-process COM DLL loaded into Explorer. A crash or delay in the extension spreads to Explorer as a whole (and to other apps that use the shell); a 64-bit environment requires a 64-bit DLL; and a managed-code implementation is unsupported.45
  • On Windows 11 the context menu split in two. The only commands that appear on the new menu are ones registered with IExplorerCommand plus package identity; classic IContextMenu extensions are moved to the old menu under “Show more options” (Shift+F10).67
  • The official route for putting a custom command on the new menu is to register a native DLL that implements IExplorerCommand in an MSIX manifest (desktop4:FileExplorerContextMenus). An app that cannot become MSIX can be given identity alone with a sparse package (MSIX with external location).78
  • If all you want is “open with this app”, an association and a static verb are still enough. You do not need a shell-extension DLL, and Microsoft itself states plainly “choose the simplest method that meets the requirements (a static verb)”.9
  • After registration or a change, notify with SHChangeNotify(SHCNE_ASSOCCHANGED); at uninstall, delete the ProgID but do not delete the extension key’s default value — that is the official guide. Shell integration includes the design of the cleanup.110

In one sentence: the world of associations and verbs is unchanged; only how the menu is shown split in two on Windows 11. Below we walk through this from the foundation up.

2. How File Association Works — The Three-Layer Structure Extension Key → ProgID → Verb

2.1. Reading the Three-Layer Structure from One Example

What happens when you double-click a file of a given extension is decided by three layers of registry keys.1

HKEY_CLASSES_ROOT
   .kmrpt                                  ← (1) Extension key
      (Default) = KomuraSoft.Report.1      ←     A pointer that only names the ProgID
      OpenWithProgids
         KomuraSoft.Report.1               ←     A 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 points at a ProgID name as its default value. Writing a command here directly is a mistake.
  • (2) The ProgID (KomuraSoft.Report.1) is the substance of the association; it holds the display name, the icon, and the verb list.
  • (3) A verb is an action such as “open” or “print”, and the default value of shell\open\command is the command line that is actually launched.

This separation is why you can point several extensions (.kmrpt and .kmrpt-file, for example) at the same ProgID, or swap the ProgID when you upgrade the app.

The three-layer structure of file associationThe extension key is a pointer whose default value names a ProgID; the ProgID is the substance that holds the display name, icon, and verb list; and the default value of command under the verb is the command line that is actually launchednames the ProgID as defaultExtension key .kmrptProgID KomuraSoft.Report.1verb (open and others under shell)command default valueReport.exe is launchedAlso 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 of HKLM\Software\Classes and HKCU\Software\Classes. If the same key exists in both, the HKCU side wins.2

HKCR is a merged viewHKCR is HKLM and HKCU Classes laid on top of each other; if the same key exists in both, HKCU wins; write registration to HKLM or HKCU explicitly and treat HKCR as read-onlyHKLM\\Software\\Classes (all users)HKCR (merged view)HKCU\\Software\\Classes (per user)If the same key exists, HKCU winsTreat it as read-only (for confirmation)

Figure 2: HKCR is how HKLM and HKCU Classes look when laid together; always name one or the other as the write destination.

Write destination Meaning Rights required
HKLM\Software\Classes Registration common to all users Administrator
HKCU\Software\Classes Registration for that user only None
Writing to HKCR directly Dispatched depending on where the existing key already lives Depends

In practice, the safe split is to always write registration to either HKLM or HKCU explicitly, and treat HKCR as read-only (for confirmation). The relationship with WOW64 registry redirection is also worth sorting out. 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 a 32-bit installer writing it does not escape to the Wow6432Node side. Some COM-registration subkeys such as Classes\CLSID, on the other hand, are redirected, and when you register a shell extension (in-process COM) the 32-bit / 64-bit write split matters. Details are in “Registry 32-bit/64-bit Redirection and Virtualization Pitfalls”.

2.3. Registration on the App Side — App Paths, Applications, RegisteredApplications

There are also three kinds of registration on the app side, pairing with the file side (extension and ProgID).11

  • App Paths (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths): Registration that lets ShellExecuteEx launch by executable file name alone. Microsoft recommends this because you do not have to pollute the PATH environment variable.
  • Applications (HKCR\Applications\<app.exe>): Defines the default way to open when an arbitrary file is handed over under “Open with”, and the app’s display name (FriendlyAppName).
  • RegisteredApplications + Capabilities: Declares the extensions and MIME types the app can handle, and is the registration that makes it appear as a candidate on the Windows Default apps settings page.

Most consultations of “our app does not appear in the Default apps list” are cases where the ProgID was registered and this Capabilities registration was omitted.

The three kinds of registration on the app sideApp-side registration has three kinds — App Paths, Applications, and RegisteredApplications — responsible respectively for launch by file name alone, the default way to open under Open with, and appearance on the Default apps settings pageApp-side registrationApp PathsApplicationsRegisteredApplicationsLaunch by file name aloneDefault under Open withAppears on the Default apps pageA Capabilities declaration is required

Figure 3: There are three kinds of app-side registration, and appearing as a Default apps candidate requires a Capabilities registration.

2.4. The Default App Belongs to the User — UserChoice Protection

Writing a ProgID as the extension key’s default value does not by itself make it the default app. The result of the user explicitly choosing under “Open with” and similar is kept in HKCU\...\Explorer\FileExts\<extension>\UserChoice, and association resolution prefers that side.

And the important point is that Windows does not support programmatic change of the default app. Default-app settings are designed to be done by the user through the system Settings UI; UserChoice data is obfuscated, and a filter driver (UCPD.sys) blocks writes from apps. In a managed environment, Group Policy / MDM policy is the official means.3

That tools such as SetUserFTA, which “imitate the hash and rewrite it”, have been used is the other side of this protection. What you should put in an in-house app’s installer is not stealing the default, but the three of (a) correct registration of the ProgID and verbs, (b) adding yourself to OpenWithProgIds, and (c) if needed, steering to the Settings page.

Default-app resolution and UserChoice protectionThe result of an explicit user choice is kept in UserChoice and preferred in association resolution; UCPD.sys blocks rewrites from apps, so what an installer can do is register as a candidate and steer to the Settings pagepreferredUCPD.sys blocks itUserChoice (the user's choice)Association resolutionExtension-key default valueRewrite from an appThe installer's jobRegister the ProgID and verbsAdd to OpenWithProgIdsSteer to the Settings page

Figure 4: Association resolution prefers the user’s choice (UserChoice), and the OS protects it from rewrites by apps.

3. Verbs Other Than “Open” — print, edit, runas, Custom Verbs

A verb is not only open. Standard verbs whose meaning the OS knows include edit, print, play, and preview as well as open, and a standard verb automatically gets a display name that follows the OS locale. The default verb used on double-click is decided in the order: the shell key’s default value → the first verb in the registry → openopenwith.12

Order in which the default verb is decidedThe default verb used on double-click is the first one found in the order shell-key default value, first verb in the registry, open, openwithif noneif noneif noneshell-key default valueFirst verb in the registryopenopenwith

Figure 5: The default verb on double-click is the first one found in this order.

When you want to add your own action, 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)   ← menu display name
         command
            (Default) = "C:\Program Files\KomuraSoft\Report.exe" /verify "%1"

Three small facts that help to know.

  • Register a verb named runas and you define an elevation launch equivalent to “Run as administrator”, and it is also used when a ShellExecute-family API specifies runas.
  • Put an empty value named Extended on the verb key and it becomes an extended verb shown only when you Shift+right-click. Convenient for hiding a dangerous operation you rarely use.12
  • Some associations of older apps still have a configuration that sends a document into an existing process with DDE (the ddeexec key), but launching a verb via DDE is already a Deprecated legacy. There is no reason to write it new.12

One more accident that happens often is quoting on the command line. If an element of the command string can contain a space, you must wrap it in quotes. That applies of course to an EXE path such as C:\Program Files\..., and %1 (the path of the selected file) should always be written "%1". You cannot guarantee that a user’s file path contains no space. An unquoted My Program.exe is interpreted as “launch My with the argument Program.exe”.13

The quoting accident on the command lineAn unquoted command is split at the space and misinterpreted as launching My with the argument Program.exe, so an EXE path that can contain a space and %1, which represents the selected file path, should always be wrapped in quotessplit at the spaceUnquoted commandMisinterpreted as launching a different EXEQuoted commandLaunches as intendedWrap the EXE path in quotesAlways wrap %1 in quotes too

Figure 6: An unquoted command is wrongly split at a space, so always wrap the EXE path and %1 in quotes.

The registry-only mechanism so far (a static verb) can be realized without writing a single DLL, and it does not risk making Explorer unstable. Microsoft itself repeatedly says “before you write a shell extension, consider whether the simplest static verb that meets the requirements will do”.9

4. Classic Shell Extensions — a DLL That Runs Inside Explorer

4.1. Kinds of Shell Extension

Requirements a static verb cannot meet — “change the menu dynamically depending on the selection”, “replace the icon or the property sheet” — use a shell-extension handler. Representative kinds are as follows.4

Handler Main interface What it can do
Context-menu handler IContextMenu + IShellExtInit Dynamically add and control menu items
Icon handler / icon overlay IExtractIcon / IShellIconOverlayIdentifier Per-file icon and overlay
Property-sheet handler IShellPropSheetExt Add a tab to the property sheet
Thumbnail / infotip IThumbnailProvider / IQueryInfo Thumbnail view and hover description
Drag-and-drop / copy-hook handler IDropTarget / ICopyHook Intervene at drop or at copy/move

These are all implemented as COM classes and registered in the registry by CLSID. The idea of COM itself is covered in “What Are COM / ActiveX / 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 Explorer (or into any app that opened a common file dialog). Every caveat follows from that.4

  • If the extension crashes, Explorer is taken down with it. If it hangs, a right-click freezes for several seconds. The damage is also not limited to Explorer; it reaches every app that displayed a file-open dialog.
  • Menu construction happens on the UI thread, so you must not do slow work such as network access or file I/O at menu-display time.
  • Register the threading model as Apartment as a rule.
Collateral-damage structure of an in-process extensionA shell-extension DLL is loaded not only into Explorer but also into the process of any app that opened a file dialog, so a crash or hang in the extension spreads to the whole host processloaded in-processloaded in-processShell-extension DLLExplorerAny app that opens a dialogA crash or hang spreadsDo not do slow work at display time

Figure 7: The extension DLL runs inside the host process, so a crash or hang spreads to the host as a whole.

Investigate a consultation such as “Explorer freezes when I open a particular folder” or “a right-click takes five seconds” and it is not rare for the cause to be a third-party shell extension rather than the in-house app. Isolation methods are in Chapter 8.

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 as 32-bit is never loaded and never appears on the menu at all. There is also no error, so it is a staple cause of “I registered it but it does not appear”. Combining a 32-bit app body with a 64-bit shell-extension DLL is a legitimate configuration, but you need to watch the fact that COM registration splits by bitness (Wow6432Node). Launching from a verb’s command is a separate-process EXE, so it is not subject to this constraint (leaving it a 32-bit EXE is fine).

Matching bitness of a shell-extension DLLThe only shell-extension DLL a 64-bit Explorer can load is a 64-bit one; a 32-bit-only DLL never appears on the menu and produces no error; an EXE launched from a verb command is a separate process and is not subject to the constraintcan loadcannot loadseparate process64-bit Explorer64-bit shell-extension DLL32-bit-only DLLDoes not appear on the menu, with no errorEXE launched from a verbFine left 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

I often get the question “can I write a shell extension in C#”, but Microsoft has stated plainly that writing an in-process shell extension in managed code (.NET) is not recommended and is out of support.5

The reason is the nature of the extension being loaded into an arbitrary process. CLR version collisions (especially below .NET Framework 4), the problem of the CLR reentering the message loop while waiting on a lock, and non-deterministic object lifetime from garbage collection colliding with COM’s reference-count contract are structural reasons the host app becomes unstable. Some items have been mitigated on .NET Framework 4 and later and on modern .NET, but the official position has not changed.

The practical guideline is simple. Write an in-process extension 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 and similar).5

Judging whether managed code is allowedAn in-process extension that runs inside Explorer is written in native C++ as a rule; if you want managed code, make it a normal EXE launched from a verb command or an out-of-process extension that runs in a separate processyesnoRuns in-process?Write it in native C++Managed code is fineCLR / reentrancy riskHost becomes unstableVerb-launched EXEOut-of-process preview

Figure 9: An in-process extension is native C++ as a rule; managed code is limited to a configuration that runs in a separate process.

5. The Windows 11 New Context Menu — the Menu Split in Two

5.1. What Happened

Windows 11 refreshed File Explorer’s context menu. Cut, copy, and similar became a row of icons at the top; “Open” and “Open with” were grouped together at the top; and commands an app adds are grouped below the shell’s standard commands. When one app adds several commands, they are gathered into a flyout (submenu) named after the app.6

And the crucial point is this. Classic IContextMenu-based shell extensions were not deleted; they were moved to the old-menu side that opens with “Show more options” (Shift+F10) and loads the Windows 10 menu as-is.6 The identity of the opening consultation’s “the menu was hidden” is this split.

The context menu Windows 11 split in twoWhat opens first on a right-click is the new menu; the only commands that appear there are ones registered with IExplorerCommand and package identity; classic IContextMenu extensions are moved to the old menu that opens with Show more optionsShow more options Shift+F10Right-click a fileNew menu (Windows 11)IExplorerCommand + identity commandsOld menu (the Windows 10 menu)Classic IContextMenu extensionsSeveral commands are gathered into a flyout

Figure 10: The only commands that appear on the new menu are IExplorerCommand + identity commands; classic extensions are moved to the old-menu side.

5.2. The Official Route onto the New Menu — IExplorerCommand + Manifest Registration

There is one way to put a custom command on the new menu. Prepare a native DLL that implements the IExplorerCommand interface, and declare the COM server and the context-menu extension in an MSIX package manifest.7

<!-- 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>

ItemType’s Type can specify a particular extension, or * (all files), Directory (folders), or Directory\Background (a folder background). Match the DLL to Explorer’s architecture (64-bit / ARM64).7

IExplorerCommand itself is an interface that has existed since the Windows 7 era; you implement the title (GetTitle), the icon (GetIcon), enabled / disabled / hidden state (GetState), and execution (Invoke). The methods are called from the UI thread, so access to network resources is forbidden, and menu-construction methods need to return quickly. Do heavy work after Invoke.147

Manifest structure of a new-menu registrationThe MSIX manifest's COM-server declaration maps a CLSID to a DLL, and the context-menu-extension declaration ties the target and the implementation together with ItemType and Verb, so a custom command appears on the new menumaps CLSID to DLLspecifies with ItemType and VerbMSIX manifestCOM-server declarationMenu-extension declarationIExplorerCommand implementation DLLCommand appears on the new menuThe target is an extension, all files, and similar

Figure 11: Two declarations in the manifest tie the implementation DLL to the target, and the command appears on the new menu.

5.3. The Option for a Non-Packaged App — Getting Identity Alone with a Sparse Package

The escape hatch when “our app cannot be distributed except as MSI; MSIX is impossible” is a sparse package (MSIX with external location). You sign a small MSIX that is only a manifest, containing no app body, and register it at the end of the existing installer. The app then acquires package identity, and the manifest registration above (= appearance on the new menu) becomes possible. It is available from Windows 10 version 2004 onward, and the package needs a signature with a certificate trusted on the target machine.8

The flow of obtaining identity with a sparse packageAfter the existing installer places the app body, registering a manifest-only sparse package with an external location gives the app package identity and makes new-menu manifest registration possibleExisting installerPlace the app bodySparse packageManifest only, no bodyRegister with an external locationAcquire package identityNew-menu registration becomes possibleA trusted signature is required

Figure 12: Register a sparse package that contains no body, with an external location, and the app acquires package identity.

The greatest advantage is that you do not have to replace the installer; it is the realistic answer for an app that already has an MSI/EXE installer asset. 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

A point that is easy to misunderstand: the associations of Chapters 2 and 3 (ProgID and verb) 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 displayed at the top of the new menu. So if all you want is “to be able to open with this app”, Windows 11 needs no extra 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 layer of the new menu you need IExplorerCommand plus identity — that is the split of roles.7

The split of roles between associations and the new menuA ProgID-and-verb association is still used on the new menu to resolve the default verb, Open, and Open with, and is displayed at the top; putting an arbitrary custom command on the first layer of the new menu requires IExplorerCommand and identityAssoc.(ProgID + verb)Resolve default / OpenTop of the new menuNo extra work on Win11A custom commandIExplorerCommandplus identityNew-menu first layer

Figure 13: Associations still handle “Open”-family resolution on the new menu; only a custom command requires IExplorerCommand plus identity.

6. A Practical Decision Table — Which of the Three Options to Take

We organize what we have so far into a practical three-way choice.

What you want to achieve Recommended means How it looks on Windows 11 Work and cost required
(a) Launch the in-house app on double-click or “Open” Association + static verb (registry registration only) Integrated into “Open” and “Open with” on the new menu Installer registry registration only. No DLL, no extra signing requirement
(b) Put a custom command for the selected file/folder on the new menu IExplorerCommand implementation + MSIX manifest registration. A non-packaged app is given identity with a sparse package First layer of the new menu (several commands are gathered into an app-name flyout) 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) Old-menu side only, under “Show more options” (Shift+F10) Maintain a 64-bit build and COM registration. Plan a later move to (b)

There are two judgement points. First, do not bring in (b) or (c) for a requirement that (a) will meet. The moment you write a shell extension you take on responsibility for Explorer’s stability. Second, (c) is only “not broken”; as a user experience it stays one step worse. The more frequently a command is used in daily operation, the larger the return on a move to (b).

How to choose among the three optionsIf all you want is launch on double-click or Open, an association and a static verb are enough; to put a custom command on the new menu use IExplorerCommand and MSIX manifest registration; if you cannot become MSIX, grant identity with a sparse package; keep an existing classic IContextMenu extension on the old-menu side for nowyesnoyesyesnonoIs Open enough?Assoc. + static verbCustom on new menu?Can you become MSIX?IExplorerCommand+MSIXSparse-pkg identityKeep classic for nowOld-menu side onlyNo DLL, small risk

Figure 14: Choose among a static verb, IExplorerCommand plus identity, and keeping the classic one, according to the requirement.

7. Deployment and Registration in Practice — Installer, Sparse Package, Cleanup

7.1. HKLM or HKCU

Match it to the shape of the installer. All-user (placed under Program Files, administrator rights) is HKLM\Software\Classes; per-user install (no elevation) is HKCU\Software\Classes. Mix them and you produce the “A can open it but B cannot” kind of inquiry. For a shell extension that involves CLSID registration, Reg-Free COM — which removes the need for registry registration itself — is a valid option for in-app COM use, but it cannot be applied to a shell extension Explorer loads, so you need the straightforward registration (“What Is Reg-Free COM”).

7.2. After a Change, Notify — SHChangeNotify

After you register, change, or delete an association, notify the SHCNE_ASSOCCHANGED event with SHChangeNotify. Skip this and Explorer can fail to notice the change until a reboot.110

// Call once after changing associations, e.g. 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; remove before deleting the files.8

# 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>

A point to watch: Add-AppxPackage registers for the user who ran it. If you call it from a custom action of a per-machine MSI, running under LocalSystem does not grant identity to the user who installed, so you configure it to run under user impersonation. Even then, the registration under impersonation is only for the user who ran that install. On a PC used by several users, other users and users created later have no package identity, and the command does not appear on the new menu. To have every user use it, provide a mechanism such as checking your own package registration at first launch and registering if it is missing (per-user registration), and include removal from each user who has a registration in the uninstall plan. Reflecting a manifest registration can also require an Explorer restart (or a sign-out).7

Order of registering and removing a sparse packageAt install time register the sparse package after placing the files; at uninstall time remove the registration before deleting the files; watch the fact that registration is effective only for the user who ran itInstallPlace the filesRegister the sparse packageUninstallRemove the package registrationDelete the filesRegistration is effective only for the running user

Figure 15: Register after placing the files, remove before deleting the files, and watch the fact that registration is per running user.

7.4. Cleanup at Uninstall — What to Delete and What to Leave

Cleanup at uninstall has a clear official-guide line.1

  • Delete: The whole in-house ProgID key, Capabilities/RegisteredApplications registration, the shell extension’s CLSID registration, the sparse package (Remove-AppxPackage).
  • Leave: The extension key (.kmrpt) default value. The official recommendation is not to delete it even if it still points at the in-house ProgID. Judging after install whether another app has taken the default is difficult, and Windows simply ignores a default-value ProgID that is unregistered, so leaving it does no real harm.
  • Call SHChangeNotify(SHCNE_ASSOCCHANGED) at the end of the cleanup as well.

Most “we uninstalled and leftovers still appear on the menu” problems are a leak in this cleanup design.

Cleanup design at uninstallAt uninstall you delete the in-house ProgID key, CLSID registration, and sparse package; leave the extension-key default value because an unregistered ProgID is ignored; notify the change with SHChangeNotify at the end of the cleanupUninstallDeleteLeaveProgID and CLSID registrationSparse packageExtension-key default valueAn unregistered ProgID is ignoredNotify with SHChangeNotify at the end

Figure 16: Delete the in-house registration, leave the extension-key default value, and notify the change at the end of the cleanup.

8. Troubleshooting — Missing, Duplicate, Heavy

8.1. It Does Not Appear on the Menu

Isolate in this order.

  1. Which menu you are looking at: A classic-style registration only appears on the old-menu side under Shift+F10. Check both first.
  2. Bitness: A 32-bit-only shell-extension DLL is not loaded into 64-bit Explorer (Section 4.3).
  3. Registration destination: HKLM/HKCU, a Wow6432Node mix-up. Confirm the actual key with reg query.
  4. Package registration: For the new menu, confirm presence with Get-AppxPackage, trust of the signing certificate, and the -ExternalLocation path, then restart Explorer.7
  5. Missed notification: If SHChangeNotify was forgotten, you can tell by whether an Explorer restart makes it take effect.
Isolation order when it does not appear on the menuStart by confirming which menu you are looking at, then isolate DLL bitness, registry registration destination, package registration and signing, and a missed SHChangeNotify, in that orderConfirm which menu, old or newConfirm DLL bitnessConfirm HKLM and HKCU registration destinationConfirm package registration and signingTell a missed notification by restarting

Figure 17: When it “does not appear”, isolate in the order menu you are looking at, bitness, registration destination, package registration, missed notification.

8.2. It Appears Twice, or Will Not Go Away

Typical causes are coexistence of a classic registry registration and a manifest registration, a leak in uninstall cleanup (Section 7.4), or leftovers of an old-version ProgID. If it appears twice only on the old menu, think leftovers; if it appears on both old and new, think coexistence.

Isolating a double displayTwice only on the old menu points at leftovers such as a cleanup leak or an old ProgID; twice on both old and new points at coexistence of a classic registry registration and a manifest registrationold menu onlyboth old and newOn which does it appear twice?LeftoversCoexistenceCleanup leak or an old ProgID left behindClassic registry registration coexisting with the new registration

Figure 18: Twice only on the old menu points at leftovers; twice on both old and new points at coexistence.

8.3. Explorer Is Heavy or Crashes

When a right-click is slow, or a particular folder crashes, first inventory the installed shell extensions. List non-Microsoft extensions with a tool such as NirSoft’s ShellExView, temporarily disable the suspicious ones, and binary-search to identify the culprit DLL. On a crash, Event Viewer’s “Faulting module” is also a clue. If the in-house extension was the cause, suspect synchronous I/O or network access on the menu-construction path (Sections 4.2 and 5.2).

Identifying the culprit DLL when it is heavy or crashesList non-Microsoft shell extensions in ShellExView, temporarily disable the suspicious ones and binary-search to identify the culprit DLL; on a crash, Event Viewer's faulting module is also a clueInventory shell extensionsList the non-Microsoft onesTemporarily disable and binary-searchIdentify the culprit DLLOn a crashCheck the faulting module

Figure 19: Temporarily disable non-Microsoft extensions and binary-search; on a crash, also use Event Viewer.

8.4. Windows Sandbox Is Convenient for Verification

Verification of shell integration is based on confirming “install on a clean environment → operate → uninstall → leftovers zero”. Convenient here is Windows Sandbox (Pro/Enterprise/Education): every launch brings up a brand-new disposable Windows in a few seconds, so you can run installer registration and cleanup tests as many times as you like. Close it and everything disappears, so it also suits leftover-registry investigation.15

9. Summary

  • File association is the three-layer structure “extension key → ProgID → verb”, and HKCR is a merged view of HKLM/HKCU Classes. Name the write destination explicitly, and always wrap %1 in quotes.
  • The default app is designed to be chosen by the user and cannot be changed from a program. The installer’s job is to register correctly as a candidate.
  • A classic shell extension is an in-process COM DLL loaded into Explorer. A crash or delay spreads to the whole; 64-bit is required; managed code is unsupported; implementation in native C++ is the rule.
  • On Windows 11 the context menu split in two. Putting a custom command on the new menu requires IExplorerCommand plus an MSIX manifest; classic IContextMenu is moved to the “Show more options” side.
  • For an app that cannot become MSIX, obtaining identity with a sparse package (MSIX with external location) is the realistic answer.
  • If all you want is “open with this app”, an association and a static verb are still enough. Starting from the simplest means is the official guideline as well.
  • After registration, a change, or a deletion, notify with SHChangeNotify; at uninstall, delete the ProgID but leave the extension-key default value. Windows Sandbox is convenient for verification.

If a Windows 11 PC replacement made you notice that “the menu was hidden”, first confirm which of (a), (b), and (c) in Chapter 6’s decision table it is. You should be able to estimate the scale of the work on the spot.

KomuraSoft LLC handles the design and implementation of file association, context menus, and shell extensions for business apps; targeting the Windows 11 new context menu (moving to IExplorerCommand, introducing a sparse package); reviewing registration and cleanup of an existing installer; and investigating the cause of Explorer being heavy or crashing. It is fine to start from deciding what to do about “the menu that was hidden under Show more options”.

References

  1. Microsoft Learn, File Types. On the structure of an extension key pointing at a ProgID; OpenWithProgIds; the split of registration between HKLM/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

  2. Microsoft Learn, HKEY_CLASSES_ROOT Key. On HKEY_CLASSES_ROOT being a merged view of HKLM\Software\Classes and HKCU\Software\Classes; user-side definitions taking priority over machine-side ones; and the dispatch rules when writing.  2

  3. Microsoft Learn, Windows app defaults platform. On changing the default app being designed to be done only through the system Settings UI; user-settings data being obfuscated and write-protected by a filter driver (UCPD.sys); registry-based change being unsupported; and using Group Policy / MDM policy in a managed environment.  2

  4. 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 Explorer as a whole; registration with ThreadingModel=Apartment; and considering a simpler alternative before a shell extension.  2 3

  5. Microsoft Learn, Guidance for Implementing In-Process Extensions. On Microsoft not recommending and not supporting an in-process shell-extension implementation in managed code; reasons including CLR version collisions, reentrancy, and non-deterministic object lifetime; and managed code being acceptable for an out-of-process extension (a preview handler, or a launch from shell\verb\command).  2 3

  6. Windows Developer Blog, Extending the Context Menu and Share Dialog in Windows 11. On the design of the Windows 11 new context menu; extension via IExplorerCommand plus app identity; placing “Open” and “Open with” at the top; gathering several commands into an app-name flyout; and classic IContextMenu extensions being loaded as the Windows 10 menu under “Show more options” (Shift+F10).  2 3

  7. 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 plus a desktop4:FileExplorerContextMenus manifest declaration; ItemType being able to specify *, Directory, or Directory\Background; matching DLL architecture; keeping menu-construction methods fast; covering a non-packaged app with a sparse package; an Explorer restart sometimes being required for the registration to take effect; and a file association not being a general-purpose menu extension.  2 3 4 5 6 7 8

  8. Microsoft Learn, Grant package identity by packaging with external location. On obtaining package identity by registering an external-location package (sparse package) without changing the existing installer; availability from Windows 10 version 2004 onward; and Windows features that require identity (context-menu registration, notifications, and similar) becoming usable.  2 3

  9. 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 toward the not-recommended side; and IExplorerCommand/IExplorerCommandState being the recommended method.  2

  10. Microsoft Learn, SHChangeNotify function. On how to raise the SHCNE_ASSOCCHANGED event that notifies the system of a file-association change, and on using it so the shell notices the change.  2

  11. Microsoft Learn, Application Registration. On registration of an executable via the App Paths subkey being recommended; the role of the Applications subkey; verb registration via SystemFileAssociations; and the priority of the ProgID and related information when the default app changes. 

  12. Microsoft Learn, Creating Shortcut Menu Handlers. On how to register a static verb; the order in which the default verb is decided (default value → first verb → Open → Open With); display names of standard verbs being supplied by the OS; extended verbs via Extended; association with DDE commands being Deprecated; and WOW64-redirection caveats in a 64-bit environment.  2 3

  13. Microsoft Learn, Verbs and File Associations. On a verb being an action also used by ShellExecuteEx; elements of a command string that can contain a space needing to be wrapped in quotes, and “%1” always being written quoted; and registering a default procedure under HKCR\Applications. 

  14. Microsoft Learn, IExplorerCommand interface. On the method makeup of GetTitle, GetIcon, GetState, Invoke, EnumSubCommands, and similar; methods being called on the UI thread so they must not communicate with network resources; and availability from Windows Vista onward. 

  15. Microsoft Learn, Windows Sandbox. On being able to launch a disposable, isolated Windows environment in a few seconds, all changes being discarded when you close it, it being suitable for software testing and installer verification, and availability on Pro/Enterprise/Education. 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Why does our app's context-menu item on Windows 11 only appear under "Show more options"?
Because on Windows 11 File Explorer's context menu split into two layers, old and new. 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 (= they have package identity). Classic IContextMenu-based shell extensions were moved to the old menu that you open with "Show more options" (Shift+F10). The extension itself is not broken, so it continues to work for now, but if you want it on the new menu you need a migration to IExplorerCommand and either packaging as MSIX or granting identity with a sparse package.
Can the installer set our app as the default app for a file (the app that opens on double-click)?
No. Choosing the default app is designed to be something the user does, 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 write-protects it 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 steal the default, but to get ready to be chosen.
May I write a shell extension in managed code such as C#?
Microsoft has stated plainly that writing an in-process shell extension (a context-menu handler, an icon handler, and similar) in managed code is not recommended and is unsupported. The extension is loaded into Explorer and into the process of any app that opens a common file dialog, so CLR version collisions, reentrancy, and non-deterministic object lifetime make the host app unstable. Implementation in native C++ is the rule. 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 the ordinary way with an existing installer (MSI, Inno Setup, and similar), you register it with Add-AppxPackage -ExternalLocation pointing at the install folder, and the app 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 from Windows 10 version 2004 onward, 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). The typical double display is that a classic registry registration and an MSIX manifest registration coexist, or that a ProgID or extension CLSID registration was left behind at uninstall. After changing associations, also suspect a missed SHChangeNotify(SHCNE_ASSOCCHANGED); right after package registration, suspect a missed Explorer restart. If that still does not resolve it, temporarily disable non-Microsoft extensions in ShellExView and binary-search to identify the culprit DLL.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog