Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop

Explorer Context Menu Manager

5.00/5 (2 votes)
16 Sep 2015CPOL12 min read 22.2K   1.6K  
Helper class to add, change and delete Registry based Explorer Context Menus from your app

Introduction

The task of adding Explorer Context Menus is usually left to installers because when they run, they have the required Admin rights to access the registry. However, it's sometimes nice to be able to modify these at runtime, for instance to add or remove an associated extension as a user option.

ExplorerMenuManager is a class you can use to add, remove or change Explorer Context Menus at runtime. It will help create these either for the current user or for the system.

It is also a simpler, cleaner alternative to implementing a Shell Extension.

Image 1

Background

While preparing a different article concerning Explorer Shell Extensions and the issue of using .NET managed code for them, I started thinking about simple alternatives. Rather than tacking this on to that article, I decided on a companion article.

Simple is of course, a relative term when dealing with the Registry. The idea behind ExplorerMenuManager was to provide as much functionality as possible and avoid the need for a Shell Extension, but still be easy to implement.

In particular, I wanted the ability to be able to alter associations on the fly. That is, if desired, the app can dissociate one or more extensions and the related context menu.

Context Menus vs Shell Extensions

Shell Extensions are DLLs and which Explorer invokes to get information about a Context Menu. These are listed in your registry as shellex keys. The menu text and so forth is never written to the Registry, but is provided by methods in the DLL Shell Extension. The action to perform is also decided by the Shell Extension which can do the job itself or start some other process.

Conversely, standard or static Context Menus use shell keys with the menu text and command in the Registry. This makes them easy to find and remove if you find them annoying.

From the end user's perspective, these do essentially the same thing, but there are a few differences:
Clicking the menu command on a standard Context Menu with multiple files selected, will start multiple instances of the target app, one for each file selected. There is no way to send many files to one instance (but VB makes it very easy to overcome this -- see below). Using an in-process Shell Extension launcher, all selected files are sent to one instance from the outset.

Another minor difference is when multiple files are selected, a standard menu will only display if all the selected files are registered as the same type. A Shell Extension menu is displayed based on the file Right-Clicked and a mixed bag of files can be sent/processed by a Shell Extension.

Technologically, they are very different. Shell Extensions run as part of Explorer, and can be something other than a Context Menu Handler. They can be Property Page Extensions or Thumbnail Handlers among others. Since these run in-process, a Shell Extensions written in .NET may result in more memory consumed and can be noticeably slower. There are several Microsoft warnings about .NET Shell Extensions, so they may cause problems or fail on occasion (I find them noticeable slower).

This article will cover how to use ExplorerMenuManager to add, change or remove context menu associations by user. ExplorerMenuManager provides the means to install and alter them system wide (HKCR) or by user though this is not advised.

It will also show how to respond in code to fetch the command line from later instances started so that your context menu can appear to sends many files to one instance like a Shell Extension can.

Using the Code

ExplorerMenuManager is a class you use to define and implement your context menu in the Registry. It supports both Adding and Removing menu entries, so that you can allow users to perhaps tick off which extensions they want to associate with your app. It is very easy to use:

VB.NET
Dim ExpMenu As New ExpMenuManager

With ExpMenu
     .AddExtensions("zig", "zag", ".zork")   ' or collect from UI (see demo)
     .MenuText = "Open in ZigZag"
     .ImageFile = "zigzag.ico"
     .ExtendedMenu = False
     .Remove = False                         ' adding these not removing them
End With
  • Any Icon you specify must exist in the folder with your EXE
  • The image must be a valid 16x16 icon

The various methods and tools are intended to associated the menu you define with the active EXE. As such, it works out the app name, etc. for itself. To run and test from the VS IDE still requires a compiled version in the desired directory along with the Icon, etc. How to configure VS for this is described later.

Context Menus are typically system wide, that is associating ".txt" with Notepad does so for all users. Since this requires writing to HKCR which in turn requires Admin privileges, ExplorerMenuManager will create the instructions required for RegEdit, then optionally start it as an elevated process:

VB.NET
If ExpMenu.CreateRegFileText(True) Then       ' creates the text/script
    ExpMenu.RegisterExtensions()              ' start regedit for you
End If

The result would be a new Context Menu entry which is immediately available:

zigfile
    shell
        Open with ZigZag
            commmand            "...pathto\MyMainApp.exe" "%1"

CreateRegFileText creates the text RegEdit requires, RegisterExtensions creates a temp file from that text, then attempts to run RegEdit as an elevated process. If ExpMenu.Remove is True, then the extension associations would have been removed rather than added to the Registry.

Image 2

You can also use the contents of the RegEditText property to invoke RegEdit yourself; perhaps to allow the user to review the contents before proceeding. In this case, you can replicate what happens in RegisterExtensions and use RegisterByFile instead:

VB.NET
' get a temp file... or bug the user with a FileDialog
Dim RegFile As String = Path.GetTempFileName()

ExpMenu.CreateRegFileText(True)                    ' create the RegEdit file contents

' ToDo: let the user approve changes (?)

' save data to file
File.WriteAllLines(RegFile, ExpMenu.RegEditText)   ' save script to file

' start regedit as process... or do it yourself
ExpMenu.RegisterByFile(RegFile, True)

File.Delete(RegFile)

Naturally, great care should be taken with what is written to the file to be fed to RegEdit, especially if the user "helps".

Add/Remove Associations

For cases where you are modifying the associated extensions, it is a two step process: first, all the old extensions need to be removed, then the new set added. Even though there is likely a great deal of overlap between the two sets, this is simpler and more foolproof.

ExplorerMenuManager provides the means to append the second instruction set to the first, to avoid firing RegEdit twice along with the required UAC dialog. Start by removing the association for all possible extensions:

VB.NET
Dim ExpMenu As New ContextMenuManager

With ExpMenu
    .AddExtensions(AllPossibleFileExts)
    .MenuText = ExpMenuText
    .ImageFile = ...
    .ExtendedMenu = False
    .Remove = Remove
End With

ExpMenu.CreateRegFileText(True)           ' create the remove instructions

ExpMenu.RemoveExtension("mp4")            ' remove undesired extension
ExpMenu.Remove = False                    ' change to ADD mode

ExpMenu.AppendRegFileText()               ' Append ADD instructions to existing REMOVE set
ExpMenu.RegisterExtensions()              ' Exec RegEdit with this instruction set

As before, you can get the instruction buffer and apply the changes yourself using RegisterByFile. The file these will be registered to will be the currently executing EXE.

Create a Menu Only for the Current User

Almost no apps create user level context menus, but they are possible. This would allow one user to opt out of one or more of the master extensions. The problem with this is that if there is also a master context menu for the root, the user level can be replaced. In some cases, your menu may display twice. It is not recommended, but it can work:

VB.NET
Dim ExpMenu As New ContextMenuManager

With ExpMenu
    .AddExtensions(...)
    ...
    .Remove = False
End With

ExpMenu.ApplyUserMenuEntries()

If .Remove is True, the associations will be removed, otherwise they are added.

Properties, Methods

VB.NET
Sub AddExtension(ext As String)
Sub AddExtensions(ParamArray ext As String())

Adds one or more extensions to be associated with your Explorer Context Menu. In the sample above, the same Context Menu will be associated with each listed extension. Then, the class will add a leading dot to make it a legitimate extension if it is missing.

All the other properties apply to all the extensions passed. That is, one Context Menu and handler is created for all of them. Your app however can implement multiple menus - Open, View, Send.

Property MenuText As String

The menu text to display for your extensions. This should come from a Constant to avoid accidentally adding different versions to the Registry.

Property ImageFile As String

The file name of a valid icon you wish to display with the menu text. The image must be an Icon and it must reside in the same folder as your target app. Do not include a path.

Property ExtendedMenu As Boolean

Set to True if you want your menu entry to show up only with Shift+Right Click.

Property Remove As Boolean

True or False whether the specified extensions should be added or removed from the registry.

ReadOnly Property RegEditText As String()

A string array containing the contents for a RegEdit .reg file, once CreateRegFileText has been invoked.

Function CreateRegFileText(AllUsers As Boolean) As Boolean

Fills the RegEditText with the instruction set for the extensions specified. Removal instructions are generated when Remove is true, otherwise instructions to create associations is generated.

Technically, you can create RegEdit file contents for HKCU menus, but it is somewhat silly to do this through RegEdit which will result in UAC dialogs when they can be applied directly.

See also ApplyUserMenuEntries and AppendRegFileText

Function AppendRegFileText() As Boolean

Appends instructions to the existing set of instructions. This should be used when modifying associations: first get the RegEdit instructions to remove all extensions; reset the Remove property to False; then AppendRegFileTextinstructions to tack on the code to reduiter the new set.

This provides instructions for both the Add and Remove actions into one file.

Function RegisterExtensions(Optional silent As Boolean = False) As Boolean

Invokes RegEdit with the current contents of the RegEditText array. The optional Silent parameter suppresses the 2 warning dialogs from RegEdit, but not UAC.

Function ApplyUserMenuEntries() As Boolean

Creates or removes the required registry entries for your Context Menu in HKCU rather than HKCR. I almost removed this because it is rather odd, but it might be useful in some niche cases.

Note that all the functions return False when there are problems with the definitions you have set up. For instance, an empty MenuText or the Image specified cannot be found in the folder where the EXE is located.

Send CommandLine to First App Instance

One of the few advantages to a Shell Extension is that it can start an instance of the target app, passing all the selected files to it. Normally, with static Context Menu entries in the Registry, multiple instances will start, but VB makes it very easy to "forward" the command line to the first instance.

  1. Add a method to your form to receive new arguments:
    VB.NET
    ' when new args are received, add them to the listbox and beep
    Friend Sub NewArgumentsReceived(args As String())
        lbFiles.Items.AddRange(args)
        Console.Beep()
    End Sub
    
  2. Make your app SingleInstance

    Project -> Properties -> Application tab
    Check the "Make Single Instance Application" checkbox option

  3. Add Code to Respond to the StartupNextInstance event

    Visual Basic's Application Framework provides an event which fires when single-instance application is started and an application instance is already active. The later instances will send the command line to the first instance via a pipe. We can add code to handle those new arguments.

    From the same Project Properties tab, click the Application Events button. This will open the Application Events code module. Select MyApplication Events in the left dropdown; and StartupNextInstance in the right. This will open/create the event handler. Here, we need to send the command line received to the form method we just wrote:

    VB.NET
    Private Sub MyApplication_StartupNextInstance(sender As Object,
                    e As ApplicationServices.StartupNextInstanceEventArgs) _
                      Handles Me.StartupNextInstance
    
        ' MyMainForm is the class name of the target form
        Dim f = Application.MainForm
        '  use YOUR actual form class name:
        If f.GetType Is GetType(MyMainForm) Then
            CType(f, MyMainForm).NewArgumentsReceived(e.CommandLine.ToArray)
        End If
            
        End Sub

    A few lines of code to respond to arguments passed to later instances. It's only slightly more involved when your app starts from Sub Main and there is no App Framework to provide the StartupNextInstance event. This explains the details implementing it for an app which starts from a Sub Main.

    Image 3

    The app started with the first file. Others were passed later via StartupNextInstance.

    With multiple files selected, the first instance will receive them one at a time since Explorer will try to start as many instances as there are files selected.

    When the first instance starts, the firewall may seek permission for it to "listen for connections from other computers". This is just the pipe mechanism to receive arguments initializing. You may want to inform your users about it, but the forwarding seems to work even if permission is denied.

    An Exception can occur starting 2 instances when selecting 2 files associated with the same context menu. If a firewall permission dialog comes up, users cannot usually respond fast enough before the connection request times out. This can also be useful with a Shell Extension which starts an app; typically these would start a new instance.

How To Use It

You can simply include the ExpContextMenu.vb file in your project. There is code to try and resolve the correct name if you use it from a DLL, but it is untested.

The Demo

The demo is a simple app to register or unregister a trio of file extensions. The code will generally be more interesting. As noted elsewhere, since it listens for command lines passed by later instances, running it may result in a firewall action depending on how yours is configured.

To test or run it, you must have a compiled copy in your test folder. This is required: ExplorerMenuManager won't create menu entries for a file which does not exist. It will also be needed for testing command line forwarding.

  • Create a test folder somewhere such as C:\Temp
  • Copy the files located in TestFiles to that test folder. These are fake/empty text files with the extensions needed and the icon used in the demo.
  • Configure VS to start in that folder. This is to emulate deployed runtime conditions:
    • Open Project Properties
    • On the Debug Tab, Start Options, set the working directory to the Test folder you created
  • Compile the demo and copy the EXE to the same test folder.

Note: The demo doesn't suppress any of the RegEdit confirmation dialogs. RegEdit's silent mode can be invoked using the optional silent parameter:

ExpMenu.RegisterRegFile(RegFile, True)

Run the demo either from VS or the compiled file. Register one or two of the file extensions listed. Then from Explorer, right click and you should see the new entry on the menu:

Image 4

Unregister them and the menu will cease to appear.

The Icon file must reside in the same folder as the target app, and both the icon files and EXE must exist. The demo includes a folder of Test Files which are nothing but empty Text files with extension names used in the demo.

If you have a compiled copy of the app in the test folder, you can also demo commandline forwarding:

  • Run either the VS project or the disk file as the first app instance (the VS instance will count as the first provided you have set the Start Options, Working Directory to your test folder)
  • If you haven't done so, register zig or zag files for your app
  • From explorer, open your context menu and click a file registered to the app (.zig, .zag, .zoe for the demo). This should start the EXE on disk which will forward the command line arguments to the first instance.

Summary

A standard or static Context Menu is more lightweight and easier to implement than a Shell Extension with the main limitation that they do not automatically send multiple files to a single instance of the target app.

ExplorerMenuManager provides the means to easily add and remove context menu handlers and avoid Shell Extensions. Its primary purpose is to provide the flexibility to allow individual users to tailor your context menu to their needs. As such, ExplorerMenuManager is mainly geared to work with global associations, but can work on a per-user model.

The demo also shows how to overcome the multiple instance limitation by handling the MyApplication.StartupNextInstance event to receive new command line arguments in a Single Instance application.

History

  • 2015.09.09 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)