2018-08-29 14:59:20 -04:00
using Microsoft.Win32 ;
using System ;
2020-02-26 17:18:06 -05:00
using System.Collections ;
2018-08-29 14:59:20 -04:00
using System.Collections.Generic ;
2020-02-26 17:18:06 -05:00
using System.IO ;
using System.Linq ;
using Vanara.Windows.Shell.Registration ;
2018-08-29 14:59:20 -04:00
using static Vanara . PInvoke . ShlwApi ;
namespace Vanara.Windows.Shell
{
/// <summary>Represents a programmatic identifier in the registry for an application.</summary>
2020-02-26 17:18:06 -05:00
/// <example>
/// <code title="Register a ProgId with a verb and associated extension.">
///using (var progId = ProgId.Register("Company.Product.1", "My first product", systemWide))
///{
/// progId.Verbs.Add("open", "Open", Application.ExecutablePath);
/// progId.FileTypeAssociations.Add(".txt");
///}
/// </code>
/// </example>
2018-08-29 14:59:20 -04:00
/// <seealso cref="System.IDisposable"/>
public class ProgId : RegBasedSettings
{
2020-02-26 17:18:06 -05:00
private const string OpenWithProgIds = "OpenWithProgIds" ;
2018-08-29 14:59:20 -04:00
internal ProgId ( string progId , RegistryKey pkey , bool readOnly ) : base ( pkey , readOnly )
{
ID = progId ;
2020-02-26 17:18:06 -05:00
FileTypeAssociations = new FileTypeCollection ( progId , readOnly , IsSystemWide ) ;
2018-08-29 14:59:20 -04:00
Verbs = new CommandVerbDictionary ( this , readOnly ) ;
}
/// <summary>
/// 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.
/// </summary>
public bool AllowSilentDefaultTakeOver
{
get = > key . HasSubKey ( "AllowSilentDefaultTakeOver" ) ;
set = > ToggleKeyValue ( "AllowSilentDefaultTakeOver" , value ) ;
}
/// <summary>Overrides one of the folder options that hides the extension of known file types.</summary>
public bool AlwaysShowExt
{
get = > key . HasValue ( "AlwaysShowExt" ) ;
set = > ToggleValue ( "AlwaysShowExt" , value ) ;
}
/// <summary>
2020-02-26 17:18:06 -05:00
/// 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.
2018-08-29 14:59:20 -04:00
/// </summary>
public string AppUserModelID
{
get = > key . GetValue ( "AppUserModelID" ) ? . ToString ( ) ;
set = > UpdateValue ( "AppUserModelID" , value ) ;
}
/// <summary>Gets or sets the CLSID of the COM server associated with this ProgId.</summary>
public Guid ? CLSID
{
get { var s = key . GetSubKeyDefaultValue ( "CLSID" ) ? . ToString ( ) ; return s = = null ? ( Guid ? ) null : new Guid ( s ) ; }
2019-01-27 17:42:41 -05:00
set = > UpdateKeyValue ( "CLSID" , value ? . ToRegString ( ) ) ;
2018-08-29 14:59:20 -04:00
}
/// <summary>Gets or sets the versioned ProgId for this instance.</summary>
2020-02-26 17:18:06 -05:00
public string CurVer
2018-08-29 14:59:20 -04:00
{
2020-02-26 17:18:06 -05:00
get = > key . GetSubKeyDefaultValue ( "CurVer" ) ? . ToString ( ) ;
set = > UpdateKeyValue ( "CurVer" , value ) ;
2018-08-29 14:59:20 -04:00
}
/// <summary>Gets the default icon to display for file types associated with this ProgID.</summary>
public IconLocation DefaultIcon
{
get = > IconLocation . TryParse ( key . GetSubKeyDefaultValue ( "DefaultIcon" ) ? . ToString ( ) , out var loc ) ? loc : null ;
set = > UpdateKeyValue ( "DefaultIcon" , value ? . ToString ( ) ) ;
}
/// <summary>
/// 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.
/// </summary>
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 ) ;
}
/// <summary>Gets or sets the list of properties to show in the listview on extended tiles.</summary>
public PropertyDescriptionList ExtendedTileInfo
{
get = > GetPDL ( key , "ExtendedTileInfo" ) ;
set = > UpdateValue ( "ExtendedTileInfo" , value ? . ToString ( ) ) ;
}
2020-02-26 17:18:06 -05:00
/// <summary>A collection of extensions with which this ProgId is associated.</summary>
public ICollection < string > FileTypeAssociations { get ; }
2018-08-29 14:59:20 -04:00
/// <summary>
/// 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.
/// </summary>
public string FriendlyName
{
get = > key . GetValue ( null ) ? . ToString ( ) ;
set = > UpdateValue ( null , value ? ? "" ) ;
}
/// <summary>Gets the friendly name for the ProgID, suitable to display to the user.</summary>
public IndirectString FriendlyTypeName
{
get = > IndirectString . TryParse ( key . GetValue ( "FriendlyTypeName" ) ? . ToString ( ) , out var loc ) ? loc : null ;
set = > UpdateValue ( "FriendlyTypeName" , value ? . ToString ( ) ) ;
}
/// <summary>Gets or sets the list of all the properties to show in the details page.</summary>
public PropertyDescriptionList FullDetails
{
get = > GetPDL ( key , "FullDetails" ) ;
set = > UpdateValue ( "FullDetails" , value ? . ToString ( ) ) ;
}
/// <summary>Gets the programmatic identifier.</summary>
public string ID { get ; }
/// <summary>
/// Gets the brief help message that the Shell displays for this ProgID. This may be a string, a IndirectString, or a PropertyDescriptionList.
/// </summary>
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 ( ) ) ;
}
/// <summary>
/// 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.
/// </summary>
public bool IsShortcut
{
get = > key . HasValue ( "IsShortcut" ) ;
set = > ToggleValue ( "IsShortcut" , value ) ;
}
/// <summary>Gets or sets a value indicating that the extension is never to be shown regardless of folder options.</summary>
public bool NeverShowExt
{
get = > key . HasValue ( "NeverShowExt" ) ;
set = > ToggleValue ( "NeverShowExt" , value ) ;
}
/// <summary>
2020-02-26 17:18:06 -05:00
/// Specifies that the associated ProgId should not be opened by users. The value is presented as a warning to users. Use <see
/// cref="string.Empty"/> to use the system prompt.
2018-08-29 14:59:20 -04:00
/// </summary>
public string NoOpen
{
get = > key . GetValue ( "NoOpen" ) ? . ToString ( ) ;
set = > UpdateValue ( "NoOpen" , value ) ;
}
/// <summary>Gets or sets the list of properties to display in the preview pane.</summary>
public PropertyDescriptionList PreviewDetails
{
get = > GetPDL ( key , "PreviewDetails" ) ;
set = > UpdateValue ( "PreviewDetails" , value ? . ToString ( ) ) ;
}
/// <summary>Gets or sets the one or two properties to display in the preview pane title section.</summary>
public PropertyDescriptionList PreviewTitle
{
get = > GetPDL ( key , "PreviewTitle" ) ;
set = > UpdateValue ( "PreviewTitle" , value ? . ToString ( ) ) ;
}
/// <summary>Gets or sets the list of properties to show in the listview on tiles.</summary>
public PropertyDescriptionList TileInfo
{
get = > GetPDL ( key , "TileInfo" ) ;
set = > UpdateValue ( "TileInfo" , value ? . ToString ( ) ) ;
}
/// <summary>Gets the command verbs associated with this ProgID.</summary>
public CommandVerbDictionary Verbs { get ; }
2020-02-26 17:18:06 -05:00
/// <summary>Initializes a new instance of the <see cref="ProgId"/> class.</summary>
/// <param name="progId">The programmatic identifier string.</param>
/// <param name="readOnly">
/// If <see langword="true"/>, provides read-only access to the registration; If <see langword="false"/>, the properties can be set
/// to update the registration values.
/// </param>
/// <param name="autoLoadVersioned">
/// if set to <c>true</c> automatically load a referenced versioned ProgId instead of the specified ProgId.
/// </param>
/// <param name="systemWide">
/// If <see langword="true"/>, open the ProgId for system-wide use. If <see langword="false"/>, open the ProgId for the current user only.
/// </param>
/// <returns>The requested <see cref="ProgId"/> instance.</returns>
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 ) ;
}
/// <summary>Registers the programmatic identifier (ProgId).</summary>
/// <param name="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.
/// </param>
/// <param name="friendlyName">
/// 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.
/// </param>
/// <param name="systemWide">
/// If <see langword="true"/>, register the ProgId system-wide. If <see langword="false"/>, register the ProgId for the current user only.
/// </param>
/// <returns>A <see cref="ProgId"/> instance to continue definition of ProgId settings.</returns>
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 ) ;
}
/// <summary>Unregisters the ProgID.</summary>
/// <param name="progId">The key for the ProgID. The function will succeed even if this value does not exists.</param>
/// <param name="withFileExt">If set to <see langword="true"/>, also remove all associated registered file extensions.</param>
/// <param name="systemWide">
/// If <see langword="true"/>, register the ProgId system-wide. If <see langword="false"/>, register the ProgId for the current user only.
/// </param>
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 ( ) ;
}
/// <inheritdoc/>
public override void Dispose ( )
{
base . Dispose ( ) ;
ShellRegistrar . NotifyShell ( ) ;
}
internal static IEnumerable < string > GetAssociatedFileExtensions ( string progId , bool systemWide = false )
{
using var root = ShellRegistrar . GetRoot ( systemWide , false ) ;
foreach ( var ext in root . GetSubKeyNames ( ) . Where ( ext = > ext . StartsWith ( "." ) ) )
{
2020-07-26 15:12:33 -04:00
var hasValue = false ;
using ( var openWithKey = root . OpenSubKey ( Path . Combine ( ext , OpenWithProgIds ) ) )
hasValue = openWithKey ? . HasValue ( progId ) ? ? false ;
if ( hasValue )
2020-02-26 17:18:06 -05:00
yield return ext ;
}
}
2018-08-29 14:59:20 -04:00
private static PropertyDescriptionList GetPDL ( RegistryKey key , string valueName )
{
var pdl = key . GetValue ( valueName ) ? . ToString ( ) ;
return pdl = = null ? null : new PropertyDescriptionList ( pdl ) ;
}
2020-02-26 17:18:06 -05:00
}
/// <summary>A virtual collection of file types associated with a ProgId in the Windows Registry.</summary>
class FileTypeCollection : ICollection < string >
{
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 ;
}
/// <summary>Gets the count.</summary>
/// <value>The count.</value>
public int Count = > Enum ( ) . Count ( ) ;
/// <summary>Gets or sets a value indicating whether these settings are read-only.</summary>
public bool IsReadOnly { get ; }
/// <summary>Gets or sets a value indicating whether these settings are read-only.</summary>
public bool IsSystemWide { get ; }
/// <summary>Adds the specified item.</summary>
/// <param name="ext">The extension to associate.</param>
public void Add ( string ext )
{
EnsureWritable ( ) ;
2020-07-26 15:12:33 -04:00
using var assoc = FileTypeAssociation . Register ( ext , IsSystemWide ) ;
assoc . OpenWithProgIds . Add ( progId ) ;
2020-02-26 17:18:06 -05:00
}
/// <summary>Clears this instance.</summary>
public void Clear ( ) = > throw new NotSupportedException ( ) ;
/// <summary>Determines whether this instance contains the object.</summary>
/// <param name="item">The item.</param>
/// <returns><see langword="true"/> if [contains] [the specified item]; otherwise, <see langword="false"/>.</returns>
public bool Contains ( string item ) = > ShellRegistrar . GetRoot ( IsSystemWide , false , Path . Combine ( item , "OpenWithProgIds" ) ) . HasValue ( progId ) ;
/// <summary>Copies to.</summary>
/// <param name="array">The array.</param>
/// <param name="arrayIndex">Index of the array.</param>
public void CopyTo ( string [ ] array , int arrayIndex ) = > Array . Copy ( Enum ( ) . ToArray ( ) , 0 , array , arrayIndex , Count ) ;
/// <summary>Gets the enumerator.</summary>
/// <returns></returns>
public IEnumerator < string > GetEnumerator ( ) = > Enum ( ) . GetEnumerator ( ) ;
/// <summary>Removes the specified item.</summary>
/// <param name="item">The item.</param>
/// <returns></returns>
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 ; }
}
/// <summary>Gets the enumerator.</summary>
/// <returns></returns>
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ( ) = > GetEnumerator ( ) ;
/// <summary>Checks the ReadOnly flag and throws an exception if it is true.</summary>
protected void EnsureWritable ( ) { if ( IsReadOnly ) throw new NotSupportedException ( "The collection is read only." ) ; }
2018-08-29 14:59:20 -04:00
2020-02-26 17:18:06 -05:00
private IEnumerable < string > Enum ( ) = > ProgId . GetAssociatedFileExtensions ( progId , IsSystemWide ) ;
2018-08-29 14:59:20 -04:00
}
}