Skip to content

Instantly share code, notes, and snippets.

@vbfox
Created October 14, 2011 00:19
Show Gist options
  • Save vbfox/1285901 to your computer and use it in GitHub Desktop.
Save vbfox/1285901 to your computer and use it in GitHub Desktop.
Uses the Windows API to get the tags entered by the user in windows explorer
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
/// <summary>
/// Extract the tags that windows store for a file (internally called keywords)
/// </summary>
/// <code>
/// var file = new FileInfo(@"C:\myfile.jpg");
/// Console.WriteLine("Tags: {0}", String.Join("; ", file.GetTags()));
/// Console.ReadLine();
/// </code>
/// <remarks>
/// Parts of the original code come from this blog article :
/// http://blogs.msdn.com/b/adamroot/archive/2008/04/11/interop-with-propvariants-in-net.aspx
/// With heavy adaptation to make it work in x64
/// </remarks>
static class FileTags
{
[Flags]
enum SHCONT : ushort
{
SHCONTF_CHECKING_FOR_CHILDREN = 0x0010,
SHCONTF_FOLDERS = 0x0020,
SHCONTF_NONFOLDERS = 0x0040,
SHCONTF_INCLUDEHIDDEN = 0x0080,
SHCONTF_INIT_ON_FIRST_NEXT = 0x0100,
SHCONTF_NETPRINTERSRCH = 0x0200,
SHCONTF_SHAREABLE = 0x0400,
SHCONTF_STORAGE = 0x0800,
SHCONTF_NAVIGATION_ENUM = 0x1000,
SHCONTF_FASTITEMS = 0x2000,
SHCONTF_FLATLIST = 0x4000,
SHCONTF_ENABLE_ASYNC = 0x8000
}
[ComImport,
Guid("000214E6-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
ComConversionLoss]
interface IShellFolder
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void ParseDisplayName(IntPtr hwnd, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In, MarshalAs(UnmanagedType.LPWStr)] string pszDisplayName, [Out] out uint pchEaten, [Out] out IntPtr ppidl, [In, Out] ref uint pdwAttributes);
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int EnumObjects([In] IntPtr hwnd, [In] SHCONT grfFlags, [MarshalAs(UnmanagedType.Interface)] out IEnumIDList ppenumIDList);
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int BindToObject([In] IntPtr pidl, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IShellFolder ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void BindToStorage([In] ref IntPtr pidl, [In, MarshalAs(UnmanagedType.Interface)] IBindCtx pbc, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void CompareIDs([In] IntPtr lParam, [In] ref IntPtr pidl1, [In] ref IntPtr pidl2);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void CreateViewObject([In] IntPtr hwndOwner, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetAttributesOf([In] uint cidl, [In] IntPtr apidl, [In, Out] ref uint rgfInOut);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetUIObjectOf([In] IntPtr hwndOwner, [In] uint cidl, [In] IntPtr apidl, [In] ref Guid riid, [In, Out] ref uint rgfReserved, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetDisplayNameOf([In] ref IntPtr pidl, [In] uint uFlags, out IntPtr pName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetNameOf([In] IntPtr hwnd, [In] ref IntPtr pidl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszName, [In] uint uFlags, [Out] IntPtr ppidlOut);
}
[ComImport,
Guid("000214F2-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IEnumIDList
{
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int Next(uint celt, IntPtr rgelt, out uint pceltFetched);
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int Skip([In] uint celt);
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int Reset();
[PreserveSig]
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int Clone([MarshalAs(UnmanagedType.Interface)] out IEnumIDList ppenum);
}
public enum SIGDN : uint
{
NORMALDISPLAY = 0,
PARENTRELATIVEPARSING = 0x80018001,
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
DESKTOPABSOLUTEPARSING = 0x80028000,
PARENTRELATIVEEDITING = 0x80031001,
DESKTOPABSOLUTEEDITING = 0x8004c000,
FILESYSPATH = 0x80058000,
URL = 0x80068000
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
public interface IShellItem
{
void BindToHandler(IntPtr pbc,
[MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
[MarshalAs(UnmanagedType.LPStruct)]Guid riid,
out IntPtr ppv);
void GetParent(out IShellItem ppsi);
void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
void Compare(IShellItem psi, uint hint, out int piOrder);
};
static class NativeMethods
{
[DllImport("shell32.dll", EntryPoint = "SHGetDesktopFolder", CharSet = CharSet.Unicode, SetLastError = true)]
static extern int SHGetDesktopFolder_([MarshalAs(UnmanagedType.Interface)] out IShellFolder ppshf);
public static IShellFolder SHGetDesktopFolder()
{
IShellFolder result;
Marshal.ThrowExceptionForHR(SHGetDesktopFolder_(out result));
return result;
}
[DllImport("shell32.dll")]
public static extern void ILFree([In] IntPtr pidl);
[DllImport("Propsys.dll", EntryPoint = "PSGetItemPropertyHandler")]
public static extern int PSGetItemPropertyHandler(IntPtr punkItem, bool fReadWrite, ref Guid riid,
out IntPtr ppv);
[DllImport("Shell32.dll", EntryPoint = "SHCreateShellItem")]
static extern int SHCreateShellItem_(
[In, Optional] IntPtr pidlParent,
[In, Optional] IShellFolder psfParent,
[In] IntPtr pidl,
[Out] out IShellItem ppsi);
public static void SHCreateShellItem(IntPtr pidlParent, IShellFolder psfParent, IntPtr pidl, out IShellItem ppsi)
{
var result = SHCreateShellItem_(pidlParent, psfParent, pidl, out ppsi);
Marshal.ThrowExceptionForHR(result);
}
}
[StructLayout(LayoutKind.Sequential)]
public struct PROPERTYKEY
{
public Guid fmtid;
public UIntPtr pid;
}
[ComImport]
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore32
{
[PreserveSig]
int GetCount([Out] out uint cProps);
[PreserveSig]
int GetAt([In] uint iProp, out PROPERTYKEY pkey);
[PreserveSig]
int GetValue([In] ref PROPERTYKEY key, out PropVariant32 pv);
[PreserveSig]
int SetValue([In] ref PROPERTYKEY key, [In] ref object pv);
[PreserveSig]
int Commit();
}
[ComImport]
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore64
{
[PreserveSig]
int GetCount([Out] out uint cProps);
[PreserveSig]
int GetAt([In] uint iProp, out PROPERTYKEY pkey);
[PreserveSig]
int GetValue([In] ref PROPERTYKEY key, out PropVariant64 pv);
[PreserveSig]
int SetValue([In] ref PROPERTYKEY key, [In] ref object pv);
[PreserveSig]
int Commit();
}
interface IPropVariant : IDisposable
{
byte[] GetDataBytes();
IntPtr FirstPointer { get; }
VarEnum Type { get; }
}
class PropVariantAdapter : IDisposable
{
readonly IPropVariant propVariant;
public PropVariantAdapter(IPropVariant propVariant)
{
this.propVariant = propVariant;
}
sbyte cVal // CHAR cVal;
{
get { return (sbyte)propVariant.GetDataBytes()[0]; }
}
byte bVal // UCHAR bVal;
{
get { return propVariant.GetDataBytes()[0]; }
}
short iVal // SHORT iVal;
{
get { return BitConverter.ToInt16(propVariant.GetDataBytes(), 0); }
}
ushort uiVal // USHORT uiVal;
{
get { return BitConverter.ToUInt16(propVariant.GetDataBytes(), 0); }
}
int lVal // LONG lVal;
{
get { return BitConverter.ToInt32(propVariant.GetDataBytes(), 0); }
}
uint ulVal // ULONG ulVal;
{
get { return BitConverter.ToUInt32(propVariant.GetDataBytes(), 0); }
}
long hVal // LARGE_INTEGER hVal;
{
get { return BitConverter.ToInt64(propVariant.GetDataBytes(), 0); }
}
ulong uhVal // ULARGE_INTEGER uhVal;
{
get { return BitConverter.ToUInt64(propVariant.GetDataBytes(), 0); }
}
float fltVal // FLOAT fltVal;
{
get { return BitConverter.ToSingle(propVariant.GetDataBytes(), 0); }
}
double dblVal // DOUBLE dblVal;
{
get { return BitConverter.ToDouble(propVariant.GetDataBytes(), 0); }
}
bool boolVal // VARIANT_BOOL boolVal;
{
get { return (iVal != 0); }
}
int scode // SCODE scode;
{
get { return lVal; }
}
decimal cyVal // CY cyVal;
{
get { return decimal.FromOACurrency(hVal); }
}
DateTime date // DATE date;
{
get { return DateTime.FromOADate(dblVal); }
}
/// <summary>
/// Gets the variant value.
/// </summary>
public object Value
{
get
{
// TODO: Add support for reference types (ie. VT_REF | VT_I1)
// TODO: Add support for safe arrays
switch (propVariant.Type)
{
case VarEnum.VT_I1:
return cVal;
case VarEnum.VT_UI1:
return bVal;
case VarEnum.VT_I2:
return iVal;
case VarEnum.VT_UI2:
return uiVal;
case VarEnum.VT_I4:
case VarEnum.VT_INT:
return lVal;
case VarEnum.VT_UI4:
case VarEnum.VT_UINT:
return ulVal;
case VarEnum.VT_I8:
return hVal;
case VarEnum.VT_UI8:
return uhVal;
case VarEnum.VT_R4:
return fltVal;
case VarEnum.VT_R8:
return dblVal;
case VarEnum.VT_BOOL:
return boolVal;
case VarEnum.VT_ERROR:
return scode;
case VarEnum.VT_CY:
return cyVal;
case VarEnum.VT_DATE:
return date;
case VarEnum.VT_FILETIME:
return DateTime.FromFileTime(hVal);
case VarEnum.VT_BSTR:
return Marshal.PtrToStringBSTR(propVariant.FirstPointer);
case VarEnum.VT_BLOB:
var blobData = new byte[lVal];
IntPtr pBlobData;
switch (IntPtr.Size)
{
case 4:
pBlobData = (IntPtr)BitConverter.ToInt32(propVariant.GetDataBytes(), IntPtr.Size);
break;
case 8:
pBlobData = (IntPtr)BitConverter.ToInt64(propVariant.GetDataBytes(), IntPtr.Size);
break;
default:
throw new NotSupportedException();
}
Marshal.Copy(pBlobData, blobData, 0, lVal);
return blobData;
case VarEnum.VT_LPSTR:
return Marshal.PtrToStringAnsi(propVariant.FirstPointer);
case VarEnum.VT_LPWSTR:
return Marshal.PtrToStringUni(propVariant.FirstPointer);
case VarEnum.VT_UNKNOWN:
return Marshal.GetObjectForIUnknown(propVariant.FirstPointer);
case VarEnum.VT_DISPATCH:
return propVariant.FirstPointer;
case VarEnum.VT_VECTOR | VarEnum.VT_LPWSTR:
var data = propVariant.GetDataBytes();
var elementCount = BitConverter.ToInt32(data, 0);
var elementsArray = ToIntPtr(data, IntPtr.Size);
var result = new string[elementCount];
for (int i = 0; i < elementCount; i++)
{
var ptr = ReadIntPtr(elementsArray + IntPtr.Size * i);
result[i] = Marshal.PtrToStringUni(ptr);
}
return result;
case VarEnum.VT_EMPTY:
return null;
default:
var message = string.Format("The type of this variable is not support ('{0}')",
propVariant.Type);
throw new NotSupportedException(message);
}
}
}
static IntPtr ReadIntPtr(IntPtr ptr)
{
if (IntPtr.Size == 4)
return (IntPtr)Marshal.ReadInt32(ptr);
else
return (IntPtr)Marshal.ReadInt64(ptr);
}
static IntPtr ToIntPtr(byte[] value, int startIndex)
{
if (IntPtr.Size == 4)
return (IntPtr)BitConverter.ToInt32(value, startIndex);
else
return (IntPtr)BitConverter.ToInt64(value, startIndex);
}
public void Dispose()
{
propVariant.Dispose();
}
}
/// <summary>
/// Represents the OLE struct PROPVARIANT.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct PropVariant32 : IPropVariant
{
ushort vt;
ushort wReserved1;
ushort wReserved2;
ushort wReserved3;
IntPtr p;
int p2;
public IntPtr FirstPointer
{
get { return p; }
}
/// <summary>
/// Gets a byte array containing the data bits of the struct.
/// </summary>
/// <returns>A byte array that is the combined size of the data bits.</returns>
public byte[] GetDataBytes()
{
var ret = new byte[IntPtr.Size + sizeof(int)];
BitConverter.GetBytes(p.ToInt32()).CopyTo(ret, 0);
BitConverter.GetBytes(p2).CopyTo(ret, IntPtr.Size);
return ret;
}
/// <summary>
/// Called to properly clean up the memory referenced by a PropVariant instance.
/// </summary>
[DllImport("ole32.dll")]
private extern static int PropVariantClear(ref PropVariant32 pvar);
/// <summary>
/// Called to clear the PropVariant's referenced and local memory.
/// </summary>
/// <remarks>
/// You must call Clear to avoid memory leaks.
/// </remarks>
public void Clear()
{
// Can't pass "this" by ref, so make a copy to call PropVariantClear with
PropVariant32 var = this;
PropVariantClear(ref var);
// Since we couldn't pass "this" by ref, we need to clear the member fields manually
// NOTE: PropVariantClear already freed heap data for us, so we are just setting
// our references to null.
vt = (ushort)VarEnum.VT_EMPTY;
wReserved1 = wReserved2 = wReserved3 = 0;
p = IntPtr.Zero;
p2 = 0;
}
void IDisposable.Dispose()
{
Clear();
}
/// <summary>
/// Gets the variant type.
/// </summary>
public VarEnum Type
{
get { return (VarEnum)vt; }
}
}
/// <summary>
/// Represents the OLE struct PROPVARIANT.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct PropVariant64 : IPropVariant, IDisposable
{
ushort vt;
ushort wReserved1;
ushort wReserved2;
ushort wReserved3;
IntPtr p;
IntPtr p2;
public IntPtr FirstPointer
{
get { return p; }
}
/// <summary>
/// Gets a byte array containing the data bits of the struct.
/// </summary>
/// <returns>A byte array that is the combined size of the data bits.</returns>
public byte[] GetDataBytes()
{
var ret = new byte[IntPtr.Size * 2];
BitConverter.GetBytes(p.ToInt64()).CopyTo(ret, 0);
BitConverter.GetBytes(p2.ToInt64()).CopyTo(ret, IntPtr.Size);
return ret;
}
/// <summary>
/// Called to properly clean up the memory referenced by a PropVariant instance.
/// </summary>
[DllImport("ole32.dll")]
private extern static int PropVariantClear(ref PropVariant64 pvar);
/// <summary>
/// Called to clear the PropVariant's referenced and local memory.
/// </summary>
/// <remarks>
/// You must call Clear to avoid memory leaks.
/// </remarks>
public void Clear()
{
// Can't pass "this" by ref, so make a copy to call PropVariantClear with
PropVariant64 var = this;
PropVariantClear(ref var);
// Since we couldn't pass "this" by ref, we need to clear the member fields manually
// NOTE: PropVariantClear already freed heap data for us, so we are just setting
// our references to null.
vt = (ushort)VarEnum.VT_EMPTY;
wReserved1 = wReserved2 = wReserved3 = 0;
p = IntPtr.Zero;
p2 = IntPtr.Zero;
}
void IDisposable.Dispose()
{
Clear();
}
/// <summary>
/// Gets the variant type.
/// </summary>
public VarEnum Type
{
get { return (VarEnum)vt; }
}
}
static readonly UIntPtr PIDSI_KEYWORDS = (UIntPtr)5;
static readonly Guid FMTID_SummaryInformation = new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9");
static IntPtr GetShellFolderChildrenRelativePIDL(IShellFolder parentFolder, string displayName)
{
uint pchEaten;
uint pdwAttributes = 0;
IntPtr ppidl;
parentFolder.ParseDisplayName(IntPtr.Zero, null, displayName, out pchEaten, out ppidl, ref pdwAttributes);
return ppidl;
}
public static IntPtr PathToAbsolutePIDL(string path)
{
var desktopFolder = NativeMethods.SHGetDesktopFolder();
return GetShellFolderChildrenRelativePIDL(desktopFolder, path);
}
static PropVariantAdapter GetPropVariant32(IntPtr propertyStore, PROPERTYKEY key)
{
var store = (IPropertyStore32)Marshal.GetTypedObjectForIUnknown(propertyStore, typeof(IPropertyStore32));
PropVariant32 propVariant;
return store.GetValue(ref key, out propVariant) == 0
? new PropVariantAdapter(propVariant)
: null;
}
static PropVariantAdapter GetPropVariant64(IntPtr propertyStore, PROPERTYKEY key)
{
var store = (IPropertyStore64)Marshal.GetTypedObjectForIUnknown(propertyStore, typeof(IPropertyStore64));
PropVariant64 propVariant;
return store.GetValue(ref key, out propVariant) == 0
? new PropVariantAdapter(propVariant)
: null;
}
static PropVariantAdapter GetPropVariant(IntPtr propertyStore, PROPERTYKEY key)
{
switch (IntPtr.Size)
{
case 4:
return GetPropVariant32(propertyStore, key);
case 8:
return GetPropVariant64(propertyStore, key);
default:
throw new NotSupportedException();
}
}
public static string[] GetTags(this FileInfo file)
{
if (file == null) throw new ArgumentNullException("file");
if (!file.Exists) throw new ArgumentException("The file must exists", "file");
var pidl = PathToAbsolutePIDL(file.FullName);
// get the file IShellItem as an IUnknown
IShellItem shellItem;
NativeMethods.SHCreateShellItem(IntPtr.Zero, null, pidl, out shellItem);
NativeMethods.ILFree(pidl);
var shellItemIUnknown = Marshal.GetIUnknownForObject(shellItem);
// Get the property store pointer
IntPtr propertyStorePointer;
var propertyStoreGuid = typeof(IPropertyStore32).GUID;
var getPropertyHandlerResult = NativeMethods.PSGetItemPropertyHandler(shellItemIUnknown,
false, ref propertyStoreGuid, out propertyStorePointer);
if (getPropertyHandlerResult != 0) return new string[0];
// Get the propVariant
var key = new PROPERTYKEY { fmtid = FMTID_SummaryInformation, pid = PIDSI_KEYWORDS };
using (var propVariant = GetPropVariant(propertyStorePointer, key))
{
if (propVariant == null) return new string[0];
var value = propVariant.Value;
if (value == null) return new string[0];
return (string[]) propVariant.Value;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment