How to retrieve a list of all available file properties for all file types
See the question and my original answer on StackOverflowTo get all properties, you must use the PSEnumeratePropertyDescriptions function
Here is a .NET Framework version which redefines (partially) some interfaces and types you need:
internal class Program
{
static void Main()
{
PSEnumeratePropertyDescriptions(PROPDESC_ENUMFILTER.PDEF_ALL, typeof(IPropertyDescriptionList).GUID, out var list);
for (var i = 0; i < list.GetCount(); i++)
{
var pd = list.GetAt(i, typeof(IPropertyDescription).GUID);
var pk = pd.GetPropertyKey();
Console.WriteLine($"Property Key : {pk.fmtid:B} {pk.pid}");
pd.GetCanonicalName(out var name);
Console.WriteLine($"Canonical Name : {Marshal.PtrToStringUni(name)}");
Marshal.FreeCoTaskMem(name);
pd.GetDisplayName(out name);
if (name != IntPtr.Zero)
{
Console.WriteLine($"Display Name : {Marshal.PtrToStringUni(name)}");
Marshal.FreeCoTaskMem(name);
}
var viewable = pd.GetTypeFlags(PROPDESC_TYPE_FLAGS.PDTF_ISVIEWABLE) == PROPDESC_TYPE_FLAGS.PDTF_ISVIEWABLE;
Console.WriteLine($"Viewable : {viewable}");
Console.WriteLine();
}
}
public struct PROPERTYKEY
{
public Guid fmtid;
public int pid;
}
public enum PROPDESC_ENUMFILTER
{
PDEF_ALL = 0,
PDEF_SYSTEM = 1,
PDEF_NONSYSTEM = 2,
PDEF_VIEWABLE = 3,
PDEF_QUERYABLE = 4,
PDEF_INFULLTEXTQUERY = 5,
PDEF_COLUMN = 6,
}
[Flags]
public enum PROPDESC_TYPE_FLAGS
{
PDTF_DEFAULT = 0,
PDTF_MULTIPLEVALUES = 0x1,
PDTF_ISINNATE = 0x2,
PDTF_ISGROUP = 0x4,
PDTF_CANGROUPBY = 0x8,
PDTF_CANSTACKBY = 0x10,
PDTF_ISTREEPROPERTY = 0x20,
PDTF_INCLUDEINFULLTEXTQUERY = 0x40,
PDTF_ISVIEWABLE = 0x80,
PDTF_ISQUERYABLE = 0x100,
PDTF_CANBEPURGED = 0x200,
PDTF_SEARCHRAWVALUE = 0x400,
PDTF_DONTCOERCEEMPTYSTRINGS = 0x800,
PDTF_ALWAYSINSUPPLEMENTALSTORE = 0x1000,
PDTF_ISSYSTEMPROPERTY = unchecked((int)0x80000000),
PDTF_MASK_ALL = unchecked((int)0x80001fff),
}
[DllImport("propsys")]
public static extern int PSEnumeratePropertyDescriptions(PROPDESC_ENUMFILTER filterOn, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IPropertyDescriptionList ppv);
[ComImport, Guid("1F9FC1D0-C39B-4B26-817F-011967D3440E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyDescriptionList
{
int GetCount();
[return: MarshalAs(UnmanagedType.Interface)]
IPropertyDescription GetAt(int iElem, [MarshalAs(UnmanagedType.LPStruct)] Guid riid);
}
[ComImport, Guid("6F79D558-3E96-4549-A1D1-7D75D2288814"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyDescription
{
PROPERTYKEY GetPropertyKey();
[PreserveSig] int GetCanonicalName(out IntPtr name);
int GetPropertyType();
[PreserveSig] int GetDisplayName(out IntPtr name);
[PreserveSig] int GetEditInvitation(out IntPtr name);
PROPDESC_TYPE_FLAGS GetTypeFlags(PROPDESC_TYPE_FLAGS mask);
// following methods are undefined in this code since we don't need it
}
}
If you use .NET Core 5+ you can benefit from the CsWin32 project which will define all needed interop for you. Note using this requires to declare your code unsafe somehow. Introp code is also auto-generated so using it is not strictly equivalent (uint vs int, etc.)
csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="NativeMethods.txt" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="NativeMethods.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
NativeMethods.txt:
IPropertyDescription
IPropertyDescriptionList
PSEnumeratePropertyDescriptions
Program.cs
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Windows.Win32;
using Windows.Win32.UI.Shell.PropertiesSystem;
[assembly: SupportedOSPlatform("windows6.0.6000")]
namespace DumpShellProps;
internal class Program
{
static unsafe void Main()
{
PInvoke.PSEnumeratePropertyDescriptions(PROPDESC_ENUMFILTER.PDEF_ALL, typeof(IPropertyDescriptionList).GUID, out var ppv).ThrowOnFailure();
var list = (IPropertyDescriptionList)Marshal.GetTypedObjectForIUnknown((nint)ppv, typeof(IPropertyDescriptionList));
list.GetCount(out var count);
for (uint i = 0; i < count; i++)
{
list.GetAt(i, typeof(IPropertyDescription).GUID, out var obj);
var pd = (IPropertyDescription)obj;
pd.GetPropertyKey(out var pk);
Console.WriteLine($"Property Key : {pk.fmtid:B} {pk.pid}");
pd.GetCanonicalName(out var cname);
Console.WriteLine($"Canonical Name : {cname}");
Marshal.FreeCoTaskMem((nint)cname.Value);
try
{
pd.GetDisplayName(out var dname);
Console.WriteLine($"Display Name : {dname}");
Marshal.FreeCoTaskMem((nint)dname.Value);
}
catch { } // not all properties have a display name
Console.WriteLine();
}
}
}