【C#】StdRegProvを使用してレジストリを操作する(90%ぐらいの完全版)

最近IEの右クリックメニュー拡張に手を出していて、その辺の絡みでレジストリを自分で作る必要が出てきました。

当然面倒なのである程度自動化しようと思ったら前に書いたやつが読み込みしか出来なかったので、もうStdRegProvのメソッドを全部ラップしてしまいました。

コードを読む前に

そもそもRegistryKeyクラス (Microsoft.Win32)があるので、わざわざこれを使うメリットがどれだけあるのかって言うと、結構微妙です。

単純に私はMicrosoft.Win32 名前空間が嫌いなだけですので、そんなポリシーを持たない人は普通にそっちの方が使いやすいと思います。

ざっくりとした解説

WMIのStdRegProvクラスを使ってレジストリにアクセスします。C#からWMIを使う場合は前にも説明しましたSystem.Management 名前空間のクラスを使用します。

今回使用するのはManagementClassクラスです。

using(var mc = new ManagementClass("StdRegProv"))
{
    //TODO: invoke method
}

基本的には準備するのはこれだけです。

もう一度StdRegProvクラスのドキュメントに目を通してみると、メンバーやらプロパティやらは一切持っておらず、メソッドのみが規定されています。

ですので、今回はManagementClassを介してメソッドを呼び出し、その結果を受け取ることだけに注意します。

ManagementClassからメソッドを発行する

ためしにGetStringValueメソッドをManagementClassから発行してみましょう。

まずはManagementObject.GetMethodParametersメソッドを呼び出してManagementBaseObjectを手に入れます。

using(var mc = new ManagementClass("StdRegProv"))
{
    // ManagementClass#GetMethodParameters(methodName)を呼び出すことで
    // 該当method用のプロパティを持ったオブジェクトを取得出来る
    var inParam = mc.GetMethodParameters("GetStringValue");
}

で、取得したManagementBaseObjectに対し、メソッドのパラメータを設定していきます。

using (var mc = new ManagementClass("StdRegProv"))
// ManagementClass#GetMethodParameters(methodName)を呼び出すことで
// 該当method用のプロパティを持ったオブジェクトを取得出来る
using (var inParam = mc.GetMethodParameters("GetStringValue"))
{
    
    // HKEY_CLASSES_ROOT
    inParam["hDefKey"] = (uint)2147483648;
    
    // なんか適当にレジストリのパス
    inParam["sSubKeyName"] = "";
    
    // ValueNameをstring.Emptyにすると規定値をとってくる
    inParam["sValueName"] = string.Empty;
    
}

これでメソッドを投げる準備は完了です。ManagementObject.InvokeMethodメソッドを使ってメソッドを発行しましょう。

using (var mc = new ManagementClass("StdRegProv"))
// ManagementClass#GetMethodParameters(methodName)を呼び出すことで
// 該当method用のプロパティを持ったオブジェクトを取得出来る
using (var inParam = mc.GetMethodParameters("GetStringValue"))
{
    
    // HKEY_CLASSES_ROOT
    inParam["hDefKey"] = (uint)2147483648;
    
    // なんか適当にレジストリのパス
    inParam["sSubKeyName"] = "";
    
    // ValueNameをstring.Emptyにすると規定値をとってくる
    inParam["sValueName"] = string.Empty;
    
    using (var outParam = mc.InvokeMethod("GetStringValue", inParam, null))
    {
        // TODO:check return value
    }
}

InvokeMethodでもまたManagementBaseObjectが返って来ます。今度は返り値となる値がプロパティに入っているので、そいつを取り出しましょう。

using (var inParam = mc.GetMethodParameters("GetStringValue"))
{
    
    // HKEY_CLASSES_ROOT
    inParam["hDefKey"] = (uint)2147483648;
    
    // なんか適当にレジストリのパス
    inParam["sSubKeyName"] = "";
    
    // ValueNameをstring.Emptyにすると規定値をとってくる
    inParam["sValueName"] = string.Empty;
    
    using (var outParam = mc.InvokeMethod("GetStringValue", inParam, null))
    {
        // returnValueが0以外の場合は何らかのエラーが発生している
        var returnValue = (uint)outParam["returnValue"];
        if (returnValue != 0) throw new ApplicationException(returnValue.ToString());
        
        var value = (stirng)outParam["sValue"];
        
        Console.WriteLine(value);
    }
}

まぁ一つ二つラップするぐらいならちゃんと全部書いてもいいんですが、全部これを書くとなると面倒なので上手いことヘルパーメソッドを作ってしまったほうがいいです。

コード

それじゃあ実際のコードです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management;

namespace Registry
{

    /// <summary>レジストリのルートキー</summary>
    public enum DefKey : uint
    {
        HKEY_CLASSES_ROOT = 2147483648,
        HKEY_CURRENT_USER = 2147483649,
        HKEY_LOCAL_MACHINE = 2147483650,
        HKEY_USERS = 2147483651,
        HKEY_CURRENT_CONFIG = 2147483653,
    }

    /// <summary>CheckAccessで確認したい権限</summary>
    public enum Required : uint
    {
        KEY_QUERY_VALUE = 1,
        KEY_SET_VALUE = 2,
        KEY_CREATE_SUB_KEY = 4,
        KEY_ENUMERATE_SUB_KEYS = 8,
        KEY_NOTIFY = 16,
        KEY_CREATE = 32,
        DELETE = 65536,
        READ_CONTROL = 131072,
        WRITE_DAC = 262144,
        WRITE_OWNER = 524288,
    }

    /// <summary>レジストリの値の種類</summary>
    public enum RegType : int
    {
        REG_SZ = 1,
        REG_EXPAND_SZ = 2,
        REG_BINARY = 3,
        REG_DWORD = 4,
        REG_MULTI_SZ = 7,
        REG_QWORD = 11,
    }

    /// <summary>レジストリ情報のラッパークラス</summary>
    public class Registry
    {
        public DefKey Root { get; set; }
        public List<Required> Required { get; set; }
        public string Path { get; set; }
        public string Name { get; set; }
        public Dictionary<string, RegType> Values { get; set; }
        public string[] SubKeys { get; set; }
        public List<Registry> Tree { get; set; }
    }

    /// <summary>レジストリをWMI経由で操作するラッパークラス</summary>
    public class RegistryController : IDisposable
    {
        private readonly ManagementClass _mc;
        private DefKey _root;

        /// <summary>
        /// StdRegProvと接続し、レジストリを操作する
        /// ルートキーはHKEY_CLASSES_ROOT
        /// </summary>
        public RegistryController()
        {
            _mc = new ManagementClass("StdRegProv");
            _root = DefKey.HKEY_CLASSES_ROOT;
        }

        /// <summary>
        /// StdRegProvと接続し、レジストリを操作する
        /// </summary>
        /// <param name="root">ルートキー</param>
        public RegistryController(DefKey root)
        {
            _mc = new ManagementClass("StdRegProv");
            _root = root;
        }

        /// <summary>
        /// ルートキーの変更
        /// </summary>
        /// <param name="root">変更先ルートキー</param>
        public void ChangeRoot(DefKey root)
        {
            _root = root;
        }

        /// <summary>
        /// レジストリの情報をラップしたクラスの取得
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public Registry GetRegistry(string path)
        {
            var rootRegistry = new Registry
            {
                Root = _root,
                Path = path,
                Name = path.Split('\\').Last(),
                Required = GetRequireds(path),
                Values = GetValueNameAndTypes(path),
                SubKeys = GetKeys(path),
                Tree = new List<Registry>(),
            };

            if (rootRegistry.SubKeys != null)
            {
                foreach (var subkey in rootRegistry.SubKeys)
                {
                    rootRegistry.Tree.Add(GetRegistry(path + @"\" + subkey));
                }
            }

            return rootRegistry;
        }

        /// <summary>
        /// アクセス権限確認
        /// </summary>
        /// <param name="path">確認したいキー</param>
        /// <param name="required">確認したい権限</param>
        /// <returns></returns>
        public bool CheckAccess(string path, Required required)
        {
            // ドキュメントでは'lRequired'となっているけど実際には'uRequired'である。
            // どんなミスだ。
            return InvokeMethod<bool>("CheckAccess", "bGranted"
                , sSubKeyName => path, uRequired => (uint)required);
        }

        /// <summary>
        /// アクセス権限取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="requireds"></param>
        /// <returns></returns>
        public List<Required> GetRequireds(string path)
        {
            var requiredList = new List<Required>();

            var requireds = new Required[]
            {
                Required.DELETE,
                Required.KEY_CREATE,
                Required.KEY_CREATE_SUB_KEY,
                Required.KEY_ENUMERATE_SUB_KEYS,
                Required.KEY_NOTIFY,
                Required.KEY_QUERY_VALUE,
                Required.KEY_SET_VALUE,
                Required.READ_CONTROL,
                Required.WRITE_DAC,
                Required.WRITE_OWNER,
            };

            foreach (var require in requireds)
            {
                if (CheckAccess(path, require)) requiredList.Add(require);
            }

            return requiredList;
        }

        /// <summary>
        /// サブキー作成
        /// </summary>
        /// <param name="path">作成するキーのパス</param>
        public void CreateSubKey(string path)
        {
            InvokeMethod("CreateKey", (object sSubKeyName) => path);
        }

        /// <summary>
        /// サブキー削除
        /// </summary>
        /// <param name="path">削除するキーのパス</param>
        public void DeleteSubKey(string path)
        {
            InvokeMethod("DeleteKey", (object sSubKeyName) => path);
        }

        /// <summary>
        /// 名前付の値削除
        /// </summary>
        /// <param name="path">削除するキーのパス</param>
        /// <param name="valueName">削除する値の名称</param>
        public void DeleteValue(string path, string valueName)
        {
            InvokeMethod("DeleteValue", (object sSubKeyName) => path, sValueName => valueName);
        }

        /// <summary>
        /// キーの列挙
        /// </summary>
        /// <returns>設定したルートのキー一覧</returns>
        public string[] GetKeys()
        {
            return InvokeMethod<string[]>("EnumKey", "sNames", sSubKeyName => string.Empty);
        }

        /// <summary>
        /// サブキーの列挙
        /// </summary>
        /// <param name="path">サブキーを取得するキーのパス</param>
        /// <returns>サブキーの配列</returns>
        public string[] GetKeys(string path)
        {
            return InvokeMethod<string[]>("EnumKey", "sNames", sSubKeyName => path);
        }

        /// <summary>
        /// 値の名称と種類の列挙
        /// </summary>
        /// <param name="path">値の名称と種類を列挙したいキーのパス</param>
        /// <returns>値の名称がKey、種類がValueのDictionary</returns>
        public Dictionary<string, RegType> GetValueNameAndTypes(string path)
        {
            return InvokeMethod<Dictionary<string, RegType>>("EnumValues", outParam =>
            {
                // EnumValuesでは規定(Default)値を取得してこない
                // 多分REG_SZ以外ないからだと思うけどちょっとね…。
                var names = (string[])outParam["sNames"];
                var types = (int[])outParam["Types"];
                var nameTypeDic = new Dictionary<string, RegType>();

                if (names == null) return nameTypeDic;

                for (int i = 0; i < names.Length; i++)
                {
                    nameTypeDic.Add(names[i], (RegType)types[i]);
                }

                return nameTypeDic;

            }, sSubKeyName => path);
        }

        /// <summary>
        /// REG_BINARY取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="valueName"></param>
        /// <returns></returns>
        public byte[] GetBinaryValue(string path, string valueName = "")
        {
            return InvokeMethod<byte[]>("GetBinaryValue", "uValue", sSubKeyName => path, sValueName => valueName);
        }

        /// <summary>
        /// REG_BINARY設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <param name="valueName"></param>
        public void SetBinaryValue(string path, byte[] value, string valueName = "")
        {
            InvokeMethod("SetBinaryValue", (object sSubKeyName) => path, sValueName => valueName, uValue => value);
        }

        /// <summary>
        /// REG_DWORD取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="valueName"></param>
        /// <returns></returns>
        public uint GetDWORDValue(string path, string valueName = "")
        {
            return InvokeMethod<uint>("GetDWORDValue", "uValue", sSubKeyName => path, sValueName => valueName);
        }

        /// <summary>
        /// REG_DWORD設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <param name="valueName"></param>
        public void SetDWORDValue(string path, uint value, string valueName = "")
        {
            InvokeMethod("SetDWORDValue", (object sSubKeyName) => path, sValueName => valueName, uValue => value);
        }

        /// <summary>
        /// REG_EXPAND_SZ取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="valueName"></param>
        /// <returns></returns>
        public string GetExpandedStringValue(string path, string valueName = "")
        {
            return InvokeMethod<string>("GetExpandedStringValue", "sValue", sSubKeyName => path, sValueName => valueName);
        }

        /// <summary>
        /// REG_EXPAND_SZ設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <param name="valueName"></param>
        public void SetExpandedStringValue(string path, string value, string valueName = "")
        {
            InvokeMethod("SetExpandedStringValue", (object sSubKeyName) => path, sValueName => valueName, sValue => value);
        }

        /// <summary>
        /// REG_MULTI_SZ取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="valueName"></param>
        /// <returns></returns>
        public string[] GetMultiStringValue(string path, string valueName = "")
        {
            return InvokeMethod<string[]>("GetMultiStringValue", "sValue", sSubKeyName => path, sValueName => valueName);
        }

        /// <summary>
        /// REG_MULTI_SZ設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <param name="valueName"></param>
        public void SetMultiStringValue(string path, string[] value, string valueName = "")
        {
            InvokeMethod("SetMultiStringValue", (object sSubKeyName) => path, sValueName => valueName, sValue => value);
        }

        /// <summary>
        /// REG_QWORD取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="valueName"></param>
        /// <returns></returns>
        public UInt64 GetQWORDValue(string path, string valueName = "")
        {
            return InvokeMethod<UInt64>("GetQWORDValue", "uValue", sSubKeyName => path, sValueName => valueName);
        }

        /// <summary>
        /// REG_QWORD設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <param name="valueName"></param>
        public void SetQWORDValue(string path, UInt64 value, string valueName = "")
        {
            InvokeMethod("SetQWORDValue", (object sSubKeyName) => path, sValueName => valueName, uValue => value);
        }

        //public void GetSecurityDescriptor(string path)
        //{
        //    throw new NotImplementedException();
        //}

        //public void SetSecurityDescriptor(string path)
        //{
        //    throw new NotImplementedException();
        //}

        /// <summary>
        /// REG_SZ取得
        /// </summary>
        /// <param name="path"></param>
        /// <param name="valueName"></param>
        /// <returns></returns>
        public string GetStringValue(string path, string valueName = "")
        {
            return InvokeMethod<string>("GetStringValue", "sValue", sSubKeyName => path, sValueName => valueName);
        }

        /// <summary>
        /// REG_SZ設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="value"></param>
        /// <param name="valueName"></param>
        public void SetStringValue(string path, string value, string valueName = "")
        {
            InvokeMethod("SetStringValue", (object sSubKeyName) => path, sValueName => valueName, sValue => value);
        }

        /// <summary>
        /// StdRegProvのメソッドを呼ぶためのパラメータを作成
        /// </summary>
        /// <param name="methodName">メソッド名</param>
        /// <param name="args">パラメータ</param>
        /// <returns></returns>
        private ManagementBaseObject CreateInParam(string methodName, params Expression<Func<object, object>>[] args)
        {
            // ManagementClass#GetMethodParameters(methodName)を呼び出すことで
            // 該当method用のプロパティを持ったオブジェクトを取得出来る
            var inParam = _mc.GetMethodParameters(methodName);

            // hDefKey(レジストリのルート)は必ず記述する必要がある
            inParam["hDefKey"] = (uint)_root;

            foreach (var arg in args)
            {
                var argValue = arg.Compile().Invoke(null);
                if (argValue == null) continue;

                // パラメータの設定
                inParam[arg.Parameters[0].Name] = argValue;
            }

            return inParam;
        }

        /// <summary>
        /// StdRegProv#InvokeMethod (Void)
        /// </summary>
        /// <param name="methodName">メソッド名</param>
        /// <param name="args">メソッドに渡すパラメータ</param>
        private void InvokeMethod(string methodName, params Expression<Func<object, object>>[] args)
        {
            using (var inParam = CreateInParam(methodName, args))
            using (var outParam = _mc.InvokeMethod(methodName, inParam, null))
            {
                // returnValueが0以外の場合は何らかのエラーが発生している
                var returnValue = (uint)outParam["returnValue"];
                if (returnValue != 0) throw new ApplicationException(returnValue.ToString());
            }
        }

        /// <summary>
        /// StdRegProv#InvokeMethod (T)
        /// </summary>
        /// <typeparam name="T">メソッドから取得するプロパティの型</typeparam>
        /// <param name="methodName">メソッド名</param>
        /// <param name="outPropertyName">メソッド実行後、返り値を取得したいプロパティ名</param>
        /// <param name="args">メソッドへ渡すパラメータ</param>
        /// <returns></returns>
        private T InvokeMethod<T>(string methodName, string outPropertyName, params Expression<Func<object, object>>[] args)
        {
            using (var inParam = CreateInParam(methodName, args))
            using (var outParam = _mc.InvokeMethod(methodName, inParam, null))
            {
                var returnValue = (uint)outParam["returnValue"];
                if (returnValue != 0 && returnValue != 2) throw new ApplicationException(returnValue.ToString());

                return (T)outParam[outPropertyName];
            }
        }

        /// <summary>
        /// StdRegProv#InvokeMethod (T)
        /// </summary>
        /// <typeparam name="T">メソッドから取得するプロパティの型</typeparam>
        /// <param name="methodName">メソッド名</param>
        /// <param name="outPropety">メソッド実行後、返ってきたManagementBaseObjectからTを作成するFunction</param>
        /// <param name="args">メソッドへ渡すパラメータ</param>
        /// <returns></returns>
        private T InvokeMethod<T>(string methodName, Func<ManagementBaseObject, T> outPropety, params Expression<Func<object, object>>[] args)
        {
            using (var inParam = CreateInParam(methodName, args))
            using (var outParam = _mc.InvokeMethod(methodName, inParam, null))
            {
                var returnValue = (uint)outParam["returnValue"];
                if (returnValue != 0 && returnValue != 2) throw new ApplicationException(returnValue.ToString());

                return (T)outPropety(outParam);
            }
        }

        public void Dispose()
        {
            if (_mc != null) _mc.Dispose();
        }

        ~RegistryController()
        {
            if (_mc != null) _mc.Dispose();
        }
    }
}

SecurityDescriptorに絡むところ以外を全部ラップしたせいで長くなってしまってるんですが、基本的にはすべてRegistryController#InvokeMethodを経由します。

実際にはこんな風に使います。

using (var controller = new RegistryController(DefKey.HKEY_CURRENT_USER))
{
    var menuExt = controller.GetRegistry(@"Software\Microsoft\Internet Explorer\MenuExt");

    // testと言うValueにTESTと言う文字列を設定
    //controller.SetStringValue(menuExt.Path, "TEST", "test");

    // testSubKeyと言うサブキーを作成
    //controller.CreateSubKey(menuExt.Path + @"\testSubKey");

    // testSubKeyと言うサブキーを削除
    controller.DeleteSubKey(menuExt.Path + @"\testSubKey");

    // testと言うValueを削除
    controller.DeleteValue(menuExt.Path, "test");

}

GetRegistryを呼び出すとその瞬間のTreeを取りに行くので、LINQでがりがりまわせば色々できます。まぁ、サンプルでやる気力はないんですが。

SecurityDescriptorを無視した理由

大して使いもしないのに作らなきゃいけないものが多すぎるからです。欲しけりゃ自分で実装するか大人しくRegistryKeyクラスを使いましょう。

まとめ

久しぶりにあんまり頭を使わないコーディングでした。