ShellEx.info

Migrating Shell Extensions from Windows 10 to Windows 11: The Complete Developer Guide

Updated February 2026 — Covers Windows 11 24H2, MSIX App Attach, Win32 App Isolation

If you maintain a Windows desktop application that includes Shell Extensions — context menu handlers, icon overlays, property sheet extensions, or preview handlers — you have likely encountered issues after users upgraded to Windows 11. Extensions that worked flawlessly for years on Windows 10 may now cause crashes, fail to appear in the new context menu, or trigger security warnings.

Windows 11 fundamentally changed how the context menu works, introduced new security boundaries, and is pushing developers toward modern packaging with MSIX. This guide covers everything you need to know to migrate your shell extensions and ensure they work reliably on Windows 11, while maintaining backward compatibility with Windows 10.


What Changed in Windows 11

1. The Two-Tier Context Menu

The most visible change is the new context menu. In Windows 10, right-clicking a file showed a single menu containing all entries from registered IContextMenu handlers. In Windows 11, the menu is split:

Key implication: If your extension only implements IContextMenu, your menu items are hidden behind “Show more options” by default. Users will not see them unless they click through.

2. IExplorerCommand Interface

The modern Windows 11 context menu uses IExplorerCommand, IExplorerCommandState, and IEnumExplorerCommand. These interfaces provide:

3. MSIX Packaging Integration

Windows 11 strongly encourages (and in some cases requires) shell extension registration through MSIX manifests via the desktop4:FileExplorerContextMenus and desktop10:ApprovedShellExtension elements.

4. Win32 App Isolation (Preview)

Starting in Windows 11 24H2, Microsoft introduced Win32 App Isolation, which runs Win32 applications in a sandboxed environment similar to UWP. Shell extensions loading into explorer.exe under this model face additional restrictions.


Strategy Decision: Migrate, Dual-Target, or Stay Legacy?

Before coding, decide your approach:

StrategyWhen to UseEffortResult
Full Migration to IExplorerCommandNew apps, small menuMediumItems in primary menu, modern feel
Dual Registration (both interfaces)Existing apps needing Win10+11HighWorks everywhere, future-proof
Stay Legacy (IContextMenu only)Legacy apps, Win10 focusNoneItems hidden behind “Show more options”
Sparse Package + IExplorerCommandApps without full MSIXMediumPrimary menu without full MSIX

Option 1: Implementing IExplorerCommand

Interface Overview

class ATL_NO_VTABLE CMyExplorerCommand :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CMyExplorerCommand, &CLSID_MyExplorerCommand>,
    public IExplorerCommand,
    public IObjectWithSite
{
public:
    // IExplorerCommand
    STDMETHOD(GetTitle)(IShellItemArray* psiItemArray, LPWSTR* ppszName);
    STDMETHOD(GetIcon)(IShellItemArray* psiItemArray, LPWSTR* ppszIcon);
    STDMETHOD(GetToolTip)(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip);
    STDMETHOD(GetCanonicalName)(GUID* pguidCommandName);
    STDMETHOD(GetState)(IShellItemArray* psiItemArray, BOOL fOkToBeSlow,
                        EXPCMDSTATE* pCmdState);
    STDMETHOD(Invoke)(IShellItemArray* psiItemArray, IBindCtx* pbc);
    STDMETHOD(GetFlags)(EXPCMDFLAGS* pFlags);
    STDMETHOD(EnumSubCommands)(IEnumExplorerCommand** ppEnum);
};

Implementation Example

STDMETHODIMP CMyExplorerCommand::GetTitle(
    IShellItemArray* psiItemArray, LPWSTR* ppszName
) {
    // Return the menu item text
    return SHStrDupW(L"Open with My App", ppszName);
}

STDMETHODIMP CMyExplorerCommand::GetIcon(
    IShellItemArray* psiItemArray, LPWSTR* ppszIcon
) {
    // Return path to icon resource
    // Supports .ico, .png, and resource references
    wchar_t modulePath[MAX_PATH];
    GetModuleFileName(_AtlBaseModule.GetModuleInstance(),
                      modulePath, MAX_PATH);

    // Reference icon by resource ID
    wchar_t iconPath[MAX_PATH + 10];
    swprintf_s(iconPath, L"%s,-%d", modulePath, IDI_MYICON);
    return SHStrDupW(iconPath, ppszIcon);
}

STDMETHODIMP CMyExplorerCommand::GetState(
    IShellItemArray* psiItemArray, BOOL fOkToBeSlow,
    EXPCMDSTATE* pCmdState
) {
    *pCmdState = ECS_ENABLED;  // Always enabled

    // Optionally check file types
    if (psiItemArray) {
        DWORD count = 0;
        psiItemArray->GetCount(&count);
        if (count == 0) {
            *pCmdState = ECS_HIDDEN;
        }
    }

    return S_OK;
}

STDMETHODIMP CMyExplorerCommand::Invoke(
    IShellItemArray* psiItemArray, IBindCtx* pbc
) {
    if (!psiItemArray) return E_INVALIDARG;

    DWORD count = 0;
    HRESULT hr = psiItemArray->GetCount(&count);
    if (FAILED(hr)) return hr;

    for (DWORD i = 0; i < count; i++) {
        ComPtr<IShellItem> pItem;
        hr = psiItemArray->GetItemAt(i, &pItem);
        if (FAILED(hr)) continue;

        LPWSTR filePath = nullptr;
        hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath);
        if (SUCCEEDED(hr) && filePath) {
            // Process the file
            ProcessFile(filePath);
            CoTaskMemFree(filePath);
        }
    }
    return S_OK;
}

STDMETHODIMP CMyExplorerCommand::GetFlags(EXPCMDFLAGS* pFlags) {
    *pFlags = ECF_DEFAULT;
    return S_OK;
}

STDMETHODIMP CMyExplorerCommand::EnumSubCommands(
    IEnumExplorerCommand** ppEnum
) {
    *ppEnum = nullptr;
    return E_NOTIMPL;  // No sub-commands
}

Registration for IExplorerCommand

IExplorerCommand-based extensions must be registered via package manifest (MSIX) or Sparse Package. They cannot be registered via traditional registry entries alone.


The best approach for existing applications is to implement both IContextMenu (legacy) and IExplorerCommand (modern), allowing the extension to work on both Windows 10 and Windows 11.

Architecture

MyExtension.dll
├── CMyContextMenu          // IContextMenu + IShellExtInit
│   └── Registered via HKCR\*\shellex\ContextMenuHandlers
│       (Works on Win10 + Win11 "Show more options")

└── CMyExplorerCommand      // IExplorerCommand
    └── Registered via MSIX manifest
        (Appears in Win11 primary menu)

Class Factory

Both COM classes live in the same DLL and are registered via the same DllGetClassObject:

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) {
    if (rclsid == CLSID_MyContextMenu) {
        // Legacy handler for Win10 compatibility
        return CMyContextMenuFactory::CreateInstance(riid, ppv);
    }
    if (rclsid == CLSID_MyExplorerCommand) {
        // Modern handler for Win11 primary menu
        return CMyExplorerCommandFactory::CreateInstance(riid, ppv);
    }
    return CLASS_E_CLASSNOTAVAILABLE;
}

Avoiding Duplicate Entries

On Windows 11, if both handlers are active, the user may see the menu item twice (once in the primary menu, once in “Show more options”). To prevent this:

  1. In CMyContextMenu::QueryContextMenu(), check the Windows version:
STDMETHODIMP CMyContextMenu::QueryContextMenu(
    HMENU hmenu, UINT indexMenu, UINT idCmdFirst,
    UINT idCmdLast, UINT uFlags
) {
    // Skip legacy registration on Win11 if the IExplorerCommand
    // version is registered
    if (IsWindows11OrGreater() && IsExplorerCommandRegistered()) {
        return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0);  // Add 0 items
    }

    // Normal Win10 behavior
    InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,
               idCmdFirst, L"Open with My App");
    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 1);
}

Option 3: Sparse Packages (Modern Registration Without Full MSIX)

If converting your entire application to MSIX is not feasible, you can use a Sparse Package to register just the IExplorerCommand handler:

Creating a Sparse Package Manifest

Create AppxManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
         xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
         xmlns:desktop10="http://schemas.microsoft.com/appx/manifest/desktop/windows10/10"
         IgnorableNamespaces="desktop4 desktop10">

  <Identity Name="MyApp.ShellExtension"
            Publisher="CN=My Company"
            Version="1.0.0.0" />

  <Properties>
    <DisplayName>My App Shell Extension</DisplayName>
    <PublisherDisplayName>My Company</PublisherDisplayName>
  </Properties>

  <Applications>
    <Application Id="App">
      <Extensions>
        <desktop4:Extension Category="windows.fileExplorerContextMenus">
          <desktop4:FileExplorerContextMenus>
            <desktop4:ItemType Type="*">
              <desktop4:Verb Id="MyAppOpen"
                             Clsid="{YOUR-EXPLORER-COMMAND-CLSID}" />
            </desktop4:ItemType>
          </desktop4:FileExplorerContextMenus>
        </desktop4:Extension>

        <com:Extension Category="windows.comServer">
          <com:ComServer>
            <com:SurrogateServer DisplayName="My App Explorer Command">
              <com:Class Id="{YOUR-EXPLORER-COMMAND-CLSID}"
                         Path="MyExtension.dll"
                         ThreadingModel="STA" />
            </com:SurrogateServer>
          </com:ComServer>
        </com:Extension>
      </Extensions>
    </Application>
  </Applications>
</Package>

Registering the Sparse Package

# Register the sparse package from your installer
Add-AppxPackage -Path "AppxManifest.xml" -ExternalLocation "C:\Program Files\MyApp" -Register

Unregistering

Remove-AppxPackage "MyApp.ShellExtension_1.0.0.0_x64__publisher-id"

Handling Windows 11 24H2 Breaking Changes

Windows 11 24H2 introduced several changes that affect shell extensions:

1. Enhanced DLL Loading Security

24H2 enforces stricter DLL loading policies:

2. Explorer Process Changes

24H2 may run Explorer tabs in separate processes:

3. Updated Context Menu Rendering

The context menu rendering pipeline was updated, which may cause:


Testing Your Migration

Test Matrix

Test CaseWindows 10 22H2Windows 11 23H2Windows 11 24H2
Legacy menu items visiblePrimary menu”Show more options""Show more options”
IExplorerCommand items visibleN/A (not supported)Primary menuPrimary menu
Icon overlays✓ (check 15-limit)✓ (check 15-limit)
Preview handlers
Copy hook handlers
DLL loads without errorsCheck signing

Automated Testing

Create a PowerShell test script that verifies registration:

# Verify legacy IContextMenu registration
$legacyPath = "Registry::HKCR\*\shellex\ContextMenuHandlers\MyApp"
if (Test-Path $legacyPath) {
    Write-Host "✓ Legacy handler registered" -ForegroundColor Green
} else {
    Write-Host "✗ Legacy handler missing" -ForegroundColor Red
}

# Verify IExplorerCommand registration (via sparse package)
$package = Get-AppxPackage | Where-Object { $_.Name -match "MyApp.ShellExtension" }
if ($package) {
    Write-Host "✓ Sparse package registered: $($package.Version)" -ForegroundColor Green
} else {
    Write-Host "✗ Sparse package not found" -ForegroundColor Yellow
}

# Verify COM class registration
$clsid = "{YOUR-EXPLORER-COMMAND-CLSID}"
$comPath = "Registry::HKCR\CLSID\$clsid\InprocServer32"
if (Test-Path $comPath) {
    $dllPath = (Get-ItemProperty $comPath)."(default)"
    Write-Host "✓ COM class registered: $dllPath" -ForegroundColor Green

    # Check DLL signature
    $sig = Get-AuthenticodeSignature $dllPath
    if ($sig.Status -eq "Valid") {
        Write-Host "  ✓ DLL is signed by: $($sig.SignerCertificate.Subject)" -ForegroundColor Green
    } else {
        Write-Host "  ✗ DLL signature: $($sig.Status)" -ForegroundColor Red
    }
} else {
    Write-Host "✗ COM class not registered" -ForegroundColor Red
}

Debugging Tips

  1. Visual Studio: Attach to explorer.exe for legacy handlers, prevhost.exe for preview handlers.
  2. WinDbg: Use !comcalls to trace COM interface calls in explorer.
  3. Process Monitor: Watch for RegQueryValue failures that indicate missing registrations.
  4. DebugView: Add OutputDebugString calls to trace your extension’s execution path.

Migration Checklist

Use this checklist when migrating your shell extension:


Summary

Migrating shell extensions from Windows 10 to Windows 11 requires understanding the new two-tier context menu, IExplorerCommand interface, MSIX/Sparse Package registration, and 24H2 security changes. For most applications, dual registration (both IContextMenu and IExplorerCommand) provides the best experience across all Windows versions while future-proofing for upcoming changes.

Building from scratch? Read our C++ Shell Extension Development Guide for a foundational overview of COM, ATL, and registration basics.