using Microsoft.Win32; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using Vanara.Windows.Shell.Registration; using static Vanara.PInvoke.ShlwApi; namespace Vanara.Windows.Shell { /// Represents a programmatic identifier in the registry for an application. /// /// ///using (var progId = ProgId.Register("Company.Product.1", "My first product", systemWide)) ///{ /// progId.Verbs.Add("open", "Open", Application.ExecutablePath); /// progId.FileTypeAssociations.Add(".txt"); ///} /// /// /// public class ProgId : RegBasedSettings { private const string OpenWithProgIds = "OpenWithProgIds"; internal ProgId(string progId, RegistryKey pkey, bool readOnly) : base(pkey, readOnly) { ID = progId; FileTypeAssociations = new FileTypeCollection(progId, readOnly, IsSystemWide); Verbs = new CommandVerbDictionary(this, readOnly); } /// /// Gets a value indicating whether to signal that Windows should ignore this ProgID when determining a default handler for a public /// file type. Regardless of whether this value is set, the ProgID continues to appear in the OpenWith shortcut menu and dialog. /// public bool AllowSilentDefaultTakeOver { get => key.HasSubKey("AllowSilentDefaultTakeOver"); set => ToggleKeyValue("AllowSilentDefaultTakeOver", value); } /// Overrides one of the folder options that hides the extension of known file types. public bool AlwaysShowExt { get => key.HasValue("AlwaysShowExt"); set => ToggleValue("AlwaysShowExt", value); } /// /// Gets the application's explicit Application User Model ID (AppUserModelID) if the application uses an explicit AppUserModelID /// and uses either the system's automatically generated Recent or Frequent Jump Lists or provides a custom Jump List. If an /// application uses an explicit AppUserModelID and does not set this value, items will not appear in that application's Jump Lists. /// public string AppUserModelID { get => key.GetValue("AppUserModelID")?.ToString(); set => UpdateValue("AppUserModelID", value); } /// Gets or sets the CLSID of the COM server associated with this ProgId. public Guid? CLSID { get { var s = key.GetSubKeyDefaultValue("CLSID")?.ToString(); return s == null ? (Guid?)null : new Guid(s); } set => UpdateKeyValue("CLSID", value?.ToRegString()); } /// Gets or sets the versioned ProgId for this instance. public string CurVer { get => key.GetSubKeyDefaultValue("CurVer")?.ToString(); set => UpdateKeyValue("CurVer", value); } /// Gets the default icon to display for file types associated with this ProgID. public IconLocation DefaultIcon { get => IconLocation.TryParse(key.GetSubKeyDefaultValue("DefaultIcon")?.ToString(), out var loc) ? loc : null; set => UpdateKeyValue("DefaultIcon", value?.ToString()); } /// /// Gets flags that control some aspects of the Shell's handling of the file types linked to this ProgID. EditFlags may also limit /// how much the user can modify certain aspects of these file types using a file's property sheet. /// public FILETYPEATTRIBUTEFLAGS EditFlags { get { var val = key.GetValue("EditFlags", 0); if (val is byte[] b) val = BitConverter.ToInt32(b, 0); if (val is int i) return (FILETYPEATTRIBUTEFLAGS)i; throw new InvalidOperationException("Unable to retrieve EditFlags value."); } set => UpdateValue("EditFlags", (uint)value, RegistryValueKind.DWord, (uint)FILETYPEATTRIBUTEFLAGS.FTA_None); } /// Gets or sets the list of properties to show in the listview on extended tiles. public PropertyDescriptionList ExtendedTileInfo { get => GetPDL(key, "ExtendedTileInfo"); set => UpdateValue("ExtendedTileInfo", value?.ToString()); } /// A collection of extensions with which this ProgId is associated. public ICollection FileTypeAssociations { get; } /// /// Gets a friendly name for that ProgID, suitable to display to the user. The use of this entry to hold the friendly name is /// deprecated by the FriendlyTypeName entry on systems running Windows 2000 or later. However, it may be set for backward compatibility. /// public string FriendlyName { get => key.GetValue(null)?.ToString(); set => UpdateValue(null, value ?? ""); } /// Gets the friendly name for the ProgID, suitable to display to the user. public IndirectString FriendlyTypeName { get => IndirectString.TryParse(key.GetValue("FriendlyTypeName")?.ToString(), out var loc) ? loc : null; set => UpdateValue("FriendlyTypeName", value?.ToString()); } /// Gets or sets the list of all the properties to show in the details page. public PropertyDescriptionList FullDetails { get => GetPDL(key, "FullDetails"); set => UpdateValue("FullDetails", value?.ToString()); } /// Gets the programmatic identifier. public string ID { get; } /// /// Gets the brief help message that the Shell displays for this ProgID. This may be a string, a IndirectString, or a PropertyDescriptionList. /// public object InfoTip { get { var val = key.GetValue("InfoTip")?.ToString(); if (val == null) return null; if (val.StartsWith("@")) return IndirectString.TryParse(val, out var loc) ? loc : null; if (val.StartsWith("prop:")) return new PropertyDescriptionList(val); return val; } set => UpdateValue("InfoTip", value?.ToString()); } /// /// Allows an application to register a file name extension as a shortcut file type. If a file has a file name extension that has /// been registered as a shortcut file type, the system automatically adds the system-defined link overlay icon (a small arrow) to /// the file's icon. /// public bool IsShortcut { get => key.HasValue("IsShortcut"); set => ToggleValue("IsShortcut", value); } /// Gets or sets a value indicating that the extension is never to be shown regardless of folder options. public bool NeverShowExt { get => key.HasValue("NeverShowExt"); set => ToggleValue("NeverShowExt", value); } /// /// Specifies that the associated ProgId should not be opened by users. The value is presented as a warning to users. Use to use the system prompt. /// public string NoOpen { get => key.GetValue("NoOpen")?.ToString(); set => UpdateValue("NoOpen", value); } /// Gets or sets the list of properties to display in the preview pane. public PropertyDescriptionList PreviewDetails { get => GetPDL(key, "PreviewDetails"); set => UpdateValue("PreviewDetails", value?.ToString()); } /// Gets or sets the one or two properties to display in the preview pane title section. public PropertyDescriptionList PreviewTitle { get => GetPDL(key, "PreviewTitle"); set => UpdateValue("PreviewTitle", value?.ToString()); } /// Gets or sets the list of properties to show in the listview on tiles. public PropertyDescriptionList TileInfo { get => GetPDL(key, "TileInfo"); set => UpdateValue("TileInfo", value?.ToString()); } /// Gets the command verbs associated with this ProgID. public CommandVerbDictionary Verbs { get; } /// Initializes a new instance of the class. /// The programmatic identifier string. /// /// If , provides read-only access to the registration; If , the properties can be set /// to update the registration values. /// /// /// if set to true automatically load a referenced versioned ProgId instead of the specified ProgId. /// /// /// If , open the ProgId for system-wide use. If , open the ProgId for the current user only. /// /// The requested instance. public static ProgId Open(string progId, bool readOnly = true, bool autoLoadVersioned = true, bool systemWide = false) { var key = ShellRegistrar.GetRoot(systemWide, !readOnly, progId ?? throw new ArgumentNullException(nameof(progId))); if (autoLoadVersioned) { var cv = key?.GetSubKeyDefaultValue("CurVer")?.ToString(); if (cv != null && cv != progId) { key.Close(); key = ShellRegistrar.GetRoot(systemWide, !readOnly, cv); } } if (key is null) throw new ArgumentException("Unable to load specified ProgId", nameof(progId)); return new ProgId(progId, key, readOnly); } /// Registers the programmatic identifier (ProgId). /// /// The key name for the ProgId. The proper format of a ProgID key name is [Vendor or Application].[Component].[Version], separated /// by periods and with no spaces, as in Word.Document.6. The Version portion is optional but strongly recommended. /// /// /// The friendly name for this ProgID, suitable to display to the user. The use of this entry to hold the friendly name is /// overridden by the FriendlyTypeName entry on systems running Windows 2000 or later. /// /// /// If , register the ProgId system-wide. If , register the ProgId for the current user only. /// /// A instance to continue definition of ProgId settings. public static ProgId Register(string progId, string friendlyName, bool systemWide = false) { if (progId == null) throw new ArgumentNullException(nameof(progId)); if (progId.Length > 39 || !System.Text.RegularExpressions.Regex.IsMatch(progId, @"^[a-zA-Z][\w\.]+$", System.Text.RegularExpressions.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."); using var root = ShellRegistrar.GetRoot(systemWide, true); return new ProgId(progId, root.CreateSubKey(progId, friendlyName), false); } /// Unregisters the ProgID. /// The key for the ProgID. The function will succeed even if this value does not exists. /// If set to , also remove all associated registered file extensions. /// /// If , register the ProgId system-wide. If , register the ProgId for the current user only. /// public static void Unregister(string progId, bool withFileExt = true, bool systemWide = false) { if (progId is null) return; using var reg = ShellRegistrar.GetRoot(systemWide, true); if (withFileExt) { foreach (var ext in GetAssociatedFileExtensions(progId, systemWide)) { using var ftype = FileTypeAssociation.Open(ext, systemWide, false); if (ftype.DefaultProgId == progId) ftype.DefaultProgId = null; ftype.OpenWithProgIds.Remove(progId); } } try { reg.DeleteSubKeyTree(progId); } catch { reg.DeleteSubKey(progId, false); } ShellRegistrar.NotifyShell(); } /// public override void Dispose() { base.Dispose(); ShellRegistrar.NotifyShell(); } internal static IEnumerable GetAssociatedFileExtensions(string progId, bool systemWide = false) { using var root = ShellRegistrar.GetRoot(systemWide, false); foreach (var ext in root.GetSubKeyNames().Where(ext => ext.StartsWith("."))) { var hasValue = false; using (var openWithKey = root.OpenSubKey(Path.Combine(ext, OpenWithProgIds))) hasValue = openWithKey?.HasValue(progId) ?? false; if (hasValue) yield return ext; } } private static PropertyDescriptionList GetPDL(RegistryKey key, string valueName) { var pdl = key.GetValue(valueName)?.ToString(); return pdl == null ? null : new PropertyDescriptionList(pdl); } } /// A virtual collection of file types associated with a ProgId in the Windows Registry. class FileTypeCollection : ICollection { protected internal string progId; protected internal FileTypeCollection(string progId, bool readOnly, bool systemWide) { this.progId = progId ?? throw new ArgumentNullException(nameof(progId)); IsReadOnly = readOnly; IsSystemWide = systemWide; } /// Gets the count. /// The count. public int Count => Enum().Count(); /// Gets or sets a value indicating whether these settings are read-only. public bool IsReadOnly { get; } /// Gets or sets a value indicating whether these settings are read-only. public bool IsSystemWide { get; } /// Adds the specified item. /// The extension to associate. public void Add(string ext) { EnsureWritable(); using var assoc = FileTypeAssociation.Register(ext, IsSystemWide); assoc.OpenWithProgIds.Add(progId); } /// Clears this instance. public void Clear() => throw new NotSupportedException(); /// Determines whether this instance contains the object. /// The item. /// if [contains] [the specified item]; otherwise, . public bool Contains(string item) => ShellRegistrar.GetRoot(IsSystemWide, false, Path.Combine(item, "OpenWithProgIds")).HasValue(progId); /// Copies to. /// The array. /// Index of the array. public void CopyTo(string[] array, int arrayIndex) => Array.Copy(Enum().ToArray(), 0, array, arrayIndex, Count); /// Gets the enumerator. /// public IEnumerator GetEnumerator() => Enum().GetEnumerator(); /// Removes the specified item. /// The item. /// public bool Remove(string item) { EnsureWritable(); using var openWithKey = ShellRegistrar.GetRoot(IsSystemWide, true, Path.Combine(item, "OpenWithProgIds")); try { openWithKey.DeleteValue(progId, true); return true; } catch { return false; } } /// Gets the enumerator. /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); /// Checks the ReadOnly flag and throws an exception if it is true. protected void EnsureWritable() { if (IsReadOnly) throw new NotSupportedException("The collection is read only."); } private IEnumerable Enum() => ProgId.GetAssociatedFileExtensions(progId, IsSystemWide); } }