potisanのプログラミングメモ

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

C# Windowsの拡張子の関連付けレジストリキーのハンドルと名前を取得するコード

Windows 10でレジストリから拡張子の関連付けキーのハンドルとその名前を取得するコードです。拡張子の関連付け情報を持つキーハンドルはWin32 APIAssocQueryKeyW関数で取得できますが、.NET Core 3.1の標準機能ではレジストリキーハンドル(HKEY)の名前や場所を取得する方法がありません。ここではWin32 APINtQueryKey関数を使用してこれを解決しています。

using System;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;

class Program
{
    static void Main()
    {
        // .ps1はFileExtsの設定が優先
        var name1 =  QueryAssocKeyNamesForFile(".ps1");
        // > ASSOCKEY_SHELLEXECCLASS    null
        // > ASSOCKEY_CLASS             "\REGISTRY\USER\<ユーザーのSID>_Classes\Applications\pwsh.exe"
        // > ASSOCKEY_BASECLASS         "\REGISTRY\USER\<ユーザーのSID>_Classes\Applications\pwsh.exe"
        // > ASSOCKEY_APP               "\REGISTRY\USER\<ユーザーのSID>_Classes\Applications\pwsh.exe"

        // .exeはHKEY_CLASSES_ROOT直下
        var name2 = QueryAssocKeyNamesForFile(".exe");
        // > ASSOCKEY_SHELLEXECCLASS    null
        // > ASSOCKEY_CLASS             null
        // > ASSOCKEY_BASECLASS         "\\REGISTRY\\USER\\<ユーザーのSID>_Classes\\exefile"
        // > ASSOCKEY_APP               "\\REGISTRY\\USER\\<ユーザーのSID>_Classes\\exefile"

        // .exeにxxxコマンドは存在しない(はず)
        var name3 = QueryAssocKeyNamesForFile(".exe", "xxx");
        // > ASSOCKEY_SHELLEXECCLASS    null
        // > ASSOCKEY_CLASS             null
        // > ASSOCKEY_BASECLASS         "\\REGISTRY\\USER\\<ユーザーのSID>_Classes\\exefile"
        // > ASSOCKEY_APP               null
    }

    static (string, string, string, string) QueryAssocKeyNamesForFile(string assoc, string extra = null)
    {
        using var keyHandle1 = QueryAssocKeyForFile(ASSOCKEY_SHELLEXECCLASS, assoc, extra);
        using var keyHandle2 = QueryAssocKeyForFile(ASSOCKEY_CLASS, assoc, extra);
        using var keyHandle3 = QueryAssocKeyForFile(ASSOCKEY_BASECLASS, assoc, extra);
        using var keyHandle4 = QueryAssocKeyForFile(ASSOCKEY_APP, assoc, extra);
        return (
            QueryRegistryKeyName(keyHandle1),
            QueryRegistryKeyName(keyHandle2),
            QueryRegistryKeyName(keyHandle3),
            QueryRegistryKeyName(keyHandle4));
    }

    private static SafeRegistryHandle QueryAssocKeyForFile(
        int key,
        string assoc,
        string extra = null)
    {
        var hr = NativeMethods.AssocQueryKeyW(
            ASSOCF_INIT_FOR_FILE, key, assoc, extra,
            out var keyHandle);
        return hr == 0 ? keyHandle : null;
    }

    private static string QueryRegistryKeyName(SafeRegistryHandle handle)
    {
        if (handle == null)
            return null;
        var lstatus = NativeMethods.NtQueryKey(handle,
            KEY_INFORMATION_CLASS.KeyNameInformation,
            null, 0, out var bufferSize);
        if (lstatus != STATUS_BUFFER_TOO_SMALL)
            return null;

        var buffer = new byte[bufferSize];
        lstatus = NativeMethods.NtQueryKey(handle,
            KEY_INFORMATION_CLASS.KeyNameInformation,
            buffer, bufferSize, out bufferSize);
        if (lstatus != 0)
            return null;

        var nameLength = BitConverter.ToUInt32(buffer, 0);
        return Encoding.Unicode.GetString(buffer, 4, buffer.Length - 4);
    }

    private static class NativeMethods
    {
        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
        public static extern int AssocQueryKeyW(
            int flags,
            int key,
            [In] string pszAssoc,
            [In] string pszExtra,
            out SafeRegistryHandle phkeyOut);

        [DllImport("ntdll.dll")]
        public static extern int NtQueryKey(
            SafeRegistryHandle KeyHandle,
            KEY_INFORMATION_CLASS KeyInformationClass,
            byte[] KeyInformation,
            uint Length,
            out uint ResultLength);
    }

    private const int ASSOCF_INIT_FOR_FILE = 0x00002000;

    private const int ASSOCKEY_SHELLEXECCLASS = 0;
    private const int ASSOCKEY_APP = 1;
    private const int ASSOCKEY_CLASS = 2;
    private const int ASSOCKEY_BASECLASS = 3;

    private const int STATUS_BUFFER_TOO_SMALL = unchecked((int)0xC0000023);

    public enum KEY_INFORMATION_CLASS : int
    {
        KeyNameInformation = 3,
    }
}