ShellEx.info

How to Build a Windows Shell Extension in C# and .NET 8

Updated April 2026 — Advanced Developer Tutorial

Historically, extending the Windows File Explorer was a nightmare reserved strictly for hardcore C++ developers. To add a simple “Count Lines of Code” button to the Windows right-click menu, you had to dive deep into unmanaged code, memory management, and write thousands of lines of COM (Component Object Model) boilerplate.

Attempting to write a Shell Extension in early versions of .NET (like .NET Framework 2.0) was heavily discouraged by Microsoft. Because explorer.exe could only load a single version of the CoreCLR at an time, if two different shell extensions tried to load different versions of the .NET runtime into the same explorer.exe process, the entire desktop would crash.

However, the world changed with the release of .NET Core 3.1, and perfected in .NET 8. With the introduction of Native AOT (Ahead-of-Time) compilation and self-contained COM server hosting, writing stable, blazing-fast shell extensions in C# is not just possible—it is arguably the best way to do it.

In this comprehensive tutorial, we will walk you through building, debugging, and deploying a classic Context Menu Handler using C# and .NET 8.


1. The Architecture of a Modern C# Shell Extension

Before writing code, we need to understand how Windows talks to our C# .dll.

When a user right-clicks a file, explorer.exe queries the Windows Registry (HKEY_CLASSES_ROOT) to see what extensions are registered for that specific file type. If it finds our extension’s Class ID (CLSID), Windows attempts to load our .dll directly into the explorer.exe memory space via COM.

To make our C# code accessible via COM in .NET 8, we expose specific classes using the [ComVisible(true)] attribute and implement native Windows interfaces like IShellExtInit and IContextMenu.

The Tooling: Why We Use SharpShell

While you can manually write the P/Invoke signatures for COM interfaces, it is incredibly tedious. For over a decade, the open-source library SharpShell (originally created by Dave Kerr) has been the gold standard for bridging the gap between C# and the Windows Shell.

SharpShell handles the COM registration, the registry manipulation, and provides a beautiful, object-oriented API for interacting with the shell. We will be using the updated, .NET 8-compatible fork of SharpShell for this tutorial.


2. Project Setup

Open your terminal or command prompt and let’s structure the project.

Create the Class Library

dotnet new classlib -n MyFirstShellExtension -f net8.0-windows
cd MyFirstShellExtension

Install the Required NuGet Packages

We need the updated SharpShell library that supports modern .NET runtimes.

dotnet add package SharpShell --version 3.0.0

Configure the CSProj for COM Hosting

To allow Windows to load our .NET .dll as if it were a native C++ COM server, we must enable EnableComHosting. Open your MyFirstShellExtension.csproj file and add the following lines inside the <PropertyGroup>:

<PropertyGroup>
  <TargetFramework>net8.0-windows</TargetFramework>
  <ImplicitUsings>enable</ImplicitUsings>
  <Nullable>enable</Nullable>
  <Platforms>x64</Platforms>
  <!-- CRITICAL FOR SHELL EXTENSIONS -->
  <EnableComHosting>true</EnableComHosting>
  <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

When you build the project with EnableComHosting="true", the .NET SDK will automatically generate a file named MyFirstShellExtension.comhost.dll. This is the actual file Windows will load, which then acts as a proxy to boot up the .NET 8 runtime and execute your C# code.


3. Writing the Code

Delete the default Class1.cs and create a new file called CountLinesExtension.cs.

Our goal is to create a shell extension that only appears when the user right-clicks a .txt or .cs file. When clicked, it will read the file and throw up a MessageBox telling the user how many lines of text are inside.

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using SharpShell.Attributes;
using SharpShell.SharpContextMenu;

namespace MyFirstShellExtension
{
    // 1. Generate a unique GUID for your extension
    [ComVisible(true)]
    [COMServerAssociation(AssociationType.Class, ".txt", ".cs")]
    [Guid("D10F7BA2-A746-4B1D-89F4-38B6B202CE39")]
    public class CountLinesExtension : SharpContextMenu
    {
        // 2. Define what the menu looks like
        protected override bool CanShowMenu()
        {
            // We only want to show the menu if exactly one file is selected
            return SelectedItemPaths.Count() == 1;
        }

        // 3. Create the actual menu item
        protected override ContextMenuStrip CreateMenu()
        {
            var menu = new ContextMenuStrip();

            var itemCountLines = new ToolStripMenuItem
            {
                Text = "Count Lines of Code",
                // You can add an icon here if necessary
                // Image = Properties.Resources.MyIcon
            };

            // 4. Attach the click event
            itemCountLines.Click += (sender, args) => CountLines();

            menu.Items.Add(itemCountLines);
            return menu;
        }

        // 5. The business logic
        private void CountLines()
        {
            try
            {
                var filePath = SelectedItemPaths.First();
                var lineCount = File.ReadLines(filePath).Count();
                
                MessageBox.Show($"File: {Path.GetFileName(filePath)}\nTotal Lines: {lineCount}", 
                                "Line Counter", 
                                MessageBoxButtons.OK, 
                                MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error reading file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

Explaining the Magic


4. Building and Registering the Extension

Now it is time to build and deploy our COM server.

  1. Open your terminal and build the project in Release mode targeting x64 architecture (since explorer.exe is a 64-bit process on modern Windows). dotnet build -c Release -r win-x64
  2. Navigate to your output directory: bin\Release\net8.0-windows\win-x64.
  3. You will see several files, but the two that matter are MyFirstShellExtension.dll (your C# code) and MyFirstShellExtension.comhost.dll (the native COM proxy).

Registering with regsvr32

To tell Windows about your extension, you must register the native .comhost.dll using the built-in Windows registration server.

Open an Administrator PowerShell prompt, navigate to your output folder, and run:

regsvr32 MyFirstShellExtension.comhost.dll

If successful, you will see a popup saying “DllRegisterServer in MyFirstShellExtension.comhost.dll succeeded.”

Registry Note: Behind the scenes, regsvr32 reads the COM host, looks at your C# assembly, and uses SharpShell’s internal routines to write the specific CLSID keys into HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers.

Restarting Explorer

Because explorer.exe caches shell extensions aggressively, you must restart it to see your changes.

  1. Open Task Manager.
  2. Find “Windows Explorer”, right-click it, and select Restart.
  3. Now, right-click on any .txt file on your desktop. You will see your “Count Lines of Code” button!

5. Debugging Shell Extensions

Debugging a .dll that is actively being loaded by explorer.exe can be frustrating. If you simply run dotnet run, nothing happens, because it is a class library, not an executable.

Attaching the Visual Studio Debugger

  1. Open your project in Visual Studio 2022.
  2. Go to Debug > Attach to Process...
  3. Find explorer.exe in the list and click Attach.
  4. Set a breakpoint inside your CreateMenu() or CanShowMenu() methods.
  5. Right-click a .txt file on your desktop. Visual Studio will immediately catch the breakpoint, allowing you to step through your C# code as if it were a normal desktop app!

The Locked File Problem

The moment explorer.exe loads your .dll to show the context menu, the file is locked by the operating system. You cannot recompile or overwrite your .dll while explorer.exe is holding it hostage.

To release the lock:

  1. Open Task Manager and killing Windows Explorer.
  2. Use a batch script to restart Explorer automatically during your build process.
  3. Or, use the SharpShell Server Manager tool, which can run your extension in a sandbox isolated from explorer.exe.

6. Windows 11 Considerations

If you right-click a .txt file in Windows 11, you might be disappointed. Your beautiful new extension is nowhere to be found.

Instead, you have to click “Show more options” (Shift+F10) to reveal the legacy Windows 10 context menu. Only then will your extension appear.

Why? Because Microsoft fundamentally changed the architecture of the context menu in Windows 11. Legacy COM-based shell extensions (which we just built) are strictly relegated to the “Show more options” overflow menu.

To get your application directly onto the modern, rounded Windows 11 context menu, you cannot simply write a .dll and register it with regsvr32. You must:

  1. Package your entire application as an MSIX package (UWP-style identity).
  2. Implement the new IExplorerCommand interface instead of the classic IContextMenu.
  3. Use sparse packages or the Windows App SDK.

While SharpShell handles the legacy COM architecture brilliantly, it is highly recommended to study the IExplorerCommand documentation if your primary target is top-level positioning on Windows 11.

Summary

Building Shell Extensions in C# used to be a fragile hack. But with the power of .NET 8’s COM Hosting and the elegance of the SharpShell library, it is now a clean, stable, and highly productive experience. By carefully managing your COM registration and testing via Visual Studio’s “Attach to Process” feature, you can deeply integrate your C# tools directly into the Windows File Explorer workflow.

Is Your Extension Crashing Explorer?

Learn how to read Event Viewer logs, generate crash dumps, and debug unstable shell extension code.

Read the Debugging Guide