using Microsoft.Win32; using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Vanara.Windows.Shell.Registration { /// Represents the registration entries for an application. /// /// ///using (var appReg = AppRegistration.Register(Application.ExecutablePath, systemWide)) ///{ /// appReg.SupportedTypes.Add(".doc"); /// appReg.SupportedTypes.Add(".txt"); ///} /// /// /// public class AppRegistration : RegBasedSettings { internal const string appPathsSubKey = @"Software\Microsoft\Windows\CurrentVersion\App Paths"; internal const string appsSubKey = "Applications"; private readonly AppSubKey appKey; internal AppRegistration(RegistryKey key, RegistryKey appKey, bool readOnly) : base(key, readOnly) { this.appKey = new AppSubKey(appKey, readOnly); SupportedTypes = new RegBasedKeyCollection(appKey.OpenSubKey("SupportedTypes", !readOnly), readOnly); Verbs = new CommandVerbDictionary(this.appKey, ReadOnly); } /// /// Gets or sets a value indicating that your application can accept a URL (instead of a file name) on the command line. /// Applications that can open documents directly from the internet, like web browsers and media players, should set this entry. /// /// When the ShellExecuteEx function starts an application and UseUrl is false, ShellExecuteEx downloads the document to a local /// file and invokes the handler on the local copy. /// /// /// For example, if the application has this entry set and a user right-clicks on a file stored on a web server, the Open verb will /// be made available.If not, the user will have to download the file and open the local copy. /// /// /// In Windows Vista and earlier, this entry indicated that the URL should be passed to the application along with a local file /// name, when called via ShellExecuteEx. In Windows 7, it indicates that the application can understand any http or https url that /// is passed to it, without having to supply the cache file name as well. /// /// /// if the application accepts a URL on the command-line; otherwise, . public bool AcceptsUrls { get => (uint)key.GetValue("UseUrl", 0) != 0; set => UpdateValue("UseUrl", value ? 1U : 0U, RegistryValueKind.DWord); } /// /// Gets or sets a value that enables an application to provide a specific icon to represent the application instead of the first /// icon stored in the .exe file. /// /// The default icon. public IconLocation DefaultIcon { get => IconLocation.TryParse(appKey.key.GetSubKeyDefaultValue("DefaultIcon")?.ToString(), out var loc) ? loc : null; set => appKey.UpdateKeyValue("DefaultIcon", value?.ToString()); } /// /// Gets or sets a value indicating whether debugger applications should avoid file dialog deadlocks when debugging the Windows /// Explorer process. Setting DontUseDesktopChangeRouter to produces a slightly less efficient handling of /// the change notifications, however. /// /// if [dont use desktop change router]; otherwise, . public bool DontUseDesktopChangeRouter { get => (uint)key.GetValue("DontUseDesktopChangeRouter", 0) != 0; set => UpdateValue("DontUseDesktopChangeRouter", value ? 1U : 0U, RegistryValueKind.DWord); } /// /// Gets or sets the CLSID of an object (usually a local server rather than an in-process server) that implements IDropTarget. By /// default, when the drop target is an executable file, and no DropTarget value is provided, the Shell converts the list of dropped /// files into a command-line parameter and passes it to ShellExecuteEx through lpParameters. /// /// The drop target's CLSID. public Guid? DropTarget { get => key.GetGuidValue("DropTarget"); set => UpdateValue("DropTarget", value.HasValue ? value.Value.ToString("B") : null); } /// /// Gets or sets the localizable name to display for an application instead of just the version information appearing, which may not /// be localizable. The association query ASSOCSTR reads this registry entry value and falls back to use the FileDescription name in /// the version information. If that name is missing, the association query defaults to the display name of the file. Applications /// should use ASSOCSTR_FRIENDLYAPPNAME to retrieve this information to obtain the proper behavior. /// /// The friendly name of the application. public IndirectString FriendlyAppName { get => IndirectString.TryParse(appKey.key.GetValue("FriendlyAppName")?.ToString(), out var loc) ? loc : null; set => appKey.UpdateValue("FriendlyAppName", value?.ToString()); } /// /// Gets or sets the fully qualified path to the application. The application name provided can be stated with or without its .exe /// extension. If necessary, the ShellExecuteEx function adds the extension when searching App Paths subkey. /// /// The full path of the application. /// /// The executable name cannot be changed once set. Only it's directory can be changed. /// public string FullPath { get => key.GetValue(null)?.ToString(); set { if (!string.Equals(Path.GetFileName(key.Name), Path.GetFileName(value), StringComparison.CurrentCultureIgnoreCase)) throw new InvalidOperationException("The executable name cannot be changed once set. Only it's directory can be changed."); UpdateValue(null, value); UpdateValue("Path", Path.GetDirectoryName(value)); } } /// /// Gets or sets a value indicating that the process is a host process, such as Rundll32.exe or Dllhost.exe, and should not be /// considered for Start menu pinning or inclusion in the Most Frequently Used (MFU) list. When launched with a shortcut that /// contains a non-null argument list or an explicit Application User Model IDs (AppUserModelIDs), the process can be pinned (as /// that shortcut). Such shortcuts are candidates for inclusion in the MFU list. /// /// if this process is a host process; otherwise, . public bool IsHostApp { get => appKey.key.HasValue("IsHostApp"); set => appKey.UpdateValue("IsHostApp", value ? string.Empty : null, RegistryValueKind.String); } /// /// Gets or sets a value that indicates that no application is specified for opening this file type. Be aware that if an /// OpenWithProgIDs subkey has been set for an application by file type, and the ProgID subkey itself does not also have a /// NoOpenWith entry, that application will appear in the list of recommended or available applications even if it has specified the /// NoOpenWith entry. For more information, see How to Include an Application in the Open With Dialog Box and How to exclude an /// Application from the Open with Dialog Box. /// /// if no application is specified for opening this file type; otherwise, . public bool NoOpenWith { get => appKey.key.HasValue("NoOpenWith"); set => appKey.UpdateValue("NoOpenWith", value ? string.Empty : null, RegistryValueKind.String); } /// /// Gets or sets a value indicating that the application executable and shortcuts should be excluded from the Start menu and from /// pinning or inclusion in the MFU list. This entry is typically used to exclude system tools, installers and uninstallers, and /// readme files. /// /// /// if this app should be excluded from the Start menu and from pinning or inclusion in the MFU list; /// otherwise, . /// public bool NoStartPage { get => appKey.key.HasValue("NoStartPage"); set => appKey.UpdateValue("NoStartPage", value ? string.Empty : null, RegistryValueKind.String); } /// /// Gets or sets a string that contains the URL protocol schemes for a given key. This can contain multiple values to indicate which /// schemes are supported. This string follows the format of scheme1:scheme2. If this list is not empty, file: will be /// added to the string. This protocol is implicitly supported when SupportedProtocols is defined. /// /// The supported protocols. public string SupportedProtocols { get => key.GetValue("SupportedProtocols")?.ToString(); set => UpdateValue("SupportedProtocols", value); } /// /// Gets or sets the file types that the application supports. Doing so enables the application to be listed in the cascade menu of /// the Open with dialog box. /// /// The supported files types. public ICollection SupportedTypes { get; } /// /// Gets or sets the icon used to override the taskbar icon. The window icon is normally used for the taskbar. Setting the /// TaskbarGroupIcon entry causes the system to use the icon from the .exe for the application instead. /// /// The taskbar icon override. public IconLocation TaskbarGroupIcon { get => IconLocation.TryParse(appKey.key.GetValue("TaskbarGroupIcon", null)?.ToString(), out var loc) ? loc : null; set => appKey.UpdateValue("DefaultIcon", value?.ToString()); } /// /// Gets or sets a value that determines if the taskbar should use the default icon of this executable if there is no pinnable /// shortcut for this application, and instead of the icon of the window that was first encountered. /// /// if the taskbar should use the default icon of this executable; otherwise, . public bool UseExecutableForTaskbarGroupIcon { get => appKey.key.HasValue("UseExecutableForTaskbarGroupIcon"); set => appKey.UpdateValue("UseExecutableForTaskbarGroupIcon", value ? string.Empty : null, RegistryValueKind.String); } /// /// Gets the verbs for calling the application from OpenWith. Without a verb definition specified here, the system assumes that the /// application supports CreateProcess, and passes the file name on the command line. This functionality applies to all the verb /// methods, including DropTarget, ExecuteCommand, and Dynamic Data Exchange (DDE). /// /// The command verbs associated with the app. public CommandVerbDictionary Verbs { get; } /// Opens the application registration information for the specified application. /// The full path of the application executable for which to get settings. /// /// If , register the application system-wide. If , register the application for the /// current user only. /// /// /// If , provides read-only access to the registration; If , the properties can be set /// to update the registration values. /// /// A instance. /// fullApplicationPath /// Unable to create application key in the 'App Paths' subkey. public static AppRegistration Open(string fullApplicationPath, bool systemWide = false, bool readOnly = true) { if (string.IsNullOrEmpty(fullApplicationPath)) throw new ArgumentNullException(nameof(fullApplicationPath)); var fn = Path.GetFileName(fullApplicationPath).ToLower(); // Handle registrations in user or machine "App Paths" var sk = (systemWide ? Registry.LocalMachine : Registry.CurrentUser).OpenSubKey(Path.Combine(appPathsSubKey, fn), !readOnly); if (sk is null) throw new InvalidOperationException("Unable to create application key in the 'App Paths' subkey."); var ska = ShellRegistrar.GetRoot(systemWide, !readOnly, Path.Combine(appsSubKey, fn)); if (ska is null) throw new InvalidOperationException("Unable to create application key in the 'Applications' subkey."); return new AppRegistration(sk, ska, readOnly); } /// Registers the application on Windows 7 or later. /// The full path of the application executable for which to get settings. /// /// If , register the application system-wide. If , register the application for the /// current user only. /// /// A instance to continue definition of application settings. public static AppRegistration Register(string fullApplicationPath, bool systemWide = false) { if (string.IsNullOrEmpty(fullApplicationPath)) throw new ArgumentNullException(nameof(fullApplicationPath)); fullApplicationPath = Path.GetFullPath(fullApplicationPath); var fn = Path.GetFileName(fullApplicationPath).ToLower(); // Handle registrations in user or machine "App Paths" var sk = (systemWide ? Registry.LocalMachine : Registry.CurrentUser).CreateSubKey(Path.Combine(appPathsSubKey, fn)); if (sk is null) throw new InvalidOperationException("Unable to create application key in the 'App Paths' subkey."); // Build short path and store as default value var shortPath = fullApplicationPath; var l = fullApplicationPath.Length + 5; var sb = new StringBuilder(l, l); var rl = PInvoke.Kernel32.GetShortPathName(fullApplicationPath.Length > PInvoke.Kernel32.MAX_PATH ? @"\\?\" + fullApplicationPath : fullApplicationPath, sb, (uint)sb.Capacity); if (rl > 0 && rl <= l) shortPath = sb.ToString(); sk.SetValue(null, shortPath); // Add Path value sk.SetValue("Path", Path.GetDirectoryName(fullApplicationPath)); // Handle registrations in HKCR\Applications var ska = ShellRegistrar.GetRoot(systemWide, true, Path.Combine(appsSubKey, fn)); if (ska is null) throw new InvalidOperationException("Unable to create application key in the HKCR\\Applications subkey."); return new AppRegistration(sk, ska, false); } /// Unregisters the application on Windows 7 or later. /// The full path of the application executable to unregister. /// /// If , unregister the application system-wide. If , unregister the application for /// the current user only. /// public static void Unregister(string fullApplicationPath, bool systemWide = false) { if (string.IsNullOrEmpty(fullApplicationPath)) throw new ArgumentNullException(nameof(fullApplicationPath)); var fn = Path.GetFileName(fullApplicationPath).ToLower(); using (var reg = (systemWide ? Registry.LocalMachine : Registry.CurrentUser).OpenSubKey(appPathsSubKey, true)) reg?.DeleteSubKeyTree(fn); using (var reg = ShellRegistrar.GetRoot(systemWide, true, appsSubKey)) reg?.DeleteSubKeyTree(fn); ShellRegistrar.NotifyShell(); } /// public override void Dispose() { appKey?.Dispose(); base.Dispose(); } private class AppSubKey : RegBasedSettings { public AppSubKey(RegistryKey key, bool readOnly) : base(key, readOnly) { } } } }