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);
}
}