From 515bedc299f4ca7d0abbb30b33bce3e40ff68bef Mon Sep 17 00:00:00 2001 From: David Hall Date: Tue, 29 Jan 2019 14:48:54 -0700 Subject: [PATCH] Merged ComRegistrar into ShellRegistrar. Completed work to allow local registrations in ShellRegistrar. --- Windows.Shell/ComRegistrar.cs | 56 ------------ Windows.Shell/ShellRegistrar.cs | 184 +++++++++++++++++++++++++++++++++++----- 2 files changed, 165 insertions(+), 75 deletions(-) delete mode 100644 Windows.Shell/ComRegistrar.cs diff --git a/Windows.Shell/ComRegistrar.cs b/Windows.Shell/ComRegistrar.cs deleted file mode 100644 index 0992c87a..00000000 --- a/Windows.Shell/ComRegistrar.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.Win32; -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -namespace Vanara.Windows.Shell -{ - /// A class to register COM objects. - public static class ComRegistrar - { - /// Registers the specified type as a COM Local Server. - /// The type of the COM object. - /// The friendly name of the COM object. - /// The command line arguments to supply to the executable on startup. - /// The assembly used to get the full path of the executable. If this value is , then the assembly of will be used. - /// If set to , the COM object is registered system-wide in HKLM; otherwise it is registered for the user only in HKCU. - /// The AppId to relate to this CLSID. If , the CLSID value will be used. - public static void RegisterLocalServer(string pszFriendlyName, string cmdLineArgs = null, Assembly assembly = null, bool systemWide = false, Guid? appId = null) where TComObject : ComObject - { - var cmdLine = (assembly ?? typeof(TComObject).Assembly).Location; - var clsid = GetClsid(); - var _appId = appId ?? clsid; - if (!(cmdLineArgs is null)) cmdLine += " " + cmdLineArgs; - - using (var root = GetRoot(systemWide, true)) - using (root.CreateSubKey(@"AppID\" + _appId.ToRegString(), pszFriendlyName)) - using (var rClsid = root.CreateSubKey(@"CLSID\" + clsid.ToRegString(), pszFriendlyName)) - using (rClsid.CreateSubKey("LocalServer32", cmdLine)) - { - rClsid.SetValue("AppId", _appId.ToRegString()); - } - } - - /// Unregisters the COM Local Server. - /// The type of the COM object. - /// If set to , the COM object is unregistered system-wide from HKLM; otherwise it is unregistered for the user only in HKCU. - /// The AppId to relate to this CLSID. If , the CLSID value will be used. - public static void UnregisterLocalServer(bool systemWide = false, Guid? appId = null) where TComObject : ComObject - { - var clsid = GetClsid(); - using (var root = GetRoot(systemWide, true)) - { - root.DeleteSubKeyTree(@"AppID\" + (appId ?? clsid).ToRegString()); - root.DeleteSubKeyTree(@"CLSID\" + clsid.ToRegString()); - } - } - - internal static RegistryKey GetRoot(bool systemWide, bool writable, string subkey = null) - { - var key = systemWide ? Registry.LocalMachine : Registry.CurrentUser; - return key.OpenSubKey(@"Software\Classes" + (subkey is null ? "" : "\\" + subkey), writable); - } - - private static Guid GetClsid() => Marshal.GenerateGuidForType(typeof(TComObject)); - } -} \ No newline at end of file diff --git a/Windows.Shell/ShellRegistrar.cs b/Windows.Shell/ShellRegistrar.cs index 66c3903b..ddf6e13b 100644 --- a/Windows.Shell/ShellRegistrar.cs +++ b/Windows.Shell/ShellRegistrar.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using static Vanara.PInvoke.Shell32; @@ -10,13 +12,13 @@ using static Vanara.PInvoke.ShlwApi; namespace Vanara.Windows.Shell { - /// Static class that has methods used to register and unregister shell items in the Windows Registry. + /// Contains static methods used to register and unregister shell items in the Windows Registry. public static class ShellRegistrar { /* HRESULT RegisterAppDropTarget() const; - // create registry entries for drop target based static verb. the specified clsid will be + // create registry entries for drop target based static verb. the specified CLSID will be HRESULT RegisterCreateProcessVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszCmdLine, PCWSTR pszVerbDisplayName) const; HRESULT RegisterDropTargetVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const; @@ -61,19 +63,66 @@ namespace Vanara.Windows.Shell */ + /// Gets the file extensions associated with a given ProgID. + /// The ProgID. + /// An enumeration of file extensions in the form ".ext". public static IEnumerable GetAssociatedFileExtensions(string progId) => - Registry.ClassesRoot.GetSubKeyNames().Where(n => n.StartsWith(".") && Registry.ClassesRoot.HasSubKey($"{n}\\{progId}")); + GetRoot().GetSubKeyNames().Where(n => n.StartsWith(".") && Registry.ClassesRoot.HasSubKey($"{n}\\{progId}")); - public static void RegisterApplication(string fullExePath, bool userOnly = false, bool acceptsUrls = false, Guid? dropTarget = null, IndirectString friendlyName = null, - IEnumerable supportedTypes = null, IconLocation defaultIcon = null, bool noStartPage = false, IconLocation taskGroupIcon = null, - bool useExecutableForTaskbarGroupIcon = false) + /// Determines if the specified type is registered as a COM Local Server. + /// The type of the COM object. + /// + /// The assembly used to get the full path of the executable. If this value is , then the assembly of + /// will be used. + /// + /// + /// If set to , registration is checked in HKLM; otherwise it is registered for the user only + /// in HKCU. + /// + /// The AppId to relate to this CLSID. If , the CLSID value will be used. + public static bool IsRegisteredAsLocalServer(Assembly assembly = null, bool systemWide = false, Guid? appId = null) where TComObject : ComObject + { + var cmdLine = (assembly ?? typeof(TComObject).Assembly).Location; + var clsid = GetClsid(); + var _appId = appId ?? clsid; + + using (var root = GetRoot(systemWide, true)) + { + if (!root.HasSubKey(@"CLSID\" + clsid.ToRegString())) return false; + using (var rClsid = root.OpenSubKey(@"CLSID\" + clsid.ToRegString() + @"\LocalServer32")) + if (rClsid == null || !string.Equals(rClsid.GetValue("")?.ToString(), cmdLine, StringComparison.OrdinalIgnoreCase)) return false; + if (!root.HasSubKey(@"AppID\" + _appId.ToRegString())) return false; + } + return true; + } + + /// Registers the application. + /// The full executable path. + /// if set to [user only]. + /// if set to [accepts urls]. + /// The drop target. + /// Name of the friendly. + /// The supported types. + /// The default icon. + /// if set to [no start page]. + /// The task group icon. + /// if set to [use executable for taskbar group icon]. + /// fullExePath + /// + /// Unable to create application key in the 'App Paths' subkey. + /// or + /// Unable to create application key in the HKCR\\Applications subkey. + /// + public static void RegisterApplication(string fullExePath, bool userOnly = false, bool acceptsUrls = false, Guid? dropTarget = null, + IndirectString friendlyName = null, IEnumerable supportedTypes = null, IconLocation defaultIcon = null, bool noStartPage = false, + IconLocation taskGroupIcon = null, bool useExecutableForTaskbarGroupIcon = false) { if (fullExePath == null) throw new ArgumentNullException(nameof(fullExePath)); fullExePath = Path.GetFullPath(fullExePath); var fn = Path.GetFileName(fullExePath).ToLower(); // Handle registrations in user or machine "App Paths" - using (var reg = ComRegistrar.GetRoot(!userOnly, true, @"Software\Microsoft\Windows\CurrentVersion\App Paths")) + using (var reg = GetRoot(!userOnly, true, @"Software\Microsoft\Windows\CurrentVersion\App Paths")) using (var sk = reg?.CreateSubKey(fn)) { if (sk == null) throw new InvalidOperationException("Unable to create application key in the 'App Paths' subkey."); @@ -95,7 +144,7 @@ namespace Vanara.Windows.Shell } // Handle registrations in HKCR\Applications - using (var reg = Registry.ClassesRoot.OpenSubKey(@"Applications")) + using (var reg = GetRoot(!userOnly, true, @"Applications")) using (var sk = reg?.CreateSubKey(fn)) { if (sk == null) throw new InvalidOperationException("Unable to create application key in the HKCR\\Applications subkey."); @@ -118,6 +167,13 @@ namespace Vanara.Windows.Shell NotifyShell(); } + /// Registers the command verb. + /// The parent key. + /// The verb. + /// The display name. + /// The command. + /// + /// Unable to create required key in registry. public static CommandVerb RegisterCommandVerb(RegistryKey parentKey, string verb, string displayName = null, string command = null) { var vkey = parentKey.CreateSubKey("shell\\" + verb) ?? throw new InvalidOperationException("Unable to create required key in registry."); @@ -128,13 +184,29 @@ namespace Vanara.Windows.Shell return v; } - public static void RegisterFileAssociation(string ext, string progId, PERCEIVED perceivedType = PERCEIVED.PERCEIVED_TYPE_UNSPECIFIED, string contentType = null) + /// Registers the file association. + /// The ext. + /// The ProgID. + /// Type of the perceived. + /// Type of the content. + /// + /// ext + /// or + /// progId + /// + /// + /// Extension must start with a '.' - ext + /// or + /// Undefined ProgId value. - progId + /// + /// Unable to create association key in the registry. + public static void RegisterFileAssociation(string ext, string progId, PERCEIVED perceivedType = PERCEIVED.PERCEIVED_TYPE_UNSPECIFIED, string contentType = null, bool systemWide = false) { if (ext == null) throw new ArgumentNullException(nameof(ext)); if (!ext.StartsWith(".")) throw new ArgumentException("Extension must start with a '.'", nameof(ext)); if (progId == null) throw new ArgumentNullException(nameof(progId)); if (!IsDefined(progId)) throw new ArgumentException("Undefined ProgId value.", nameof(progId)); - using (var pkey = Registry.ClassesRoot.CreateSubKey(ext)) + using (var pkey = GetRoot(systemWide, true).CreateSubKey(ext)) { if (pkey == null) throw new InvalidOperationException("Unable to create association key in the registry."); pkey.SetValue(null, progId); @@ -146,18 +218,62 @@ namespace Vanara.Windows.Shell NotifyShell(); } - public static ProgId RegisterProgID(string progId, string typeName) + /// Registers the specified type as a COM Local Server. + /// The type of the COM object. + /// The friendly name of the COM object. + /// The command line arguments to supply to the executable on startup. + /// + /// The assembly used to get the full path of the executable. If this value is , then the assembly of + /// will be used. + /// + /// + /// If set to , the COM object is registered system-wide in HKLM; otherwise it is registered for the user only + /// in HKCU. + /// + /// The AppId to relate to this CLSID. If , the CLSID value will be used. + public static void RegisterLocalServer(string pszFriendlyName, string cmdLineArgs = null, Assembly assembly = null, bool systemWide = false, Guid? appId = null) where TComObject : ComObject + { + var cmdLine = (assembly ?? typeof(TComObject).Assembly).Location; + var clsid = GetClsid(); + var _appId = appId ?? clsid; + if (!(cmdLineArgs is null)) cmdLine += " " + cmdLineArgs; + + using (var root = GetRoot(systemWide, true)) + using (root.CreateSubKey(@"AppID\" + _appId.ToRegString(), pszFriendlyName)) + using (var rClsid = root.CreateSubKey(@"CLSID\" + clsid.ToRegString(), pszFriendlyName)) + using (rClsid.CreateSubKey("LocalServer32", cmdLine)) + { + rClsid.SetValue("AppId", _appId.ToRegString()); + } + } + + /// Registers the ProgID. + /// The ProgID. + /// Name of the type. + /// + /// progId + /// + /// A ProgID may not have more then 39 characters, must start with a letter, and may only contain letters, numbers and periods. + /// or + /// ProgID already exists - progId + /// + public static ProgId RegisterProgID(string progId, string typeName, bool systemWide = false) { if (progId == null) throw new ArgumentNullException(nameof(progId)); if (progId.Length > 39 || !Regex.IsMatch(progId, @"^[a-zA-Z][\w\.]+$", RegexOptions.Singleline)) throw new ArgumentException("A ProgID may not have more then 39 characters, must start with a letter, and may only contain letters, numbers and periods."); - if (Registry.ClassesRoot.HasSubKey(progId)) throw new ArgumentException("ProgID already exists", nameof(progId)); - return new ProgId(progId, Registry.ClassesRoot.CreateSubKey(progId, typeName), false); + if (GetRoot().HasSubKey(progId)) throw new ArgumentException("ProgID already exists", nameof(progId)); + return new ProgId(progId, GetRoot(systemWide, true).CreateSubKey(progId, typeName), false); } - public static void UnregisterFileAssociation(string ext, string progId, bool throwOnMissing = true) + /// Unregisters the file association. + /// The ext. + /// The ProgID. + /// if set to [throw on missing]. + /// Unable to find association key in the registry. + public static void UnregisterFileAssociation(string ext, string progId, bool throwOnMissing = true, bool systemWide = false) { - using (var sk = Registry.ClassesRoot.OpenSubKey(ext, true)) + using (var sk = GetRoot(systemWide, true, ext)) { if (sk == null) { @@ -171,27 +287,57 @@ namespace Vanara.Windows.Shell } } - public static void UnregisterProgID(string progId, IEnumerable fileExtensions = null) + /// Unregisters the COM Local Server. + /// The type of the COM object. + /// + /// If set to , the COM object is unregistered system-wide from HKLM; otherwise it is unregistered for the user + /// only in HKCU. + /// + /// The AppId to relate to this CLSID. If , the CLSID value will be used. + public static void UnregisterLocalServer(bool systemWide = false, Guid? appId = null) where TComObject : ComObject + { + var clsid = GetClsid(); + using (var root = GetRoot(systemWide, true)) + { + root.DeleteSubKeyTree(@"AppID\" + (appId ?? clsid).ToRegString()); + root.DeleteSubKeyTree(@"CLSID\" + clsid.ToRegString()); + } + } + + /// Unregisters the ProgID. + /// The ProgID. + /// The file extensions. + public static void UnregisterProgID(string progId, IEnumerable fileExtensions = null, bool systemWide = false) { try { - Registry.ClassesRoot.DeleteSubKeyTree(progId); + GetRoot(systemWide, true).DeleteSubKeyTree(progId); } catch { - Registry.ClassesRoot.DeleteSubKey(progId, false); + GetRoot(systemWide, true).DeleteSubKey(progId, false); } if (fileExtensions == null) return; foreach (var ext in fileExtensions) - UnregisterFileAssociation(ext, progId, false); + UnregisterFileAssociation(ext, progId, false, systemWide); NotifyShell(); } + internal static RegistryKey GetRoot(bool systemWide = true, bool writable = false, string subkey = null) + { + if (!writable && subkey is null) + return Registry.ClassesRoot; + var key = systemWide ? Registry.LocalMachine : Registry.CurrentUser; + return key.OpenSubKey(@"Software\Classes" + (subkey is null ? "" : "\\" + subkey), writable); + } + internal static void NotifyShell() => SHChangeNotify(SHCNE.SHCNE_ASSOCCHANGED, SHCNF.SHCNF_FLUSHNOWAIT | SHCNF.SHCNF_IDLIST); + private static Guid GetClsid() => Marshal.GenerateGuidForType(typeof(TComObject)); + private static bool IsDefined(string rootValue) => Registry.ClassesRoot.HasSubKey(rootValue); } } \ No newline at end of file