potisanのプログラミングメモ

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

C# Windows 10の拡張子の関連付け情報を文字列で取得するコードとクラス

Windows 10ではレジストリに記録される拡張子の関連付け情報が複雑で、レジストリを直接操作すると互換性が失われる可能性があります。shlwapi.dllの公開するAssocQueryStringW関数の使用でこの複雑さや互換性を解決できます。

なお、AssocQueryStringAAnsi版、IQueryAssociationsの利用でより詳細な情報を取得できます。

using System;
using System.Runtime.InteropServices;
using System.Text;
using Potisan.Windows.Shell;

class Program
{
    static void Main()
    {
        foreach (var name in Enum.GetNames<QueryAssocString.AssocStr>())
        {
            var value = QueryAssocString.Query(
                QueryAssocString.AssocFlags.None,
                Enum.Parse<QueryAssocString.AssocStr>(name),
                @".ps1");
            Console.WriteLine($"{name}: {value}");
        }
        Console.WriteLine();
        /*
        Command:
        Executable: "C:\Program Files\PowerShell\7\pwsh.exe" -NoExit "%1"
        FriendlyDocName: C:\Program Files\PowerShell\7\pwsh.exe
        FriendlyAppName: PS1 ファイル
        NoOpen: pwsh
        ShellNewValue:
        DDECommand:
        DDEIfExec:
        DDEApplication:
        DDETopic: pwsh
        InfoTip: System
        QuickTip: prop:System.ItemTypeText;System.Size;System.DateModified
        TileInfo: prop:System.ItemTypeText;System.Size;System.DateModified
        ContentType: prop:System.ItemTypeText;System.Size;System.DateModified
        DefaultIcon:
        ShellExtension: C:\Program Files\PowerShell\7\pwsh.exe
        DropTarget:
        DelegateExecute:
        SupportedURIProtocols:
        ProgID: file:
        AppID: Applications\pwsh.exe
        AppPublisher:
        AppIconReference: Microsoft Corporation
        Max: C:\Program Files\PowerShell\7\pwsh.exe
        */

        foreach (var name in Enum.GetNames<QueryAssocString.AssocStr>())
        {
            var value = QueryAssocString.Query(
                QueryAssocString.AssocFlags.None,
                Enum.Parse<QueryAssocString.AssocStr>(name),
                @".exe");
            Console.WriteLine($"{name}: {value}");
        }
        /*
        Command:
        Executable: "%1" %*
        FriendlyDocName: %1
        FriendlyAppName: アプリケーション
        NoOpen:
        ShellNewValue:
        DDECommand:
        DDEIfExec:
        DDEApplication:
        DDETopic: %1
        InfoTip: System
        QuickTip: prop:System.FileDescription;System.Company;System.FileVersion;System.DateCreated;System.Size
        TileInfo: prop:System.ItemTypeText;System.Size;System.DateModified
        ContentType: prop:System.FileDescription;System.Company;System.FileVersion;System.DateCreated;System.Size
        DefaultIcon:
        ShellExtension: %1
        DropTarget:
        DelegateExecute:
        SupportedURIProtocols:
        ProgID: file:
        AppID: exefile
        AppPublisher:
        AppIconReference:
        Max: %1
        */
    }
}

namespace Potisan.Windows.Shell
{
    public static class QueryAssocString
    {
        public static string Query(
            AssocFlags flags,
            AssocStr str,
            string assoc,
            string extra = null,
            bool throwsException = false)
        {
            const int ASSOCF_NOTRUNCATE = 0x00000020;

            var hr = NativeMethods.AssocQueryStringW(
                (int)flags,
                (int)str, assoc, extra,
                null, out var bufferSize);
            if (hr < 0)
            {
                if (throwsException) Marshal.ThrowExceptionForHR(hr);
                else return null;
            }

            var buffer = new StringBuilder((int)bufferSize);
            NativeMethods.AssocQueryStringW(
                (int)flags | ASSOCF_NOTRUNCATE,
                (int)str, assoc, extra,
                buffer, out bufferSize);
            if (hr < 0)
            {
                if (throwsException) Marshal.ThrowExceptionForHR(hr);
                else return null;
            }

            return buffer.ToString();
        }

        private static class NativeMethods
        {
            [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
            public static extern int AssocQueryStringW(
                int flags,
                int str,
                [In] string pszAssoc,
                [In] string pszExtra,
                StringBuilder pszOut,
                out uint pcchOut);
        }

        private const int MAX_PATH = 260;

        public enum AssocFlags : int
        {
            /// <summary>ASSOCF_NONE</summary>
            None = 0x00000000,
            /// <summary>ASSOCF_INIT_NOREMAPCLSID</summary>
            InitNoRemapCLSID = 0x00000001,
            /// <summary>ASSOCF_INIT_BYEXENAME</summary>
            InitByExeName = 0x00000002,
            /// <summary>ASSOCF_OPEN_BYEXENAME</summary>
            OpenByExeName = 0x00000002,
            /// <summary>ASSOCF_INIT_DEFAULTTOSTAR</summary>
            InitDefaultToStar = 0x00000004,
            /// <summary>ASSOCF_INIT_DEFAULTTOFOLDER</summary>
            InitDefaultToFolder = 0x00000008,
            /// <summary>ASSOCF_NOUSERSETTINGS</summary>
            NoUserSettings = 0x00000010,
            // ASSOCF_NOTRUNCATE</summary>
            /// <summary>ASSOCF_VERIFY = 0x00000040,
            Verify = 0x00000040,
            /// <summary>ASSOCF_REMAPRUNDLL</summary>
            RemapRunDLL = 0x00000080,
            /// <summary>ASSOCF_NOFIXUPS</summary>
            NoFixups = 0x00000100,
            /// <summary>ASSOCF_IGNOREBASECLASS</summary>
            IgnoreBaseClass = 0x00000200,
            /// <summary>ASSOCF_INIT_IGNOREUNKNOWN</summary>
            InitIgnoreUnknown = 0x00000400,
            /// <summary>ASSOCF_INIT_FIXED_PROGID</summary>
            InitFixedProgID = 0x00000800,
            /// <summary>ASSOCF_IS_PROTOCOL</summary>
            IsProtocol = 0x00001000,
            /// <summary>ASSOCF_INIT_FOR_FILE</summary>
            InitForFile = 0x00002000
        }

        public enum AssocStr : int
        {
            /// <summary>ASSOCSTR_COMMAND</summary>
            Command,
            /// <summary>ASSOCSTR_EXECUTABLE</summary>
            Executable,
            /// <summary>ASSOCSTR_FRIENDLYDOCNAME</summary>
            FriendlyDocName,
            /// <summary>ASSOCSTR_FRIENDLYAPPNAME</summary>
            FriendlyAppName,
            /// <summary>ASSOCSTR_NOOPEN</summary>
            NoOpen,
            /// <summary>ASSOCSTR_SHELLNEWVALUE</summary>
            ShellNewValue,
            /// <summary>ASSOCSTR_DDECOMMAND</summary>
            DDECommand,
            /// <summary>ASSOCSTR_DDEIFEXEC</summary>
            DDEIfExec,
            /// <summary>ASSOCSTR_DDEAPPLICATION</summary>
            DDEApplication,
            /// <summary>ASSOCSTR_DDETOPIC</summary>
            DDETopic,
            /// <summary>ASSOCSTR_INFOTIP</summary>
            InfoTip,
            /// <summary>ASSOCSTR_QUICKTIP</summary>
            QuickTip,
            /// <summary>ASSOCSTR_TILEINFO</summary>
            TileInfo,
            /// <summary>ASSOCSTR_CONTENTTYPE</summary>
            ContentType,
            /// <summary>ASSOCSTR_DEFAULTICON</summary>
            DefaultIcon,
            /// <summary>ASSOCSTR_SHELLEXTENSION</summary>
            ShellExtension,
            /// <summary>ASSOCSTR_DROPTARGET</summary>
            DropTarget,
            /// <summary>ASSOCSTR_DELEGATEEXECUTE</summary>
            DelegateExecute,
            /// <summary>ASSOCSTR_SUPPORTED_URI_PROTOCOLS</summary>
            SupportedURIProtocols,
            /// <summary>ASSOCSTR_PROGID</summary>
            ProgID,
            /// <summary>ASSOCSTR_APPID</summary>
            AppID,
            /// <summary>ASSOCSTR_APPPUBLISHER</summary>
            AppPublisher,
            /// <summary>ASSOCSTR_APPICONREFERENCE</summary>
            AppIconReference,
            /// <summary>ASSOCSTR_MAX</summary>
            Max
        }
    }
}