Click here to Skip to main content
15,883,827 members
Articles / Desktop Programming / Win32

.NET Shell Extensions - Shell Preview Handlers

Rate me:
Please Sign up or sign in to vote.
4.93/5 (23 votes)
20 May 2014MIT8 min read 140.3K   6.1K   71  
Quickly create Shell Preview Handlers for Windows or Outlook using .NET!
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security.AccessControl;
using System.Text;
using Microsoft.Win32;
using SharpShell.Attributes;
using SharpShell.Extensions;
using SharpShell.Interop;
using SharpShell.ServerRegistration;

namespace SharpShell.SharpIconOverlayHandler
{

    //  TODO: Where appropriate, the handler should use the DisplayName

    /// <summary>
    /// The SharpIconHandler is the base class for SharpShell servers that provide
    /// custom Icon Handlers.
    /// </summary>
    [ServerType(ServerType.ShellIconOverlayHandler)]
    public abstract class SharpIconOverlayHandler : SharpShellServer, IShellIconOverlayIdentifier
    {
        #region Implementation of IShellIconOverlayIdentifier

        /// <summary>
        /// Specifies whether an icon overlay should be added to a Shell object's icon.
        /// </summary>
        /// <param name="pwszPath">A Unicode string that contains the fully qualified path of the Shell object.</param>
        /// <param name="dwAttrib">The object's attributes. For a complete list of file attributes and their associated flags, see IShellFolder::GetAttributesOf.</param>
        /// <returns>
        /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
        /// </returns>
        int IShellIconOverlayIdentifier.IsMemberOf(string pwszPath, SFGAO dwAttrib)
        {
            //  Log this key event.
            Log(string.Format("IsMemberOf for {0}", pwszPath));

            //  Always wrap calls to the abstract functions in exception handlers.
            try
            {
                var result = CanShowOverlay(pwszPath, dwAttrib);
                Log("Result is: " + result);

                //  Return OK if we should show the overlay, otherwise false.
                return CanShowOverlay(pwszPath, dwAttrib) ? WinError.S_OK : WinError.S_FALSE;
            }
            catch (Exception exception)
            {
                //  Log the exception.
                LogError("An exception occured when determining whether to show the overlay for '" + pwszPath + "'.", exception);

                //  Return false so we don't try and get the icon for a server that is erroring.
                return WinError.S_FALSE;
            }
        }

        /// <summary>
        /// Provides the location of the icon overlay's bitmap.
        /// </summary>
        /// <param name="pwszIconFile">A null-terminated Unicode string that contains the fully qualified path of the file containing the icon. The .dll, .exe, and .ico file types are all acceptable. You must set the ISIOI_ICONFILE flag in pdwFlags if you return a file name.</param>
        /// <param name="cchMax">The size of the pwszIconFile buffer, in Unicode characters.</param>
        /// <param name="pIndex">Pointer to an index value used to identify the icon in a file that contains multiple icons. You must set the ISIOI_ICONINDEX flag in pdwFlags if you return an index.</param>
        /// <param name="pdwFlags">Pointer to a bitmap that specifies the information that is being returned by the method. This parameter can be one or both of the following values: ISIOI_ICONFILE, ISIOI_ICONINDEX.</param>
        /// <returns>
        /// If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
        /// </returns>
        int IShellIconOverlayIdentifier.GetOverlayInfo(StringBuilder pwszIconFile, int cchMax, out int pIndex, out ISIOI pdwFlags)
        {
            //  Log this key event.
            Log("GetOverlayInfo called to get icon overlay.");

            //  Set empty values for the out parameters first.
            pdwFlags = ISIOI.ISIOI_ICONFILE;
            pIndex = 0;

            //  If we're not in debug mode and we've already created the temporary icon file, 
            //  we can return it. If we're in debug mode, we'll always create it.
#if !DEBUG
            if(!string.IsNullOrEmpty(temporaryIconOverlayFilePath) && File.Exists(temporaryIconOverlayFilePath))
            {
                //  Set the icon file path.
                pwszIconFile.Append(temporaryIconOverlayFilePath);
                return WinError.S_OK;
            }
#endif

            //  Storage for the overlay icon.
            Icon overlayIcon;

            //  Always wrap calls to the abstract functions in exception handlers.
            try
            {
                //  Get the overlay icon.
                overlayIcon = GetOverlayIcon();
            }
            catch (Exception exception)
            {
                //  Log the exception.
                LogError("An exception occured when trying to get the overlay icon.", exception);

                //  Return an error.
                return WinError.E_FAIL;
            }

            //  Create a temporary icon file path.
            try
            {
                CreateTemporaryIconFilePath();

                //  Save the file object to the icon file path.
                using (var fileStream = new FileStream(temporaryIconOverlayFilePath, FileMode.Create))
                {
                    //  Save the icon to the stream, then flush the stream.
                    overlayIcon.Save(fileStream);
                    fileStream.Flush(true);
                }

                //  Set the icon file path.
                pwszIconFile.Append(temporaryIconOverlayFilePath);
            }
            catch (Exception exception)
            {
                //  Log the exception.
                LogError("An exception occured when trying to create the overlay icon.", exception);

                //  Return an error.
                return WinError.E_FAIL;
            }

            //  We've successfully created the icon file and set it.
            return WinError.S_OK;
        }

        /// <summary>
        /// Specifies the priority of an icon overlay.
        /// </summary>
        /// <param name="pPriority">The address of a value that indicates the priority of the overlay identifier. Possible values range from zero to 100, with zero the highest priority.</param>
        /// <returns>
        /// Returns S_OK if successful, or a COM error code otherwise.
        /// </returns>
        int IShellIconOverlayIdentifier.GetPriority(out int pPriority)
        {
            //  Log this key event.
            Log("GetPriority called to get icon overlay priority.");

            //  By default, we'll set the lowest priority.
            pPriority = 100;

            //  Try can set the priority using the abstract function.
            try
            {
                //  Get the priority.
                var priority = GetPriority();

                //  Set the limit.
                if (priority < 0)
                    priority = 0;
                else if (priority > 100)
                    priority = 100;

                //  Set priority.
                pPriority = priority;
            }
            catch (Exception exception)
            {
                //  Log the exception.
                LogError("An exception occured when trying to get the priority.", exception);
            }

            //  Return success.
            return WinError.S_OK;
        }

        #endregion

        /// <summary>
        /// The custom registration function.
        /// </summary>
        /// <param name="serverType">Type of the server.</param>
        /// <param name="registrationType">Type of the registration.</param>
        [CustomRegisterFunction]
        internal static void CustomRegisterFunction(Type serverType, RegistrationType registrationType)
        {
            //  Open the local machine.
            using(var localMachineBaseKey = registrationType == RegistrationType.OS64Bit
                ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) :
                  RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
            {
                //  Open the ShellIconOverlayIdentifiers.
                using(var overlayIdentifiers = localMachineBaseKey
                    .OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers", 
                    RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.CreateSubKey | RegistryRights.CreateSubKey))
                {
                    //  If we don't have the key, we've got a problem.
                    if(overlayIdentifiers == null)
                        throw new InvalidOperationException("Cannot open the ShellIconOverlayIdentifiers key.");

                    //  Create the overlay key.
                    using(var overlayKey = overlayIdentifiers.CreateSubKey(serverType.Name))
                    {
                        //  If we don't have the overlay key, we've got a problem.
                        if(overlayKey == null)
                            throw new InvalidOperationException("Cannot create the key for the overlay server.");

                        //  Set the server CLSID.
                        overlayKey.SetValue(null, serverType.GUID.ToRegistryString());
                    }
                }
            }
        }

        /// <summary>
        /// Customs the unregister function.
        /// </summary>
        /// <param name="serverType">Type of the server.</param>
        /// <param name="registrationType">Type of the registration.</param>
        [CustomUnregisterFunction]
        internal static void CustomUnregisterFunction(Type serverType, RegistrationType registrationType)
        {
            //  Open the local machine.
            using (var localMachineBaseKey = registrationType == RegistrationType.OS64Bit
                ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) :
                  RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
            {
                //  Open the ShellIconOverlayIdentifiers.
                using (var overlayIdentifiers = localMachineBaseKey
                    .OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers",
                    RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.Delete | RegistryRights.EnumerateSubKeys | RegistryRights.ReadKey))
                {
                    //  If we don't have the key, we've got a problem.
                    if (overlayIdentifiers == null)
                        throw new InvalidOperationException("Cannot open the ShellIconOverlayIdentifiers key.");

                    //  Delete the overlay key.
                    if (overlayIdentifiers.GetSubKeyNames().Any(skn => skn == serverType.Name))
                        overlayIdentifiers.DeleteSubKey(serverType.Name);
                }
            }
        }

        /// <summary>
        /// Creates the temporary icon file path.
        /// </summary>
        private void CreateTemporaryIconFilePath()
        {
            //  Create the temporary icon file path.
            temporaryIconOverlayFilePath = Path.Combine(Path.GetTempPath(), GetType().Name + ".ico");

            //  If the path exists (which is possible when debugging or if the server has run before)
            //  then delete it.
            //  TODO: Is this safe in a multi-threaded shell?
            if(File.Exists(temporaryIconOverlayFilePath))
                File.Delete(temporaryIconOverlayFilePath);
        }

        /// <summary>
        /// Called by the system to get the priority, which is used to determine
        /// which icon overlay to use if there are multiple handlers. The priority
        /// must be between 0 and 100, where 0 is the highest priority.
        /// </summary>
        /// <returns>A value between 0 and 100, where 0 is the highest priority.</returns>
        protected abstract int GetPriority();

        /// <summary>
        /// Determines whether an overlay should be shown for the shell item with the path 'path' and 
        /// the shell attributes 'attributes'.
        /// </summary>
        /// <param name="path">The path for the shell item. This is not necessarily the path
        /// to a physical file or folder.</param>
        /// <param name="attributes">The attributes of the shell item.</param>
        /// <returns>
        ///   <c>true</c> if this an overlay should be shown for the specified item; otherwise, <c>false</c>.
        /// </returns>
        protected abstract bool CanShowOverlay(string path, SFGAO attributes);

        /// <summary>
        /// Called to get the icon to show as the overlay icon.
        /// </summary>
        /// <returns>The overlay icon.</returns>
        protected abstract Icon GetOverlayIcon();

        /// <summary>
        /// The temporary icon overlay file path.
        /// </summary>
        private string temporaryIconOverlayFilePath;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions