ShellBoost

Writing your first extension

We’ll start by writing a simple C# Console Application using Visual Studio.

Open Visual Studio, choose File, New, Project menu items, and pick a .NET Framework Console project. Since Shell Namespace Extensions are only supported on Windows, there’s no point in choosing another framework, like .NET core.

Writing your first extension - Picture 38

We recommend you configure your project to build in “Any Cpu” mode and you uncheck the “Prefer 32-bit” setting. This way, your .exe will run as a 32-bit process on a 32-bit machine and as a 64-bit process on a 64-bit machine.

Writing your first extension - Picture 36

Writing your first extension - Picture 37

Copy the following ShellBoost files under your project’s root:

1.       ShellBoost.<id>.x86.dll (ShellBoost native proxy assembly 32-bit)

2.       ShellBoost.<id>.x64.dll (ShellBoost native proxy assembly 64-bit)

3.       ShellBoost.Core.dll (ShellBoost .NET)

4.       ShellBoost.Core.xml (for ShellBoost API help autocompletion)

Reference ShellBoost.Core.dll as a .NET reference.

Since the native proxy assemblies are not .NET references, they must always be present in the same directory as ShellBoost.Core.dll. The simplest way to do this is to configure the Visual Studio’s “Build Action” as “Copy if newer” for these two files, so they will be copied at build time aside ShellBoost.Core.dll and your target .exe file:

Writing your first extension - Picture 39

Shell folder server

The heart of any ShellBoost program (Console or other) is a running instance of a ShellBoost ShellFolderServer .NET class. As the name implies, this class will “serve” folders (and items) to the Windows Shell. So, the first thing we’ll do is create a class that derives from that class, something like this:

public class MyShellFolderServer : ShellFolderServer // this base class is located in ShellBoost.Core
{
}

Note this doesn’t compile, because we must override the GetRootFolder() method. But we first need to define what will be our Root Folder.

Root folder

The root folder is located at the top of folders and items hierarchy for your namespace extension. It’s represented by a class that must derive from the ShellBoost RootShellFolder class:

public class MyRootFolder : RootShellFolder  // this base class is located in ShellBoost.Core
{
    // we want to keep a reference on our custom ShellFolderServer
    public MyRootFolder(MyShellFolderServer server, ShellItemIdList idList)
        : base(idList)
    {
        Server = server;
    }
 
    public MyShellFolderServer Server { get; }
}

Now, we can override the GetRootFolder() method of our Shell Folder Server, like this:

public class MyShellFolderServer : ShellFolderServer
{
    private MyRootFolder _root;
 
    // only the Shell knows our root folder PIDL
    protected override RootShellFolder GetRootFolder(ShellItemIdList idl)
    {
        if (_root == null)
        {
            _root = new MyRootFolder(this, idl);
        }
        return _root;
    }
}

Starting with ShellBoost version 1.5.0.2, the root folder class can derive from the base ShellFolder class instead of the specialized RootShellFolder class. In this case, the code is similar:

public class MyRootFolder : ShellFolder  // this base class is located in ShellBoost.Core
{
    // reference to the ShellFolderServer is now available as the FolderServer instance property 
    public MyRootFolder(ShellItemIdList idList)
        : base(idList)
    {
    }
}

Now, we can override the GetFolderAsRoot() method of our Shell Folder Server, like this:

public class MyShellFolderServer : ShellFolderServer
{
    private MyRootFolder _root;
 
    // only the Shell knows our root folder PIDL
    protected override ShellFolder GetFolderAsRoot(ShellItemIdList idl)
    {
        if (_root == null)
        {
            _root = new MyRootFolder(idl);
        }
        return _root;
    }
}

That’s it for the Shell Folder Server and the Root Folder. Now we must write code to register and start the Shell Folder Server.

Program startup

For a Console Application, program startup is quite simple. You will just need to register and start the Shell Folder Server custom class you’ve created before, and let it run “forever”. In our sample, we can terminate the program when the user presses the ESC key on the keyboard.

To register (and unregister) a Shell Namespace Extension to the Windows Shell, ShellBoost provides utility static methods located on the ShellFolderServer class. These methods handle COM registry actions on ShellBoost native proxy assemblies. Note you can also choose regular COM registration if you prefer (check the Deployment chapter for more on this).

...
ShellFolderServer.RegisterNativeDll(RegistrationMode.User);
...
ShellFolderServer.UnregisterNativeDll(RegistrationMode.User);
...

The RegistrationMode parameter in this context can be User or Machine. If you choose User, registration will happen in the HKEY_CURRENT_USER registry hive. If you choose Machine, registration will happen in the HKEY_LOCAL_MACHINE registry hive. We recommend avoiding Machine registration because it requires elevated permissions for the process which runs this code.

Starting the server is done with a code like this:

using (var server = new MyShellFolderServer())
{
    var config = new ShellFolderConfiguration();   // this class is located in ShellBoost.Core
    server.Start(config); // start the server
    Console.WriteLine("Started. Press ESC to stop.");
    while (Console.ReadKey(true).Key != ConsoleKey.Escape)
    {
    }
    Console.WriteLine("Stopped"); // end of program
}

As you see, starting the server is quite simple. Note the ShellFolderServer class is disposable hence the using code block.

Here is the complete code in program.cs:

using System;
using System.Collections.Generic;
using ShellBoost.Core;
using ShellBoost.Core.WindowsShell;
 
namespace FirstShellBoost
{
    class Program
    {
        static void Main(string[] args)
        {
            ShellFolderServer.RegisterNativeDll(RegistrationMode.User);
            Console.WriteLine("Registered");
 
            using (var server = new MyShellFolderServer())
            {
                var config = new ShellFolderConfiguration();   // this class is located in ShellBoost.Core
                server.Start(config); // start the server
                Console.WriteLine("Started. Press ESC to stop.");
 
                while (Console.ReadKey(true).Key != ConsoleKey.Escape)
                {
                }
                Console.WriteLine("Stopped"); // end of program
            }
 
            // uncomment these lines to unregister completely the namespace extension
            //ShellFolderServer.UnregisterNativeDll(RegistrationMode.User);
            //Console.WriteLine("Unregistered");
        }
    }
 
    public class MyShellFolderServer : ShellFolderServer // this base class is located in ShellBoost.Core
    {
        private MyRootFolder _root;
 
        // only the Shell knows our root folder PIDL
        protected override RootShellFolder GetRootFolder(ShellItemIdList idl)
        {
            if (_root == null)
            {
                _root = new MyRootFolder(this, idl);
            }
            return _root;
        }
    }
 
    public class MyRootFolder : RootShellFolder  // this base class is located in ShellBoost.Core
    {
        // we want to keep a reference on our custom ShellFolderServer
        public MyRootFolder(MyShellFolderServer server, ShellItemIdList idList)
            : base(idList)
        {
            Server = server;
        }
 
        public MyShellFolderServer Server { get; }
    }
}

If you build and run the code, open Windows Explorer, and select “This PC”, you should see this:

Program startup - Picture 40

If you open “My ShellBoost Project”, you should see an empty directory. This is normal. Let’s add two shell items to our root folder’s code. Press ESC on the console app to stop serving files (the Explorer Windows can stay opened), and add lines like this:

public override IEnumerable<ShellItem> EnumItems(SHCONTF options)
{
    // note by default, ShellBoost uses the key/ID value as the display name if it’s not explicitly defined
    yield return new ShellFolder(this, new StringKeyShellItemId("My First Folder"));
    yield return new ShellItem(this, new StringKeyShellItemId("My First Item"));
}

You can rebuild and run the app again, go to the Windows Explorer and press refresh (F5), you should now see something like this, depending on your shell UI settings:

Program startup - Picture 42

And voilà! You’ve just created a Shell Namespace Extension that has fully virtual items (not related to physical files).