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
[ComVisible(true)]and[Guid(...)]: These expose your class to the Windows Registry. You must generate your own unique GUID using a tool likeguidgen.exeorGuid.NewGuid(). Do not reuse the one in this tutorial.[COMServerAssociation]: This brilliant SharpShell attribute tells the framework exactly what file types your extension applies to. You can target specific extensions (.txt), entire directories (AssociationType.Directory), or all files universally (AssociationType.AllFiles).
4. Building and Registering the Extension
Now it is time to build and deploy our COM server.
- Open your terminal and build the project in Release mode targeting
x64architecture (sinceexplorer.exeis a 64-bit process on modern Windows).dotnet build -c Release -r win-x64 - Navigate to your output directory:
bin\Release\net8.0-windows\win-x64. - You will see several files, but the two that matter are
MyFirstShellExtension.dll(your C# code) andMyFirstShellExtension.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,
regsvr32reads the COM host, looks at your C# assembly, and uses SharpShell’s internal routines to write the specific CLSID keys intoHKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers.
Restarting Explorer
Because explorer.exe caches shell extensions aggressively, you must restart it to see your changes.
- Open Task Manager.
- Find “Windows Explorer”, right-click it, and select Restart.
- Now, right-click on any
.txtfile 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
- Open your project in Visual Studio 2022.
- Go to
Debug > Attach to Process... - Find
explorer.exein the list and click Attach. - Set a breakpoint inside your
CreateMenu()orCanShowMenu()methods. - Right-click a
.txtfile 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:
- Open Task Manager and killing Windows Explorer.
- Use a batch script to restart Explorer automatically during your build process.
- 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:
- Package your entire application as an MSIX package (UWP-style identity).
- Implement the new
IExplorerCommandinterface instead of the classicIContextMenu. - 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