Added classes to register and interact with the registry for shell applications, ProgIDs and file associations

pull/21/head
David Hall 2018-08-29 12:59:20 -06:00
parent 6237a60997
commit ff50e8d23d
13 changed files with 1554 additions and 22 deletions

View File

@ -0,0 +1,329 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace Vanara.Collections.Generic.Tests
{
[TestFixture()]
public class HashSetTests
{
[Test]
public void CtorTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.Count, Is.EqualTo(20));
Assert.That(s1.Comparer.Equals("S", "s"), Is.False);
var s3 = new HashSet<string>(NumGen(0, 2, 20), StringComparer.CurrentCultureIgnoreCase);
Assert.That(s3.Count, Is.EqualTo(20));
Assert.That(s3.Comparer.Equals("S", "s"), Is.True);
var s5 = new HashSet<string>();
Assert.That(s5.Count, Is.EqualTo(0));
Assert.That(s5.Comparer.Equals("S", "s"), Is.False);
var s6 = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
Assert.That(s6.Count, Is.EqualTo(0));
Assert.That(s6.Comparer.Equals("S", "s"), Is.True);
// Test dup values
var s7 = new HashSet<string>(NumGen(3, 0, 10));
Assert.That(s7.Count, Is.EqualTo(1));
Assert.That(s7.First(), Is.EqualTo("3"));
}
[Test]
public void ClearTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.Count, Is.EqualTo(20));
s1.Clear();
Assert.That(s1.Count, Is.EqualTo(0));
}
[Test]
public void ContainsTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.Contains("2"), Is.True);
Assert.That(s1.Contains("21"), Is.False);
}
[Test]
public void CopyToTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
var sa = new string[20];
s1.CopyTo(sa, 0);
Assert.That(sa.All(s => s != null), Is.True);
Assert.That(sa[1], Is.EqualTo("2"));
Assert.That(() => s1.CopyTo(sa, 10), Throws.Exception);
sa = new string[10];
Assert.That(() => s1.CopyTo(sa, 0), Throws.Exception);
}
[Test]
public void ExceptWithTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(() => s1.ExceptWith(null), Throws.Exception);
s1.ExceptWith(NumGen(4, 2, 18));
Assert.That(s1.Count, Is.EqualTo(2));
Assert.That(s1.Contains("2"), Is.True);
s1.ExceptWith(s1);
Assert.That(s1.Count, Is.EqualTo(0));
s1.ExceptWith(NumGen(0, 2, 4));
Assert.That(s1.Count, Is.EqualTo(0));
}
[Test]
public void IntersectWithTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(() => s1.IntersectWith(null), Throws.Exception);
var other = new List<string>();
s1.IntersectWith(other);
Assert.That(s1.Count, Is.EqualTo(0));
s1 = new HashSet<string>(NumGen(0, 2, 20));
var e = NumGen(4, 2, 4);
s1.IntersectWith(e);
Assert.That(s1.Count, Is.EqualTo(4));
Assert.That(s1, Is.EquivalentTo(e));
s1.IntersectWith(new[] {"3"});
Assert.That(s1.Count, Is.EqualTo(0));
}
[Test]
public void IsProperSubsetOfTest()
{
var s1 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(() => s1.IsProperSubsetOf(null), Throws.Exception);
var s2 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.IsProperSubsetOf(s2), Is.True);
var s3 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(s1.IsProperSubsetOf(s3), Is.False);
var s4 = new HashSet<string>(NumGen(1, 2, 10));
Assert.That(s1.IsProperSubsetOf(s4), Is.False);
s1.Clear();
Assert.That(s1.IsProperSubsetOf(s2), Is.True);
s2.Clear();
Assert.That(s1.IsProperSubsetOf(s2), Is.False);
}
[Test]
public void IsProperSupersetOfTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(() => s1.IsProperSupersetOf(null), Throws.Exception);
var s2 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(s1.IsProperSupersetOf(s2), Is.True);
var s3 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.IsProperSupersetOf(s3), Is.False);
var s4 = new HashSet<string>(NumGen(1, 2, 10));
Assert.That(s1.IsProperSupersetOf(s4), Is.False);
s1.Clear();
Assert.That(s1.IsProperSupersetOf(s2), Is.False);
Assert.That(s3.IsProperSupersetOf(s1), Is.True);
}
[Test]
public void IsSubsetOfTest()
{
var s1 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(() => s1.IsSubsetOf(null), Throws.Exception);
var s2 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.IsSubsetOf(s2), Is.True);
var s3 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(s1.IsSubsetOf(s3), Is.True);
var s4 = new HashSet<string>(NumGen(1, 2, 10));
Assert.That(s1.IsSubsetOf(s4), Is.False);
s1.Clear();
Assert.That(s1.IsSubsetOf(s2), Is.True);
s2.Clear();
Assert.That(s1.IsSubsetOf(s2), Is.True);
}
[Test]
public void IsSupersetOfTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(() => s1.IsSupersetOf(null), Throws.Exception);
var s2 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(s1.IsSupersetOf(s2), Is.True);
var s3 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(s1.IsSupersetOf(s3), Is.True);
var s4 = new HashSet<string>(NumGen(1, 2, 10));
Assert.That(s1.IsSupersetOf(s4), Is.False);
s1.Clear();
Assert.That(s1.IsSupersetOf(s2), Is.False);
Assert.That(s3.IsSupersetOf(s1), Is.True);
}
[Test]
public void OverlapsTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 20));
Assert.That(() => s1.Overlaps(null), Throws.Exception);
var s2 = new HashSet<string>(NumGen(4, 2, 10));
Assert.That(s1.Overlaps(s2), Is.True);
Assert.That(s1.Overlaps(new[] {"0"}), Is.True);
Assert.That(s1.Overlaps(new[] {"38"}), Is.True);
Assert.That(s1.Overlaps(new[] {"40"}), Is.False);
Assert.That(s1.Overlaps(new[] {"0", "X", "."}), Is.True);
s1.Clear();
Assert.That(s1.Overlaps(s2), Is.False);
}
[Test]
public void RemoveTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 5));
Assert.That(s1.Remove("0"), Is.True);
Assert.That(s1.Remove("1"), Is.False);
Assert.That(s1.Remove(null), Is.False);
}
[Test]
public void SetEqualsTest()
{
var s1 = new HashSet<string>(NumGen(0, 2, 10));
Assert.That(() => s1.SetEquals(null), Throws.Exception);
Assert.That(s1.SetEquals(NumGen(0, 2, 10)), Is.True);
Assert.That(s1.SetEquals(NumGen(0, 2, 5)), Is.False);
Assert.That(s1.SetEquals(NumGen(1, 2, 2)), Is.False);
var s2 = new HashSet<string>(NumGen(0, 2, 10));
Assert.That(s1.SetEquals(s2), Is.True);
s2.Remove("0");
Assert.That(s1.SetEquals(s2), Is.False);
}
[Test]
public void SymmetricExceptTest()
{
var s1 = new HashSet<string>();
Assert.That(() => s1.SymmetricExceptWith(null), Throws.Exception);
var e1 = NumGen(0, 2, 5).ToArray();
s1.SymmetricExceptWith(e1);
Assert.That(s1, Is.EquivalentTo(e1));
s1.SymmetricExceptWith(s1);
Assert.That(s1.Count, Is.EqualTo(0));
s1 = new HashSet<string>(e1);
var s2 = new HashSet<string>(NumGen(10, 2, 5));
s1.SymmetricExceptWith(s2);
Assert.That(s1.Count, Is.EqualTo(10));
s1.SymmetricExceptWith(s2);
Assert.That(s1.Count, Is.EqualTo(5));
Assert.That(s1, Is.EquivalentTo(e1));
var s3 = new HashSet<string>(NumGen(6, 2, 5));
s1.SymmetricExceptWith(s3);
Assert.That(s1.Count, Is.EqualTo(6));
Assert.That(s1.Contains("4"), Is.True);
Assert.That(s1.Contains("6"), Is.False);
Assert.That(s1.Contains("10"), Is.True);
}
/*[Test]
public void TryGetValueTest()
{
var s1 = new HashSet<Tester>(new Tester.Comparer());
s1.Add("Test1");
s1.Add("Test2");
s1.Add("Test3");
var t2 = new Tester {Name = "test2", Value = 12};
Assert.That(s1.TryGetValue(t2, out var o2), Is.True);
Assert.That(o2.Value, Is.Zero);
}
public class Tester
{
public string Name { get; set; }
public int Value { get; set; }
public static implicit operator Tester(string value) => new Tester {Name = value};
public class Comparer : EqualityComparer<Tester>
{
public override bool Equals(Tester x, Tester y) => StringComparer.InvariantCultureIgnoreCase.Compare(x?.Name, y?.Name) == 0;
public override int GetHashCode(Tester obj) => obj?.Name.ToLowerInvariant().GetHashCode() ?? 0;
}
}*/
[Test]
public void UnionWithTest()
{
var s1 = new HashSet<string>();
Assert.That(() => s1.UnionWith(null), Throws.Exception);
var e1 = NumGen(0, 2, 5).ToArray();
s1.UnionWith(e1);
Assert.That(s1, Is.EquivalentTo(e1));
s1.UnionWith(s1);
Assert.That(s1, Is.EquivalentTo(e1));
s1.UnionWith(e1);
Assert.That(s1, Is.EquivalentTo(e1));
var s2 = new HashSet<string>(NumGen(10, 2, 5));
s1.UnionWith(s2);
Assert.That(s1.Count, Is.EqualTo(10));
s1.UnionWith(s2);
Assert.That(s1.Count, Is.EqualTo(10));
var s3 = new HashSet<string>(NumGen(16, 2, 5));
s1.UnionWith(s3);
Assert.That(s1.Count, Is.EqualTo(13));
}
private static IEnumerable<string> NumGen(int start, int incr, int count, string prefix = "")
{
var n = start;
for (var i = 0; i < count; i++, n += incr)
yield return $"{prefix}{n}";
}
}
}

View File

@ -172,7 +172,7 @@ namespace Vanara.PInvoke.Tests
public void PSGetNameFromPropertyKeyTest()
{
var pkey = new PROPERTYKEY {fmtid = new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), pid = 5};
var hr = PSGetNameFromPropertyKey(ref pkey, out var str);
var hr = PSGetNameFromPropertyKey(pkey, out var str);
Assert.That(hr.Succeeded);
Assert.That(str, Is.Not.Null);
TestContext.WriteLine(str);

View File

@ -0,0 +1,117 @@
using NUnit.Framework;
using System;
using static Vanara.PInvoke.Shell32;
namespace Vanara.Windows.Shell.Tests
{
[TestFixture]
public class ShellAssocTests
{
[Test]
public void ShellAssocTest()
{
var sha = ShellAssociation.FileAssociations[".xlsx"];
Assert.That(sha.DefaultIcon, Is.EqualTo(@"C:\WINDOWS\Installer\{90150000-0011-0000-0000-0000000FF1CE}\xlicons.exe,1"));
}
[Test]
public void ReadProgIDTest()
{
using (var pi = new ProgId("Word.Document.12"))
{
Assert.That(pi.ReadOnly, Is.True);
Assert.That(pi.DefaultIcon.ToString(), Is.EqualTo(@"C:\WINDOWS\Installer\{90150000-0011-0000-0000-0000000FF1CE}\wordicon.exe,13"));
Assert.That(pi.AllowSilentDefaultTakeOver, Is.False);
Assert.That(pi.AppUserModelID, Is.Null);
Assert.That(pi.EditFlags, Is.EqualTo(PInvoke.ShlwApi.FILETYPEATTRIBUTEFLAGS.FTA_None));
Assert.That(pi.Verbs, Has.Count.EqualTo(8));
Assert.That(pi.Verbs["Close"], Is.Null);
Assert.That(pi.Verbs["New"].DisplayName, Is.EqualTo("&New"));
}
using (var pi = new ProgId("Acrobat.Document.DC"))
{
Assert.That(pi.EditFlags, Is.EqualTo(PInvoke.ShlwApi.FILETYPEATTRIBUTEFLAGS.FTA_OpenIsSafe));
Assert.That(pi.CLSID, Is.EqualTo(new Guid("{B801CA65-A1FC-11D0-85AD-444553540000}")));
Assert.That(pi.Verbs["Print"].Command, Has.Length.GreaterThan(0));
}
using (var pi = new ProgId("CABFolder"))
{
Assert.That(pi.EditFlags, Is.EqualTo(PInvoke.ShlwApi.FILETYPEATTRIBUTEFLAGS.FTA_SafeForElevation));
Assert.That(pi.FriendlyTypeName.ToString(), Is.EqualTo(@"@C:\WINDOWS\system32\cabview.dll,-20"));
Assert.That(pi.FriendlyTypeName.Value, Has.Length.GreaterThan(0));
Assert.That(pi.InfoTip.ToString(), Is.EqualTo(@"@C:\WINDOWS\system32\cabview.dll,-21"));
Assert.That((pi.InfoTip as IndirectString)?.Value, Has.Length.GreaterThan(0));
}
using (var pi = new ProgId("cdafile"))
Assert.That(pi.Verbs, Has.Count.EqualTo(0));
using (var pi = new ProgId("Msi.Package"))
{
Assert.That(pi.Verbs, Has.Count.EqualTo(4));
Assert.That(pi.Verbs.Order, Has.Count.EqualTo(4));
Assert.That(pi.Verbs.Order[3].Name, Is.EqualTo("Uninstall"));
}
}
[Test]
public void WriteProgIDTest()
{
const string sProgId = "My.Crazy.1";
const string testStr = "Testing123";
ShellRegistrar.UnregisterProgID(sProgId);
using (var progid = ShellRegistrar.RegisterProgID(sProgId, "Testing Vanara ProgId"))
using (var reg = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(sProgId))
{
Assert.That(reg, Is.Not.Null);
Assert.That(reg.GetValue(null).ToString(), Is.EqualTo(progid.FriendlyName));
Assert.That(progid.ID, Is.EqualTo(sProgId));
Assert.That(progid.ReadOnly, Is.False);
progid.AllowSilentDefaultTakeOver = true;
Assert.That(progid.AllowSilentDefaultTakeOver, Is.True);
progid.AllowSilentDefaultTakeOver = false;
Assert.That(progid.AllowSilentDefaultTakeOver, Is.False);
progid.AppUserModelID = testStr;
Assert.That(progid.AppUserModelID, Is.EqualTo(testStr));
progid.AppUserModelID = null;
Assert.That(progid.AppUserModelID, Is.Null);
var g = Guid.NewGuid();
progid.CLSID = g;
Assert.That(progid.CLSID.Value, Is.EqualTo(g));
progid.CLSID = null;
Assert.That(progid.CLSID, Is.Null);
var i = new IconLocation(@"C:\Temp\dllexp.exe", -1);
progid.DefaultIcon = i;
Assert.That(progid.DefaultIcon.ToString(), Is.EqualTo(i.ToString()));
progid.DefaultIcon = null;
Assert.That(progid.DefaultIcon, Is.Null);
var f = PInvoke.ShlwApi.FILETYPEATTRIBUTEFLAGS.FTA_NoEditIcon | PInvoke.ShlwApi.FILETYPEATTRIBUTEFLAGS.FTA_NoEdit;
progid.EditFlags = f;
Assert.That(progid.EditFlags, Is.EqualTo(f));
progid.EditFlags = 0;
Assert.That(progid.EditFlags, Is.EqualTo(PInvoke.ShlwApi.FILETYPEATTRIBUTEFLAGS.FTA_None));
var fn = new IndirectString(@"C:\Temp\dllexp.exe", -1);
progid.FriendlyTypeName = fn;
Assert.That(progid.FriendlyTypeName.ToString(), Is.EqualTo(fn.ToString()));
progid.FriendlyTypeName = null;
Assert.That(progid.FriendlyTypeName, Is.Null);
progid.InfoTip = fn;
Assert.That(progid.InfoTip.ToString(), Is.EqualTo(fn.ToString()));
progid.InfoTip = null;
Assert.That(progid.InfoTip, Is.Null);
var vopen = progid.Verbs.Add("Open", "&Open", "notepad.exe %1");
var vprint = progid.Verbs.Add("Print", "&Print", "notepad.exe %1");
var vend = progid.Verbs.Add("Terminate", "&End", "notepad.exe %1");
progid.Verbs.Order = new[] {vend, vprint};
}
}
}
}

View File

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Vanara</RootNamespace>
<AssemblyName>UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<TestProjectType>UnitTest</TestProjectType>
@ -22,6 +22,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -30,6 +31,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=3.10.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
@ -42,6 +44,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Core\Collections\EventedListTests.cs" />
<Compile Include="Core\Collections\HashSet2.0Tests.cs" />
<Compile Include="Core\Collections\HierarchyTests.cs" />
<Compile Include="Core\Collections\SparseArrayTests.cs" />
<Compile Include="Core\Extensions\EnumExtensionsTests.cs" />
@ -101,6 +104,7 @@
<Compile Include="PInvoke\WinInet\WinInetTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Security\AccessControl\SystemSecurityTests.cs" />
<Compile Include="Shell\ShellAssocTests.cs" />
<Compile Include="Shell\ShellFileOperationsTests.cs" />
<Compile Include="Shell\ControlPanelTests.cs" />
<Compile Include="Shell\ShellItemTests.cs" />

View File

@ -0,0 +1,147 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using Vanara.Collections;
namespace Vanara.Windows.Shell
{
public abstract class RegistryBasedVirtualDictionary<T> : VirtualReadOnlyDictionary<string, T>
{
protected RegistryKey baseKey;
protected readonly bool readOnly;
protected RegistryBasedVirtualDictionary(RegistryKey baseKey, bool readOnly)
{
this.baseKey = baseKey;
this.readOnly = readOnly;
}
public override IEnumerable<string> Keys => baseKey?.GetSubKeyNames().Where(SubKeyFilter) ?? new string[0];
public override bool ContainsKey(string key) => (baseKey?.HasSubKey(key) ?? false) && SubKeyFilter(key);
protected virtual bool SubKeyFilter(string keyName) => true;
}
public class CommandVerbDictionary : RegistryBasedVirtualDictionary<CommandVerb>
{
private const string rootKeyName = "shell";
private readonly RegBasedSettings parent;
internal CommandVerbDictionary(RegBasedSettings parent, bool readOnly) : base(parent.key.OpenSubKey(rootKeyName, !readOnly), readOnly)
{
this.parent = parent;
}
public override IEnumerable<string> Keys => baseKey?.GetSubKeyNames() ?? new string[0];
public CommandVerb Add(string verb, string displayName = null, string command = null)
{
if (baseKey == null && !readOnly)
baseKey = parent.key.CreateSubKey(rootKeyName);
return ShellRegistrar.RegisterCommandVerb(parent.key, verb, displayName, command);
}
public override bool ContainsKey(string key) => baseKey?.HasSubKey(key) ?? false;
public bool Remove(string key)
{
try
{
baseKey.DeleteSubKeyTree(key);
return true;
}
catch
{
return false;
}
}
public IList<CommandVerb> Order
{
get
{
var order = baseKey.GetValue("", null)?.ToString();
var vals = Values.ToList();
if (order != null)
{
var orderedItems = order.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
for (var i = orderedItems.Length - 1; i >= 0; i--)
{
var match = vals.Find(c =>
string.Equals(c.Name, orderedItems[i], StringComparison.InvariantCultureIgnoreCase));
if (match == null) continue;
vals.Remove(match);
vals.Insert(0, match);
}
}
return vals;
}
set
{
switch (value?.Count ?? 0)
{
case 0:
baseKey.DeleteValue("", false);
break;
case 1:
baseKey.SetValue("", value.First().Name);
break;
default:
baseKey.SetValue("", string.Join(",", value.Select(c => c.Name).ToArray()));
break;
}
}
}
public override bool TryGetValue(string key, out CommandVerb value)
{
value = null;
if (!ContainsKey(key)) return false;
value = new CommandVerb(baseKey.OpenSubKey(key, !readOnly), key, readOnly);
return true;
}
}
internal class ProgIdDictionary : RegistryBasedVirtualDictionary<ProgId>
{
private static readonly string[] badKeys =
{
"*", "AllFileSystemObjects", "AppID", "Applications", "AudioCD", "Briefcase", "CID", "CID.Local",
"CLSID", "CompressedFolder", "ConflictFolder", "DVD", "DVDFile", "DesktopBackground", "DirectShow",
"Directory", "Drive", "ExplorerCLSIDFlags", "Folder", "Interface", "LibraryFolder", "Local Settings",
"MIME", "Media Servers", "Media Type", "MediaFoundation", "NetServer", "NetShare", "Network",
"Printers", "Stack", "SystemFileAssociations", "TypeLib", "Unknown", "UserLibraryFolder",
"VideoClipContainers", "VirtualStore"
};
public ProgIdDictionary(bool readOnly) : base(Registry.ClassesRoot, readOnly) { }
public override bool TryGetValue(string key, out ProgId value)
{
value = null;
if (!ContainsKey(key)) return false;
value = new ProgId(key, readOnly);
return true;
}
protected override bool SubKeyFilter(string keyName) => !keyName.StartsWith(".") &&
!keyName.StartsWith("Kind.") && Array.BinarySearch(badKeys, keyName, StringComparer.OrdinalIgnoreCase) < 0;
}
internal class ShellAssociationDictionary : RegistryBasedVirtualDictionary<ShellAssociation>
{
public ShellAssociationDictionary(bool readOnly) : base(Registry.ClassesRoot, readOnly) { }
public override bool TryGetValue(string key, out ShellAssociation value)
{
value = null;
if (!ContainsKey(key)) return false;
value = new ShellAssociation(key);
return true;
}
protected override bool SubKeyFilter(string keyName) => keyName.StartsWith(".");
}
}

View File

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using Microsoft.Win32;
using Vanara.PInvoke;
namespace Vanara.Windows.Shell
{
public enum VerbMultiSelectModel { Unset, Player, Single, Document }
public enum VerbPosition { Unset, Top, Bottom }
public enum VerbSelectionModel { Item, BackgroundShortcutMenu }
/// <summary>Encapsulates a shortcut menu verb in the registry.</summary>
public class CommandVerb : RegBasedSettings, IEquatable<CommandVerb>
{
internal CommandVerb(RegistryKey key, string name, bool readOnly = true) : base(key, readOnly)
{
Name = name;
}
public string AppliesTo
{
get => key.GetValue("AppliesTo", null)?.ToString();
set => UpdateValue("AppliesTo", value);
}
public Shell32.SFGAO AttributeMask
{
get => (Shell32.SFGAO)(int)key.GetValue("AttributeMask", 0);
set => UpdateValue("AttributeMask", (int)value, RegistryValueKind.DWord);
}
public Shell32.SFGAO AttributeValue
{
get => (Shell32.SFGAO)(int)key.GetValue("AttributeValue", 0);
set => UpdateValue("AttributeValue", (int)value, RegistryValueKind.DWord);
}
/// <summary>Gets or sets the command string used to launch the command in a console window or batch (.bat) file.</summary>
/// <value>The command string.</value>
/// <remarks>
/// If any element of the command string contains or might contain spaces, it must be enclosed in quotation marks. Otherwise, if the
/// element contains a space, it will not parse correctly. For instance, "My Program.exe" starts the application properly. If you use
/// My Program.exe without quotation marks, then the system attempts to launch My with Program.exe as its first command line
/// argument. You should always use quotation marks with arguments such as "%1" that are expanded to strings by the Shell, because
/// you cannot be certain that the string will not contain a space.
/// </remarks>
public string Command
{
get => key.GetSubKeyDefaultValue("command")?.ToString();
set => UpdateKeyValue("command", value);
}
public Guid? CommandStateHandler
{
get => key.GetGuidValue("CommandStateHandler");
set => UpdateValue("CommandStateHandler", value?.ToString());
}
public string DefaultAppliesTo
{
get => key.GetValue("DefaultAppliesTo", null)?.ToString();
set => UpdateValue("DefaultAppliesTo", value);
}
public Guid? DelegateExecute
{
get => key.GetGuidValue("DelegateExecute");
set => UpdateValue("DelegateExecute", value?.ToString());
}
/// <summary>
/// Gets or sets an optional display name associated with them, which is displayed on the shortcut menu instead of the verb string
/// itself. For example, the display string for <c>openas</c> is Open With. Like normal menu strings, including an ampersand
/// character in the display string allows keyboard selection of the command.
/// </summary>
/// <value>The display name for the verb.</value>
public string DisplayName
{
get => key.GetValue("", null)?.ToString();
set => UpdateValue("", value);
}
public Guid? DropTarget
{
get
{
using (var sk = key.OpenSubKey("DropTarget"))
return sk?.GetGuidValue("CLSID");
}
set
{
CheckRW();
if (!value.HasValue)
{
try { key.DeleteSubKeyTree("DropTarget"); } catch { }
}
else
{
using (var sk = key.CreateSubKey("DropTarget"))
sk?.SetValue("CLSID", value.Value.ToString());
}
}
}
public Guid? ExplorerCommandHandler
{
get => key.GetGuidValue("ExplorerCommandHandler");
set => UpdateValue("ExplorerCommandHandler", value?.ToString());
}
public bool Extended
{
get => key.HasValue("Extended");
set => ToggleValue("Extended", value);
}
public string HasLUAShield
{
get => key.GetValue("HasLUAShield", null)?.ToString();
set => UpdateValue("HasLUAShield", value);
}
public VerbSelectionModel ImpliedSelectionModel
{
get => (VerbSelectionModel)(int)key.GetValue("ImpliedSelectionModel", 0);
set => UpdateValue("ImpliedSelectionModel", (int)value, RegistryValueKind.DWord);
}
public bool LegacyDisable
{
get => key.HasValue("LegacyDisable");
set => ToggleValue("LegacyDisable", value);
}
public IndirectString MUIVerb
{
get => IndirectString.TryParse(key.GetValue("MUIVerb")?.ToString(), out var loc) ? loc : null;
set => UpdateValue("MUIVerb", value?.ToString());
}
public VerbMultiSelectModel MultiSelectModel
{
get => (VerbMultiSelectModel)Enum.Parse(typeof(VerbMultiSelectModel), key.GetValue("MultiSelectModel", VerbMultiSelectModel.Unset.ToString()).ToString());
set => UpdateValue("MultiSelectModel", value.ToString(), RegistryValueKind.String, VerbMultiSelectModel.Unset.ToString());
}
/// <summary>Gets or sets the text string that is used by the Shell to identify the associated command.</summary>
/// <value>The verb name.</value>
public string Name { get; set; }
public bool NeverDefault
{
get => key.HasValue("NeverDefault");
set => ToggleValue("NeverDefault", value);
}
public bool OnlyInBrowserWindow
{
get => key.HasValue("OnlyInBrowserWindow");
set => ToggleValue("OnlyInBrowserWindow", value);
}
public VerbPosition Position
{
get => (VerbPosition)Enum.Parse(typeof(VerbPosition), key.GetValue("Position", VerbPosition.Unset.ToString()).ToString());
set => UpdateValue("Position", value.ToString(), RegistryValueKind.String, VerbPosition.Unset.ToString());
}
public bool ProgrammaticAccessOnly
{
get => key.HasValue("ProgrammaticAccessOnly");
set => ToggleValue("ProgrammaticAccessOnly", value);
}
public bool SeparatorAfter
{
get => 1 == key.GetValue("SeparatorAfter", 0) as int?;
set => UpdateValue("SeparatorAfter", value ? 1 : 0, RegistryValueKind.DWord);
}
public bool SeparatorBefore
{
get => 1 == key.GetValue("SeparatorBefore", 0) as int?;
set => UpdateValue("SeparatorBefore", value ? 1 : 0, RegistryValueKind.DWord);
}
public Shell32.RESTRICTIONS SuppressionPolicy
{
get => (Shell32.RESTRICTIONS)(int)key.GetValue("SuppressionPolicy", 0);
set => UpdateValue("SuppressionPolicy", (int)value, RegistryValueKind.DWord);
}
public bool Equals(CommandVerb other) => Equals((RegBasedSettings)other);
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Drawing;
using Vanara.Extensions;
using Vanara.PInvoke;
using static Vanara.PInvoke.User32_Gdi;
namespace Vanara.Windows.Shell
{
/// <summary>Wraps the icon location string used by some Shell classes.</summary>
public class IndirectString
{
/// <summary>Initializes a new instance of the <see cref="IndirectString"/> class.</summary>
public IndirectString() { }
/// <summary>Initializes a new instance of the <see cref="IndirectString"/> class.</summary>
/// <param name="module">The module file name.</param>
/// <param name="resourceIdOrIndex">If this number is positive, this is the index of the resource in the module file. If negative, the absolute value of the number is the resource ID of the icon in the module file.</param>
public IndirectString(string module, int resourceIdOrIndex)
{
ModuleFileName = module;
ResourceId = resourceIdOrIndex;
}
/// <summary>Gets the icon referred to by this instance.</summary>
/// <value>The icon.</value>
public string Value
{
get
{
if (!IsValid) return null;
using (var lib = new Kernel32.SafeLibraryHandle(ModuleFileName, Kernel32.LoadLibraryExFlags.LOAD_LIBRARY_AS_IMAGE_RESOURCE))
{
if (ResourceId >= 0) throw new NotSupportedException();
const int sz = 2048;
var sb = new System.Text.StringBuilder(sz, sz);
LoadString(lib, -ResourceId, sb, sz);
return sb.ToString();
}
}
}
/// <summary>Returns true if this location is valid.</summary>
/// <value><c>true</c> if this location is valid; otherwise, <c>false</c>.</value>
public bool IsValid => System.IO.File.Exists(ModuleFileName) && ResourceId != 0;
/// <summary>Gets or sets the module file name.</summary>
/// <value>The module file name.</value>
public string ModuleFileName { get; set; }
/// <summary>Gets or sets the resource index or resource ID.</summary>
/// <value>If this number is positive, this is the index of the resource in the module file. If negative, the absolute value of the number is the resource ID of the icon in the module file.</value>
public int ResourceId { get; set; }
/// <summary>Tries to parse the specified string to create a <see cref="IndirectString"/> instance.</summary>
/// <param name="value">The string representation in the format of either "ModuleFileName,ResourceIndex" or "ModuleFileName,-ResourceID".</param>
/// <param name="loc">The resulting <see cref="IndirectString"/> instance on success.</param>
/// <returns><c>true</c> if successfully parsed.</returns>
public static bool TryParse(string value, out IndirectString loc)
{
var parts = value?.Split(',');
if (parts != null && parts.Length == 2 && int.TryParse(parts[1], out var i) && parts[0].StartsWith("@"))
{
loc = new IndirectString(parts[0].TrimStart('@'), i);
return true;
}
loc = new IndirectString();
return false;
}
/// <summary>Returns a <see cref="System.String" /> that represents this instance.</summary>
/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
public override string ToString() => IsValid ? $"@{ModuleFileName},{ResourceId}" : string.Empty;
}
}

213
Windows.Shell/ProgId.cs Normal file
View File

@ -0,0 +1,213 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using static Vanara.PInvoke.ShlwApi;
namespace Vanara.Windows.Shell
{
/// <summary>Represents a programmatic identifier in the registry for an application.</summary>
/// <seealso cref="System.IDisposable"/>
public class ProgId : RegBasedSettings
{
/// <summary>Initializes a new instance of the <see cref="ProgId"/> class.</summary>
/// <param name="progId">The programmatic identifier string.</param>
/// <param name="readOnly">if set to <c>true</c> disallow changes.</param>
/// <param name="autoLoadVersioned">
/// if set to <c>true</c> automatically load a referenced versioned ProgId instead of the specified ProgId.
/// </param>
public ProgId(string progId, bool readOnly = true, bool autoLoadVersioned = true) : base(Registry.ClassesRoot.OpenSubKey(progId ?? throw new ArgumentNullException(nameof(progId)), !readOnly), readOnly)
{
if (autoLoadVersioned)
{
var cv = key.GetSubKeyDefaultValue("CurVer")?.ToString();
if (cv != null)
key = Registry.ClassesRoot.OpenSubKey(cv, !readOnly);
}
if (key == null) throw new ArgumentException("Unable to load specified ProgId", nameof(progId));
ID = progId;
Verbs = new CommandVerbDictionary(this, readOnly);
}
internal ProgId(string progId, RegistryKey pkey, bool readOnly) : base(pkey, readOnly)
{
ID = progId;
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>
/// 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.
/// </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); }
set => UpdateKeyValue("CLSID", value?.ToString());
}
/// <summary>Gets or sets the versioned ProgId for this instance.</summary>
public ProgId CurVer
{
get => key.HasSubKey("CurVer") ? new ProgId(ID, ReadOnly, true) : null;
set => UpdateKeyValue("CurVer", value?.ID);
}
/// <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());
}
/// <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>
/// 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.
/// </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; }
private static PropertyDescriptionList GetPDL(RegistryKey key, string valueName)
{
var pdl = key.GetValue(valueName)?.ToString();
return pdl == null ? null : new PropertyDescriptionList(pdl);
}
// TODO + public
private IEnumerable<ShellAssociation> QueryAssociations() => null; //ShellRegistrar.GetAssociatedFileExtensions(ID).Select(s => new ShellAssociation(s));
}
}

View File

@ -1,7 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Vanara.Extensions;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.PropSys;
@ -25,13 +28,13 @@ namespace Vanara.Windows.Shell
public PropertyDescription(PROPERTYKEY propkey)
{
key = propkey;
if (PSGetPropertyDescription(ref propkey, typeof(IPropertyDescription).GUID, out var ppv).Succeeded)
if (PSGetPropertyDescription(propkey, typeof(IPropertyDescription).GUID, out var ppv).Succeeded)
iDesc = (IPropertyDescription)ppv;
}
/// <summary>Initializes a new instance of the <see cref="PropertyDescription"/> class.</summary>
/// <param name="propertyDescription">The property description.</param>
internal protected PropertyDescription(IPropertyDescription propertyDescription)
protected internal PropertyDescription(IPropertyDescription propertyDescription)
{
iDesc = propertyDescription;
key = iDesc.GetPropertyKey();
@ -53,8 +56,7 @@ namespace Vanara.Windows.Shell
{
PROPDESC_CONDITION_TYPE ct = 0;
CONDITION_OPERATION co = 0;
if (iDesc != null)
iDesc.GetConditionType(out ct, out co);
iDesc?.GetConditionType(out ct, out co);
return new Tuple<PROPDESC_CONDITION_TYPE, CONDITION_OPERATION>(ct, co);
}
}
@ -172,21 +174,26 @@ namespace Vanara.Windows.Shell
/// <summary>The IPropertyDescriptionList instance.</summary>
protected IPropertyDescriptionList iList;
/// <summary>
/// Initializes a new instance of the <see cref="PropertyDescriptionList"/> class from a string.
/// </summary>
/// <param name="propList">The property list. See <see cref="IPropertySystem.GetPropertyDescriptionListFromString"/> for the required format.</param>
public PropertyDescriptionList(string propList)
{
PSGetPropertyDescriptionListFromString(propList, typeof(IPropertyDescriptionList).GUID, out iList).ThrowIfFailed();
}
/// <summary>Initializes a new instance of the <see cref="PropertyDescriptionList"/> class.</summary>
/// <param name="list">The COM interface pointer.</param>
internal protected PropertyDescriptionList(IPropertyDescriptionList list)
protected internal PropertyDescriptionList(IPropertyDescriptionList list)
{
iList = list;
}
/// <summary>Gets the number of elements in the collection.</summary>
/// <value>The number of elements in the collection.</value>
/// <inheritdoc />
public virtual int Count => (int)(iList?.GetCount() ?? 0);
/// <summary>Gets the <see cref="PropertyDescription"/> at the specified index.</summary>
/// <value>The <see cref="PropertyDescription"/>.</value>
/// <param name="index">The index.</param>
/// <returns>The <see cref="PropertyDescription"/> at the specified index.</returns>
/// <inheritdoc />
public virtual PropertyDescription this[int index] =>
new PropertyDescription(iList?.GetAt((uint)index, typeof(IPropertyDescription).GUID));
@ -196,7 +203,7 @@ namespace Vanara.Windows.Shell
/// <returns>The <see cref="PropertyDescription" /> for the specified key.</returns>
public virtual PropertyDescription this[PROPERTYKEY propkey] => new PropertyDescription(propkey);
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <inheritdoc />
public virtual void Dispose()
{
if (iList != null)
@ -206,12 +213,13 @@ namespace Vanara.Windows.Shell
}
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.</returns>
/// <inheritdoc />
public IEnumerator<PropertyDescription> GetEnumerator() => Enum().GetEnumerator();
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.</returns>
/// <inheritdoc />
public override string ToString() => "prop:" + string.Join(";", this.Select(d => $"{GetPrefixForViewFlags(d.ViewFlags)}{d.CanonicalName}").ToArray());
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>Enumerates through the items in this instance.</summary>
@ -220,7 +228,46 @@ namespace Vanara.Windows.Shell
{
for (var i = 0; i < Count; i++)
yield return this[i];
yield break;
}
// TODO: Incomplete. Needs to also include ?, < and & flags, but they are not documented.
private static string GetPrefixForViewFlags(PROPDESC_VIEW_FLAGS flags)
{
var sb = new StringBuilder();
foreach (var e in flags.GetFlags())
{
switch (e)
{
case PROPDESC_VIEW_FLAGS.PDVF_CENTERALIGN:
sb.Append('|');
break;
case PROPDESC_VIEW_FLAGS.PDVF_RIGHTALIGN:
sb.Append('/');
break;
case PROPDESC_VIEW_FLAGS.PDVF_BEGINNEWGROUP:
sb.Append('^');
break;
case PROPDESC_VIEW_FLAGS.PDVF_FILLAREA:
sb.Append('#');
break;
case PROPDESC_VIEW_FLAGS.PDVF_SORTDESCENDING:
sb.Append('-');
break;
case PROPDESC_VIEW_FLAGS.PDVF_SHOWONLYIFPRESENT:
sb.Append('*');
break;
case PROPDESC_VIEW_FLAGS.PDVF_HIDELABEL:
sb.Append('~');
break;
}
}
if (flags.IsFlagSet(PROPDESC_VIEW_FLAGS.PDVF_SHOWBYDEFAULT | PROPDESC_VIEW_FLAGS.PDVF_SHOWINPRIMARYLIST | PROPDESC_VIEW_FLAGS.PDVF_SHOWINSECONDARYLIST))
sb.Append('0');
else if (flags.IsFlagSet(PROPDESC_VIEW_FLAGS.PDVF_SHOWINPRIMARYLIST | PROPDESC_VIEW_FLAGS.PDVF_SHOWINSECONDARYLIST))
sb.Append('1');
if (flags.IsFlagSet(PROPDESC_VIEW_FLAGS.PDVF_SHOWINSECONDARYLIST))
sb.Append('2');
return sb.ToString();
}
}
@ -235,7 +282,7 @@ namespace Vanara.Windows.Shell
/// <summary>Initializes a new instance of the <see cref="PropertyType"/> class.</summary>
/// <param name="type">The IPropertyEnumType object.</param>
internal protected PropertyType(IPropertyEnumType type)
protected internal PropertyType(IPropertyEnumType type)
{
iType = type;
}
@ -302,7 +349,7 @@ namespace Vanara.Windows.Shell
/// <summary>Initializes a new instance of the <see cref="PropertyTypeList"/> class.</summary>
/// <param name="list">The IPropertyEnumTypeList object.</param>
internal protected PropertyTypeList(IPropertyEnumTypeList list)
protected internal PropertyTypeList(IPropertyEnumTypeList list)
{
iList = list;
}
@ -350,7 +397,6 @@ namespace Vanara.Windows.Shell
{
for (var i = 0; i < Count; i++)
yield return this[i];
yield break;
}
}
}

View File

@ -0,0 +1,97 @@
using Microsoft.Win32;
using System;
namespace Vanara.Windows.Shell
{
/// <summary>Base class for registry based settings.</summary>
public abstract class RegBasedSettings : IDisposable, IEquatable<RegBasedSettings>, IComparable<RegBasedSettings>
{
/// <summary>The base key from which to perform all queries.</summary>
protected internal RegistryKey key;
/// <summary>Initializes a new instance of the <see cref="RegBasedSettings"/> class.</summary>
/// <param name="key">The key to use as the base key for queries.</param>
/// <param name="readOnly">if set to <c>true</c> the supplied <paramref name="key"/> was opened read-only.</param>
protected RegBasedSettings(RegistryKey key, bool readOnly)
{
this.key = key ?? throw new ArgumentNullException(nameof(key));
ReadOnly = readOnly;
}
/// <summary>Gets or sets a value indicating whether these settings are read-only.</summary>
public bool ReadOnly { get; }
/// <inheritdoc/>
public bool Equals(RegBasedSettings other) => ((IComparable<RegBasedSettings>)this).CompareTo(other) == 0;
/// <inheritdoc/>
public override int GetHashCode() => key?.Name.GetHashCode() ?? 0;
/// <inheritdoc/>
public override string ToString() => key?.ToString() ?? "";
/// <inheritdoc/>
int IComparable<RegBasedSettings>.CompareTo(RegBasedSettings other) => string.Compare(key.Name, other?.key.Name, StringComparison.InvariantCulture);
/// <inheritdoc/>
void IDisposable.Dispose()
{
key?.Close();
}
/// <summary>Checks the ReadOnly flag and throws an exception if it is true.</summary>
protected void CheckRW() { if (ReadOnly) throw new InvalidOperationException("Object is read only and its values cannot be changed."); }
/// <summary>Toggles the value identified by having a named subkey present.</summary>
/// <param name="name">The name of the subkey.</param>
/// <param name="set">if set to <c>true</c>, creates a subkey named <paramref name="name"/>; otherwise deletes that subkey.</param>
protected void ToggleKeyValue(string name, bool set)
{
CheckRW();
if (!set)
key.DeleteSubKey(name, false);
else
key.CreateSubKey(name)?.Close();
}
/// <summary>Toggles the value identified by having a named value present.</summary>
/// <param name="name">The name of the value.</param>
/// <param name="set">if set to <c>true</c>, creates a value named <paramref name="name"/>; otherwise deletes that value.</param>
protected void ToggleValue(string name, bool set)
{
CheckRW();
if (!set)
key.DeleteValue(name, false);
else
key.SetValue(name, "", RegistryValueKind.String);
}
/// <summary>Updates the value identified by having a named subkey with its default value set.</summary>
/// <param name="name">The name of the subkey.</param>
/// <param name="value">The value of the default value.</param>
/// <param name="deleteIfValue">The value that, if equal to <paramref name="value"/>, causes the removal of the subkey.</param>
protected void UpdateKeyValue(string name, string value, string deleteIfValue = null)
{
CheckRW();
if (Equals(value, deleteIfValue))
key.DeleteSubKey(name, false);
else
key.CreateSubKey(name, value).Close();
}
/// <summary>Updates the value identified by having a named value.</summary>
/// <typeparam name="T">Type of the value</typeparam>
/// <param name="name">The name of the value.</param>
/// <param name="value">The value of the value.</param>
/// <param name="valueKind">Kind of the value.</param>
/// <param name="deleteIfValue">The value that, if equal to <paramref name="value"/>, causes the removal of the value.</param>
protected void UpdateValue<T>(string name, T value, RegistryValueKind valueKind = RegistryValueKind.Unknown, T deleteIfValue = default(T))
{
CheckRW();
if (Equals(value, deleteIfValue))
key.DeleteValue(name, false);
else
key.SetValue(name, value, valueKind == RegistryValueKind.Unknown && value is string ? RegistryValueKind.String : valueKind);
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.Win32;
namespace Vanara.Windows.Shell
{
internal static class RegistryExtensions
{
private const string improbableValue = "_aVery1mprobable*Value";
public static RegistryKey CreateSubKey(this RegistryKey key, string subkey, string defaultValue)
{
var sk = key.CreateSubKey(subkey);
if (defaultValue != null)
sk?.SetValue(null, defaultValue);
return sk;
}
public static bool DeleteAllSubItems(this RegistryKey key)
{
var succeeded = true;
foreach (var n in key.GetSubKeyNames())
try { key.DeleteSubKeyTree(n); } catch { succeeded = false; }
foreach (var n in key.GetValueNames())
key.DeleteValue(n);
return succeeded;
}
public static Guid? GetGuidValue(this RegistryKey key, string name)
{
var g = key?.GetValue(name)?.ToString();
return g != null ? new Guid(g) : (Guid?)null;
}
public static object GetSubKeyDefaultValue(this RegistryKey key, string subkey)
{
using (var sk = key.OpenSubKey(subkey))
return sk?.GetValue(null);
}
public static bool HasSubKey(this RegistryKey key, string subkeyName)
{
using (var sk = key.OpenSubKey(subkeyName))
return sk != null;
}
public static bool HasValue(this RegistryKey key, string name) => !Equals(key.GetValue(name, improbableValue), improbableValue);
}
}

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Text;
using static Vanara.PInvoke.ShlwApi;
namespace Vanara.Windows.Shell
{
public class ShellAssociation
{
private IQueryAssociations qassoc;
internal ShellAssociation(string ext) { Extension = ext; }
public static IReadOnlyDictionary<string, ShellAssociation> FileAssociations { get; } = new ShellAssociationDictionary(true);
public string AppId => GetString(ASSOCSTR.ASSOCSTR_APPID);
public string Command => GetString(ASSOCSTR.ASSOCSTR_COMMAND);
public string ContentType => GetString(ASSOCSTR.ASSOCSTR_CONTENTTYPE);
public string DefaultIcon => GetString(ASSOCSTR.ASSOCSTR_DEFAULTICON);
public string Extension { get; }
public string FriendlyAppName => GetString(ASSOCSTR.ASSOCSTR_FRIENDLYAPPNAME);
public string FriendlyDocName => GetString(ASSOCSTR.ASSOCSTR_FRIENDLYDOCNAME);
public string OpenWithPath => GetString(ASSOCSTR.ASSOCSTR_EXECUTABLE);
public static IReadOnlyDictionary<string, CommandVerb> Verbs { get; }
//public static ShellAssociation CreateFromCLSID(Guid classId) { }
//public static ShellAssociation CreateFromProgId(string progId) { }
public static ShellAssociation CreateFromFileExtension(string ext)
{
var ret = new ShellAssociation(ext);
if (false) //Environment.OSVersion.Version.Major >= 6)
{
//var elements = new[] { new ASSOCIATIONELEMENT { ac = ASSOCCLASS.ASSOCCLASS_PROGID_STR, pszClass = progId } };
//AssocCreateForClasses(elements, (uint)elements.Length, typeof(IQueryAssociations).GUID, out var iq).ThrowIfFailed();
//ret.qassoc = (IQueryAssociations)iq;
}
else
{
AssocCreate(CLSID_QueryAssociations, typeof(IQueryAssociations).GUID, out ret.qassoc).ThrowIfFailed();
ret.qassoc.Init(ASSOCF.ASSOCF_INIT_DEFAULTTOSTAR, ext);
}
return ret;
}
private string GetString(ASSOCSTR astr)
{
const ASSOCF flags = ASSOCF.ASSOCF_NOTRUNCATE | ASSOCF.ASSOCF_REMAPRUNDLL;
var sz = 0U;
qassoc.GetString(flags, astr, null, null, ref sz);
var sb = new StringBuilder((int)sz, (int)sz);
qassoc.GetString(flags, astr, null, sb, ref sz);
return sb.ToString();
}
}
}

View File

@ -0,0 +1,196 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using static Vanara.PInvoke.Shell32;
using static Vanara.PInvoke.ShlwApi;
namespace Vanara.Windows.Shell
{
/// <summary>Static class that has methods used to register and unregister shell items in the Windows Registry.</summary>
public static class ShellRegistrar
{
/*
HRESULT RegisterAppDropTarget() const;
// 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;
HRESULT RegisterExecuteCommandVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const;
HRESULT RegisterExplorerCommandVerb(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszVerbDisplayName) const;
HRESULT RegisterExplorerCommandStateHandler(PCWSTR pszProgID, PCWSTR pszVerb) const;
HRESULT RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName) const;
HRESULT RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName, PCWSTR pszValue) const;
HRESULT RegisterVerbAttribute(PCWSTR pszProgID, PCWSTR pszVerb, PCWSTR pszValueName, DWORD dwValue) const;
HRESULT RegisterVerbDefaultAndOrder(PCWSTR pszProgID, PCWSTR pszVerbOrderFirstIsDefault) const;
HRESULT RegisterPlayerVerbs(PCWSTR const rgpszAssociation[], UINT countAssociation,
PCWSTR pszVerb, PCWSTR pszTitle) const;
HRESULT UnRegisterVerb(PCWSTR pszProgID, PCWSTR pszVerb) const;
HRESULT UnRegisterVerbs(PCWSTR const rgpszAssociation[], UINT countAssociation, PCWSTR pszVerb) const;
HRESULT RegisterContextMenuHandler(PCWSTR pszProgID, PCWSTR pszDescription) const;
HRESULT RegisterRightDragContextMenuHandler(PCWSTR pszProgID, PCWSTR pszDescription) const;
HRESULT RegisterAppShortcutInSendTo() const;
HRESULT RegisterThumbnailHandler(PCWSTR pszExtension) const;
HRESULT RegisterPropertyHandler(PCWSTR pszExtension) const;
HRESULT UnRegisterPropertyHandler(PCWSTR pszExtension) const;
HRESULT RegisterLinkHandler(PCWSTR pszProgID) const;
HRESULT RegisterExtensionWithProgID(PCWSTR pszFileExtension, PCWSTR pszProgID) const;
HRESULT RegisterOpenWith(PCWSTR pszFileExtension, PCWSTR pszProgID) const;
HRESULT RegisterNewMenuNullFile(PCWSTR pszFileExtension, PCWSTR pszProgID) const;
HRESULT RegisterNewMenuData(PCWSTR pszFileExtension, PCWSTR pszProgID, PCSTR pszBase64) const;
HRESULT RegisterKind(PCWSTR pszFileExtension, PCWSTR pszKindValue) const;
HRESULT UnRegisterKind(PCWSTR pszFileExtension) const;
HRESULT RegisterPropertyHandlerOverride(PCWSTR pszProperty) const;
HRESULT RegisterHandlerSupportedProtocols(PCWSTR pszProtocol) const;
HRESULT RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName) const;
HRESULT RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName, PCWSTR pszValue) const;
HRESULT RegisterProgIDValue(PCWSTR pszProgID, PCWSTR pszValueName, DWORD dwValue) const;
*/
public static IEnumerable<string> GetAssociatedFileExtensions(string progId) =>
Registry.ClassesRoot.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<string> 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"
var root = userOnly ? Registry.CurrentUser : Registry.LocalMachine;
using (var reg = root.OpenSubKey(@"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.");
// Build short path and store as default value
var shortPath = fullExePath;
var l = fullExePath.Length + 5;
var sb = new StringBuilder(l, l);
var rl = PInvoke.Kernel32.GetShortPathName(fullExePath.Length > PInvoke.Kernel32.MAX_PATH ? @"\\?\" + fullExePath : fullExePath, sb, (uint)sb.Capacity);
if (rl > 0 && rl <= l) shortPath = sb.ToString();
sk.SetValue(null, shortPath);
// Add Path value
sk.SetValue("Path", Path.GetDirectoryName(fullExePath));
// Add UseUrl value if needed
if (acceptsUrls)
sk.SetValue("UseUrl", 1U, RegistryValueKind.DWord);
// Add DropTarget GUID if needed
if (dropTarget != null)
sk.SetValue("DropTarget", dropTarget.Value.ToString());
}
// Handle registrations in HKCR\Applications
using (var reg = Registry.ClassesRoot.OpenSubKey(@"Applications"))
using (var sk = reg?.CreateSubKey(fn))
{
if (sk == null) throw new InvalidOperationException("Unable to create application key in the HKCR\\Applications subkey.");
if (friendlyName != null)
sk.SetValue("FriendlyAppName", friendlyName.ToString());
if (supportedTypes != null)
using (var stk = sk.CreateSubKey("SupportedTypes"))
foreach (var s in supportedTypes)
stk?.SetValue(s, string.Empty, RegistryValueKind.String);
if (defaultIcon != null)
sk.CreateSubKey("DefaultIcon", defaultIcon.ToString());
if (noStartPage)
sk.SetValue("NoStartPage", string.Empty, RegistryValueKind.String);
if (taskGroupIcon != null)
sk.SetValue("TaskbarGroupIcon", taskGroupIcon.ToString());
if (useExecutableForTaskbarGroupIcon)
sk.SetValue("UseExecutableForTaskbarGroupIcon", string.Empty, RegistryValueKind.String);
}
NotifyShell();
}
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.");
var v = new CommandVerb(vkey, verb, false) {DisplayName = displayName, Command = command};
NotifyShell();
return v;
}
public static void RegisterFileAssociation(string ext, string progId, PERCEIVED perceivedType = PERCEIVED.PERCEIVED_TYPE_UNSPECIFIED, string contentType = null)
{
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))
{
if (pkey == null) throw new InvalidOperationException("Unable to create association key in the registry.");
pkey.SetValue(null, progId);
if (perceivedType > 0)
pkey.SetValue("PerceivedType", perceivedType.ToString().Substring(15).ToLower());
if (!string.IsNullOrEmpty(contentType))
pkey.SetValue("Content Type", contentType);
}
NotifyShell();
}
public static ProgId RegisterProgID(string progId, string typeName)
{
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);
}
public static void UnregisterFileAssociation(string ext, string progId, bool throwOnMissing = true)
{
using (var sk = Registry.ClassesRoot.OpenSubKey(ext, true))
{
if (sk == null)
{
if (throwOnMissing)
throw new InvalidOperationException("Unable to find association key in the registry.");
return;
}
try { sk.DeleteSubKeyTree(progId); } catch { }
NotifyShell();
}
}
public static void UnregisterProgID(string progId, IEnumerable<string> fileExtensions = null)
{
try
{
Registry.ClassesRoot.DeleteSubKeyTree(progId);
}
catch
{
Registry.ClassesRoot.DeleteSubKey(progId, false);
}
if (fileExtensions == null) return;
foreach (var ext in fileExtensions)
UnregisterFileAssociation(ext, progId, false);
NotifyShell();
}
internal static void NotifyShell() => SHChangeNotify(SHCNE.SHCNE_ASSOCCHANGED, SHCNF.SHCNF_FLUSHNOWAIT | SHCNF.SHCNF_IDLIST);
private static bool IsDefined(string rootValue) => Registry.ClassesRoot.HasSubKey(rootValue);
}
}