Instantly share code, notes, and snippets.
Created
October 14, 2011 00:19
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(1)
1
You must be signed in to fork a gist
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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