potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# プロパティシステムのサンプルコード

C# .NET5.0(.net5.0-windows)でWindowsのプロパティシステムの情報を操作する自分用のサンプルコードを記録しています。

シェルアイテムのプロパティの名前から値(PROPVARIANT)を取得する。

プロパティシステムはCOMインターフェイスIPropertySystemIPropertyStore等)を利用していますが、propsys.dllの公開するヘルパー関数を利用すれば簡単な操作はインターフェイスを意識せず実施できます。コードの流れ:

  1. シェルアイテムの名前からシェルアイテムのプロパティストアを作成する。
  2. プロパティの既知の名前からプロパティディスクリプタ―を作成する。
  3. プロパティストアとプロパティディスクリプタ―からプロパティの値をPROPVARIANT構造体として取得する。
using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        using var propValue = GetPropertyValueFromParsingName(
            path: Environment.SystemDirectory,
            propName: "System.FileOwner",
            GETPROPERTYSTOREFLAGS.GPS_DEFAULT);

        // TODO:ここでPROPVARIANTを処理します。
        Console.WriteLine("vt:{0}", propValue.vt);
        // 出力例:"vt:31"
    }

    private static PropVariant GetPropertyValueFromParsingName(
        in string path,
        in string propName,
        GETPROPERTYSTOREFLAGS storeFlags)
    {
        // try-finallyで確実に解放するアンマネージリソース
        var propStore = default(object);
        var propDesc = default(object);
        var value = default(PropVariant);
        try
        {
            // シェルアイテムの名前からプロパティストアの作成
            propStore = NativeMethods.SHGetPropertyStoreFromParsingName(
                path, null, storeFlags, IID_IUnknown);
            // プロパティ名に対応するプロパティディスクリプタの取得
            propDesc = NativeMethods.PSGetPropertyDescriptionByName(
                propName, IID_IUnknown);
            // プロパティストアからプロパティディスクリプタに対応する値の取得
            value = PropVariant.GetPropertyValue(propStore, propDesc);
            // コピーの返却
            return value;
        }
        catch
        {
            value.Clear();
            throw;
        }
        finally
        {
            // アンマネージリソースの解放
            Marshal.FinalReleaseComObject(propStore);
            Marshal.FinalReleaseComObject(propDesc);
        }
    }

    private static Guid IID_IUnknown => new Guid("{00000000-0000-0000-C000-000000000046}");

    private static class NativeMethods
    {
        [DllImport("shell32.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.IUnknown)]
        public static extern object SHGetPropertyStoreFromParsingName(
            [In] string pszPath,
            [MarshalAs(UnmanagedType.IUnknown)] object pbc,
            GETPROPERTYSTOREFLAGS flags,
            in Guid riid);

        [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.IUnknown)]
        public static extern object PSGetPropertyDescriptionByName(
            [In] string pszCanonicalName,
            in Guid riid);
    }

    [Flags]
    public enum GETPROPERTYSTOREFLAGS
    {
        GPS_DEFAULT = 0,
        GPS_HANDLERPROPERTIESONLY = 0x1,
        GPS_READWRITE = 0x2,
        GPS_TEMPORARY = 0x4,
        GPS_FASTPROPERTIESONLY = 0x8,
        GPS_OPENSLOWITEM = 0x10,
        GPS_DELAYCREATION = 0x20,
        GPS_BESTEFFORT = 0x40,
        GPS_NO_OPLOCK = 0x80,
        GPS_PREFERQUERYPROPERTIES = 0x100,
        GPS_EXTRINSICPROPERTIES = 0x200,
        GPS_EXTRINSICPROPERTIESONLY = 0x400,
        GPS_VOLATILEPROPERTIES = 0x800,
        GPS_VOLATILEPROPERTIESONLY = 0x1000,
        GPS_MASK_VALID = 0x1fff
    }

    public sealed class PropVariant : IDisposable
    {
        private PROPVARIANT value;

        private PropVariant(in PROPVARIANT value)
        {
            this.value = value;
        }

        ~PropVariant()
        {
            Dispose();
        }

        public void Clear()
        {
            NativeMethods.PropVariantClear(ref value);
        }

        public void Dispose()
        {
            Clear();
        }

        public ushort vt
        {
            get => this.value.vt;
            set => this.value.vt = value;
        }

        public static PropVariant GetPropertyValue(
            object propertyStore,
            object propertyDescription)
        {
            NativeMethods.PSGetPropertyValue(
                propertyStore,
                propertyDescription,
                out var pv);
            return new PropVariant(pv);
        }

        private static class NativeMethods
        {
            [DllImport("ole32.dll", ExactSpelling = true)]
            public static extern void PropVariantClear(ref PROPVARIANT pvar);

            [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true)]
            public static extern void PSGetPropertyValue(
                [MarshalAs(UnmanagedType.IUnknown)] object pps,
                [MarshalAs(UnmanagedType.IUnknown)] object ppd,
                out PROPVARIANT ppropvar);
        }

        [StructLayout(LayoutKind.Sequential, Size = 24)]
        public struct PROPVARIANT
        {
            public ushort vt;
        }
    }
}

シェルアイテムのプロパティの名前から値(書式化文字列)を取得する。

コードの流れ:

  1. シェルアイテムの名前からシェルアイテムのプロパティストアを作成する。
  2. プロパティの既知の名前からプロパティディスクリプタ―を作成する。
  3. プロパティストアとプロパティディスクリプタ―からプロパティの値を書式化して取得する。
using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        var propValue = FormatPropertyValueFromParsingName(
            path: Environment.SystemDirectory,
            propName: "System.FileOwner",
            GETPROPERTYSTOREFLAGS.GPS_DEFAULT,
            PROPDESC_FORMAT_FLAGS.PDFF_NOAUTOREADINGORDER);

        // TODO:ここで書式化したプロパティ値を処理します。
        Console.WriteLine(propValue);
        // 出力例:TrustedInstaller

    }

    private static string FormatPropertyValueFromParsingName(
        in string path, 
        in string propName,
        GETPROPERTYSTOREFLAGS storeFlags,
        PROPDESC_FORMAT_FLAGS formatFlags)
    {
        // try-finallyで確実に解放するアンマネージリソース
        var propStore = default(object);
        var propDesc = default(object);
        try
        {
            // シェルアイテムの名前からプロパティストアの作成
            propStore = NativeMethods.SHGetPropertyStoreFromParsingName(
                path, null, storeFlags, IID_IUnknown);
            // プロパティ名に対応するプロパティディスクリプタの取得
            propDesc = NativeMethods.PSGetPropertyDescriptionByName(
                propName, IID_IUnknown);
            // プロパティストアからプロパティディスクリプタに対応する値の取得
            using var p = NativeMethods.PSFormatPropertyValue(propStore, propDesc, formatFlags);
            return Marshal.PtrToStringUni(p.DangerousGetHandle());
        }
        finally
        {
            // アンマネージリソースの解放
            Marshal.FinalReleaseComObject(propStore);
            Marshal.FinalReleaseComObject(propDesc);
        }
    }

    private static Guid IID_IUnknown => new Guid("{00000000-0000-0000-C000-000000000046}");

    private static class NativeMethods
    {
        [DllImport("shell32.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.IUnknown)]
        public static extern object SHGetPropertyStoreFromParsingName(
            [In] string pszPath,
            [MarshalAs(UnmanagedType.IUnknown)] object pbc,
            GETPROPERTYSTOREFLAGS flags,
            in Guid riid);

        [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.IUnknown)]
        public static extern object PSGetPropertyDescriptionByName(
            [In] string pszCanonicalName,
            in Guid riid);

        [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        public static extern SafeCoTaskMemHandle PSFormatPropertyValue(
            [MarshalAs(UnmanagedType.IUnknown)] object pps,
            [MarshalAs(UnmanagedType.IUnknown)] object ppd,
            PROPDESC_FORMAT_FLAGS pdff);
    }

    [Flags]
    public enum GETPROPERTYSTOREFLAGS
    {
        GPS_DEFAULT = 0,
        GPS_HANDLERPROPERTIESONLY = 0x1,
        GPS_READWRITE = 0x2,
        GPS_TEMPORARY = 0x4,
        GPS_FASTPROPERTIESONLY = 0x8,
        GPS_OPENSLOWITEM = 0x10,
        GPS_DELAYCREATION = 0x20,
        GPS_BESTEFFORT = 0x40,
        GPS_NO_OPLOCK = 0x80,
        GPS_PREFERQUERYPROPERTIES = 0x100,
        GPS_EXTRINSICPROPERTIES = 0x200,
        GPS_EXTRINSICPROPERTIESONLY = 0x400,
        GPS_VOLATILEPROPERTIES = 0x800,
        GPS_VOLATILEPROPERTIESONLY = 0x1000,
        GPS_MASK_VALID = 0x1fff
    }

    public enum PROPDESC_FORMAT_FLAGS
    {
        PDFF_DEFAULT,
        PDFF_PREFIXNAME,
        PDFF_FILENAME,
        PDFF_ALWAYSKB,
        PDFF_RESERVED_RIGHTTOLEFT,
        PDFF_SHORTTIME,
        PDFF_LONGTIME,
        PDFF_HIDETIME,
        PDFF_SHORTDATE,
        PDFF_LONGDATE,
        PDFF_HIDEDATE,
        PDFF_RELATIVEDATE,
        PDFF_USEEDITINVITATION,
        PDFF_READONLY,
        PDFF_NOAUTOREADINGORDER
    }

    private sealed class SafeCoTaskMemHandle : SafeHandle
    {
        private SafeCoTaskMemHandle()
            :base(default(IntPtr), true)
        {
        }

        public SafeCoTaskMemHandle(IntPtr handle, bool ownsHandle)
            : base(handle, ownsHandle)
        {
        }

        public override bool IsInvalid => handle == default(IntPtr);

        protected override bool ReleaseHandle()
        {
            Marshal.FreeCoTaskMem(handle);
            return true;
        }
    }
}

PROPVARIANTobject型に変換する。

C#object型は既定のマーシャリングでVARIANT型として扱われるため、WinAPIのPropVariantToVariant関数、VariantToPropVariant関数でobject型へ変換できます。

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        using var propValue = GetPropertyValueFromParsingName(
            path: Environment.SystemDirectory,
            propName: "System.FileOwner",
            GETPROPERTYSTOREFLAGS.GPS_DEFAULT);

        // TODO:ここでPROPVARIANTを処理します。
        var obj = propValue.ToObject();
        Console.WriteLine("vt:{0}", propValue.vt);
        // 出力例:"vt:31"
    }

    private static PropVariant GetPropertyValueFromParsingName(
        in string path,
        in string propName,
        GETPROPERTYSTOREFLAGS storeFlags)
    {
        // try-finallyで確実に解放するアンマネージリソース
        var propStore = default(object);
        var propDesc = default(object);
        var value = default(PropVariant);
        try
        {
            // シェルアイテムの名前からプロパティストアの作成
            propStore = NativeMethods.SHGetPropertyStoreFromParsingName(
                path, null, storeFlags, IID_IUnknown);
            // プロパティ名に対応するプロパティディスクリプタの取得
            propDesc = NativeMethods.PSGetPropertyDescriptionByName(
                propName, IID_IUnknown);
            // プロパティストアからプロパティディスクリプタに対応する値の取得
            value = PropVariant.GetPropertyValue(propStore, propDesc);
            // コピーの返却
            return value;
        }
        catch
        {
            value.Clear();
            throw;
        }
        finally
        {
            // アンマネージリソースの解放
            Marshal.FinalReleaseComObject(propStore);
            Marshal.FinalReleaseComObject(propDesc);
        }
    }

    private static Guid IID_IUnknown => new Guid("{00000000-0000-0000-C000-000000000046}");

    private static class NativeMethods
    {
        [DllImport("shell32.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.IUnknown)]
        public static extern object SHGetPropertyStoreFromParsingName(
            [In] string pszPath,
            [MarshalAs(UnmanagedType.IUnknown)] object pbc,
            GETPROPERTYSTOREFLAGS flags,
            in Guid riid);

        [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.IUnknown)]
        public static extern object PSGetPropertyDescriptionByName(
            [In] string pszCanonicalName,
            in Guid riid);
    }

    [Flags]
    public enum GETPROPERTYSTOREFLAGS
    {
        GPS_DEFAULT = 0,
        GPS_HANDLERPROPERTIESONLY = 0x1,
        GPS_READWRITE = 0x2,
        GPS_TEMPORARY = 0x4,
        GPS_FASTPROPERTIESONLY = 0x8,
        GPS_OPENSLOWITEM = 0x10,
        GPS_DELAYCREATION = 0x20,
        GPS_BESTEFFORT = 0x40,
        GPS_NO_OPLOCK = 0x80,
        GPS_PREFERQUERYPROPERTIES = 0x100,
        GPS_EXTRINSICPROPERTIES = 0x200,
        GPS_EXTRINSICPROPERTIESONLY = 0x400,
        GPS_VOLATILEPROPERTIES = 0x800,
        GPS_VOLATILEPROPERTIESONLY = 0x1000,
        GPS_MASK_VALID = 0x1fff
    }

    public sealed class PropVariant : IDisposable
    {
        private PROPVARIANT value;

        private PropVariant(in PROPVARIANT value)
        {
            this.value = value;
        }

        public void Clear()
        {
            NativeMethods.PropVariantClear(ref value);
        }

        public void Dispose()
        {
            Clear();
        }

        public ushort vt
        {
            get => this.value.vt;
            set => this.value.vt = value;
        }

        public static PropVariant GetPropertyValue(
            object propertyStore,
            object propertyDescription)
        {
            NativeMethods.PSGetPropertyValue(
                propertyStore,
                propertyDescription,
                out var value);
            return new PropVariant(value);
        }

        public static PropVariant FromObject(object value)
        {
            NativeMethods.VariantToPropVariant(value, out var pv);
            return new PropVariant(pv);
        }

        public object ToObject()
        {
            return NativeMethods.PropVariantToVariant(value);
        }

        private static class NativeMethods
        {
            [DllImport("ole32.dll", ExactSpelling = true)]
            public static extern void PropVariantClear(ref PROPVARIANT pvar);

            [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true)]
            public static extern void PSGetPropertyValue(
                [MarshalAs(UnmanagedType.IUnknown)] object pps,
                [MarshalAs(UnmanagedType.IUnknown)] object ppd,
                out PROPVARIANT ppropvar);

            [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true)]
            public static extern object PropVariantToVariant(in PROPVARIANT pPropVar);

            [DllImport("propsys.dll", PreserveSig = false, ExactSpelling = true)]
            public static extern void VariantToPropVariant([In] object pVar, out PROPVARIANT pPropVar);
        }

        [StructLayout(LayoutKind.Sequential, Size = 24)]
        public struct PROPVARIANT
        {
            public ushort vt;
        }
    }
}