potisanのプログラミングメモ

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

C#&Win API レジストリキーハンドルからキー名を取得する

動作確認環境:C# 8.0(.NET Core 3.1)、9.0(.NET 5.0)

C#ではWin32 APIで取得したレジストリキーハンドルHKEYからMicrosoft.Win32.RegistryKeyを作成するとキーの名前(Nameプロパティ)が空の文字列""になります。ZwQueryKey関数に準ずるNtQueryKey関数を使用すればキーの名前を取得できます。

ただし、NtQueryKeyは非公開APIであり仕様変更の可能性があります。また取得される名前はリダイレクト前の名前です。

トップレベステートメントC# 9)

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

const uint KEY_READ = 0x20019;

using var keyHandle = NativeMethods.RegOpenKeyExW(
    NativeMethods.HKEY_CLASSES_ROOT, ".txt", 0, KEY_READ);
var name = QueryRegistryKeyName(keyHandle);
// \REGISTRY\USER\<ログオンユーザーのSID>_Classes\.txt

[return: MaybeNull]
static string QueryRegistryKeyName(SafeRegistryHandle handle)
{
    const int STATUS_BUFFER_TOO_SMALL = unchecked((int)0xC0000023);

    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,
        sizeof(uint), buffer.Length - sizeof(uint));
}

static class NativeMethods
{
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    public static extern int RegOpenKeyExW(
        IntPtr hKey,
        [In] string lpSubKey,
        uint ulOptions,
        uint samDesired,
        out SafeRegistryHandle phkResult);

    [return: MaybeNull]
    public static SafeRegistryHandle RegOpenKeyExW(
        IntPtr hKey,
        [In] string lpSubKey,
        uint ulOptions,
        uint samDesired)
    {
        // エラー時はhandleがIntPtr.Zeroなのでnullを返す。
        RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, out var handle);
        return handle;
    }

    public static IntPtr HKEY_CLASSES_ROOT => new IntPtr((uint)0x80000000);

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

enum KEY_INFORMATION_CLASS : int
{
    KeyNameInformation = 3,
}

コンソールアプリケーションの場合はプロジェクトファイルに<TargetFramework>net5-windows</TargetFramework><UseWindowsForms>true</UseWindowsForms>を追加します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
</Project>

Main関数(C# 8)

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

class Program
{
    static void Main()
    {
        using var keyHandle = NativeMethods.RegOpenKeyExW(
            HKEY_CLASSES_ROOT, ".txt", 0, KEY_READ);
        var name = QueryRegistryKeyName(keyHandle);
        // \REGISTRY\USER\<ログオンユーザーのSID>_Classes\.txt
    }

    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("advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        public static extern int RegOpenKeyExW(
            IntPtr hKey,
            [In] string lpSubKey,
            uint ulOptions,
            uint samDesired,
            out SafeRegistryHandle phkResult);

        public static SafeRegistryHandle RegOpenKeyExW(
            IntPtr hKey,
            [In] string lpSubKey,
            uint ulOptions,
            uint samDesired)
        {
            // エラー時はhandleがIntPtr.Zeroなのでnullを返す。
            RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, out var handle);
            return handle;
        }

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

    private static IntPtr HKEY_CLASSES_ROOT => new IntPtr((uint)0x80000000);
    private const uint KEY_READ = 0x20019;
    private const int STATUS_BUFFER_TOO_SMALL = unchecked((int)0xC0000023);

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

コンソールアプリケーションの場合はプロジェクトファイルに<UseWindowsForms>true</UseWindowsForms>を追加します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
</Project>