Creating Custom Windows Explorer Context Menu Shell Extension for Virtual Drive

This article describes how to add a context menu commands shell extension to Windows Explorer in your virtual drive application. 

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:

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)
    {
        Guid myMenuCommandGuid = typeof(ContextMenusProvider).GUID;

        if (menuGuid == myMenuCommandGuid)
        {
            return new MyMenuCommand(this, this.Logger);
        }

        throw new NotImplementedException();
    }
}

 

3. Create Context Menu Handler Class

  • If you run your context menu handler as COM exe or COM surrogate outside of the main app:

    Derive your context menu provider class from CloudFilesContextMenuVerbRpcBase class:

    [ComVisible(true)]
    [ProgId("VirtualDrive.ContextMenusProvider")]
    [Guid("9C923BF3-3A4B-487B-AB4E-B4CF87FD1C25")]
    public class ContextMenusProvider : CloudFilesContextMenuVerbRpcBase
    {
    }

    The menu handler will find the Engine instance behind the drive on which this menu handler is called automatically and than call the IEngine.GetMenuCommandAsync()  when menu is needed.

  • If you run your context menu handler as COM exe inside main app:

    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 ContextMenusProvider : CloudFilesContextMenuVerbIntegratedBase
    {
        ContextMenusProvider() : 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 handler. 

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:

    • If you run your menu handler as COM exe or COM surrogate outside of the main app:

      Add the desktop3:CloudFilesContextMenus and the com:ExeServer code tags to your packaged app manifest or sparse package manifest:

      <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="9C923BF3-3A4B-487B-AB4E-B4CF87FD1C25" />
                  </desktop3:CloudFilesContextMenus>
                </desktop3:CloudFiles>
              </desktop3:Extension>
              <com:Extension Category="windows.comServer">
                <com:ComServer>
                  <com:ExeServer
                    DisplayName="VirtualDrive.ShellExtension"             
                    Executable="VirtualDrive.ShellExtension\VirtualDrive.ShellExtension.exe">
                    <com:Class 
                      Id="9C923BF3-3A4B-487B-AB4E-B4CF87FD1C25" />
                  </com:ExeServer>
                </com:ComServer>
              </com:Extension>
            </Extensions>
          </Application>
        </Applications>
      </Package>

      Note that you must specify where your surrogate COM or as your exe COM server is located in com:Extension tag.

    • If you run your menu handler as COM exe inside main app:

      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 COM exe Server

  • If you run COM exe or COM surrogate outside of the main app:

    Create the LocalServerRpc class instance, register your class and call the LocalServerRpc.RunAsync() method:

    static async Task Main(string[] args)
    {
        using(LocalServer com = new LocalServerRpc())
        {
            com.RegisterClass<ContextMenuVerbRpc>();
            await com.RunAsync();
        }
    }

    Note that this code runs in the application which is separate from your main application, in which you run the Engine instance.

  • If you run COM exe inside main app:

    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.

Next Article:

Creating Custom States and Columns Provider Shell Extension for Virtual Drive