Creating Integrated Windows Explorer Context Menu Shell Extension

This article describes how to create a Windows Explorer context menu integrated in your main application. Your context menu handler will run as a COM inside main app, which also hosts your Engine instance. 

The functionality described in this article is available in IT Hit User File System v5 Beta and later versions.

Important! On Windows 11 context menu shell extension requires package or application identity. Otherwise your commands will appear under the "Show more options" menu in Windows Explorer.

Creating Custom Context Menu Shell Extension for Virtual Drive

Follow the steps below to add custom context menu command shell extension in Windows Explorer. Note that first two steps are identical for Integrated an RPC versions of the menu.

1. Implement Context Menu Interface

Implement the IMenuCommandWindows interface and return menu title, tooltip, icon and state as well as implement the IMenuCommandWindows.InvokeAsync() method which is called when the menu is executed. All methods receive a list of selected items as an input parameter:

public class MyMenuCommand : IMenuCommandWindows
{
    private readonly EngineWindows engine;
    private readonly ILogger logger;

    public MyMenuCommand(EngineWindows engine, ILogger logger)
    {
        this.engine = engine;
        this.logger = logger.CreateLogger("My Menu Command");
    }

    public async Task<string> GetTitleAsync(IEnumerable<string> filesPath)
    {
        return "My Menu Command";
    }

    public async Task<string> GetIconAsync(IEnumerable<string> filesPath)
    {
        string appPath = typeof(MyMenuCommand).Assembly.Location;
        return Path.Combine(Path.GetDirectoryName(appPath), "Icon.ico");
    }

    public async Task<MenuState> GetStateAsync(IEnumerable<string> filesPath)
    {
        return MenuState.Enabled;
    }

    public async Task InvokeAsync(IEnumerable<string> filesPath)
    {
        foreach (string userFileSystemPath in filesPath)
        {
            string remoteStorageIdStr = null;

            // Often you need a remote storage ID in your menu handler.
            if (engine.Placeholders.TryGetItem(
                userFileSystemPath, 
                out PlaceholderItem placeholder))
            {
                byte[] remoteStorageId = placeholder.GetRemoteStorageItemId();
                remoteStorageIdStr = BitConverter.ToString(remoteStorageId);
            }

            logger.LogMessage(remoteStorageIdStr, userFileSystemPath);
        }
    }

    public async Task<string> GetToolTipAsync(IEnumerable<string> filesPath)
    {
        return "My command tooltip";
    }
}

 

2. Return Context Menu Implementation from the Engine

Implement the IEngine.GetMenuCommandAsync() method and return your menu implementation. The Engine will call this method passing the menu GUID parameter, so you can implement multiple menu commands on the first level of the menu:

public class VirtualEngine : EngineWindows
{
    ...
    public override async Task<IMenuCommand> GetMenuCommandAsync(
        Guid menuGuid,
        IOperationContext operationContext = null)
    {
        if (menuGuid == typeof(ContextMenuVerbIntegrated).GUID)
        {
            return new MyMenuCommand(this, this.Logger);
        }

        throw new NotImplementedException();
    }
}

 

3. Create Context Menu Handler Class

Derive your context menu provider class from the CloudFilesContextMenuVerbIntegratedBase class provided with the Engine and pass your Engine instance directly to the base class constructor: 

[ComVisible(true)]
[ProgId("WebDAVDrive.ContextMenusProvider")]
[Guid("A22EBD03-343E-433C-98DF-372C6B3A1538")]
public class ContextMenuVerbIntegrated : CloudFilesContextMenuVerbIntegratedBase
{
    ContextMenuVerbIntegrated() : base(Program.Engine)
    {
    }
}

The handler will call the Engine instance that you pass to the constructor and than call the IEngine.GetMenuCommandAsync() method to get the IMenuCommandWindows interface when the menu is displayed and invoked.

You do not need to implement any methods in your menu provider class. 

You must decorate the class with the ComVisibleProgId and unique Guid attributes. You will use them when registering your COM menu handler shell extension. Note that you must create your own unique GUID for your menu provider handler. Do NOT use the GUID from this sample. 

 

4. Register Context Menu Handler

  • If you are using packaged app or sparse package manifest:

    • Add just the desktop3:CloudFilesContextMenus tag:

      <Package 
          xmlns:desktop3="http://schemas.microsoft.com/appx/manifest/desktop/windows10/3"
          xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" ...>
        <Applications>
          <Application ...>
            <Extensions>
              <desktop3:Extension Category="windows.cloudFiles">
                <desktop3:CloudFiles>
                  <desktop3:CloudFilesContextMenus>
                    <desktop3:Verb Id="MyCommand" 
                      Clsid="A22EBD03-343E-433C-98DF-372C6B3A1538" />
                  </desktop3:CloudFilesContextMenus>
                </desktop3:CloudFiles>
              </desktop3:Extension>
            </Extensions>
          </Application>
        </Applications>
      </Package>

    Note that in case of a packaged app or sparse package your COM will be automatically registered on app install and deleted on uninstall. You do NOT need to install/uninstall it manually using regsvr32.exe or Win 32 API.

  • If you run COM surrogate and using a regular installer:

    Use the ShellExtensionRegistrar.Register() method or regsvr32.exe or Win 32 API to register your COM.
    ShellExtensionRegistrar.Register()

 

5. Run Your COM Server

Create the LoclServerIntegrated class: 

using (LocalServer com = new LocalServerIntegrarted())
{
    com.RegisterClass<ContextMenuVerbIntegrated>();

    using (VirtualEngine engine = new VirtualEngine();
    {
        ...
    }
}

The LoclServerIntegrated class do NOT provide RunAsync() method and runs in the same application with your Engine instance.