2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#定石 - レジストリ操作

Posted at

はじめに

C# ソフト開発時に、決まり事として実施していた内容を記載します。

テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。

  • Windows Forms - .NET Framework 4.8
  • Windows Forms - .NET 8
  • WPF - .NET Framework 4.8
  • WPF - .NET 8

レジストリ一覧

レジストリ操作の枠組みとして、ファイル一覧と同様な、レジストリ一覧について記載します。

Intel CPU の場合、32bit レジストリは Wow6432Node ですが、ARM CPU の場合 WowAA32Node も存在します。
参考 - レジストリ リダイレクター

ARM CPU Windows 利用経験がないので、以降は Intel CPU についての記載とします。

RegistryView

64bit OS の場合、HKEY_LOCAL_MACHINE\Software、および、HKEY_CLASSES_ROOT には、64bit レジストリと 32bitレジストリ(Wow6432Node)が存在します。
WOW64で動作する 32bit 動作プロセスは、HKEY_LOCAL_MACHINE\Software にアクセスすると、HKEY_LOCAL_MACHINE\Software\Wow6432Node にリダイレクトされます。
詳細 - 複数のプロセッサ アーキテクチャに対する Windows on Windows(WOW)サポートを含む Windows インストールの影響を受けるレジストリ キー

参照先 32bit プロセス リダイレクト先
HKEY_LOCAL_MACHINE\SOFTWARE HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node
HKEY_CLASSES_ROOT HKEY_CLASSES_ROOT\Wow6432Node

RegistryView を利用すると、このようなリダイレクトを抑止して、特定のレジストリを操作ができます。
下表のように、32bit プロセスから 64bit レジストリアクセス、64bit プロセスから 32bit レジストリアクセスが可能です。

RegistryView 64bitOSで64bit動作 64bitOSで32bit動作 32bitOS
Default 64bit レジストリ 32bit レジストリ 32bit レジストリ
Registry32 32bit レジストリ 32bit レジストリ 32bit レジストリ
Registry64 64bit レジストリ 64bit レジストリ 32bit レジストリ

WIN32API - RegOpenKeyEx / RegCreateKeyEx 利用時は、KEY_WOW64_32KEY もしくは KEY_WOW64_64KEY を指定となります。

参考 - 代替レジストリ ビューへのアクセス

サンプルコード

RegistryView を指定することで、該当プロセス 32bit/64bit によらず、特定レジストリの一覧を取得するコードです。

using Microsoft.Win32;
string regPath = @"Software\Microsoft\MediaPlayer";
using (var regBase = RegistryKey.OpenBaseKey(
  RegistryHive.LocalMachine, RegistryView.Registry64))
{
  int count = RegValueUnderRegKey(regBase, regPath);
}
private int RegValueUnderRegKey(RegistryKey regBase, string regPath)
{
  int count = 0;

  // 参照のみの場合 OpenSubKey、更新する場合 CreateSubKey
  // CreateSubKey を利用すると 対象サブキーが存在しないと作成します
  using (var regKey = regBase.OpenSubKey(regPath))
  {
    if (regKey == null)
    {
      // 対象サブキーなし - TODO
    }
    else
    {
      // 対象サブキーの子サブキーも処理する場合はコメントを外す
      // var keyNames = regKey.GetSubKeyNames().OrderBy(x => x);
      // foreach (var keyName in keyNames)
      // {
      //   count += RegValueUnderRegKey(regBase, regPath + "\\" +  keyName);
      // }

      // 変数一覧
      var names = regKey.GetValueNames().OrderBy(x => x);
      foreach (var name in names)
      {
        // TODO
        // var obj = regKey.GetValue(name);       // 値取得
        // var kind = regKey.GetValueKind(name);  // データ型取得

        count++;
      }
    }
  }
  return count;
}

取得

レジストリにもアクセス権限が存在します。
基本的には、下記アクセス権限が設定されています。

対象 管理者権限所有ユーザ 一般ユーザ
HKEY_CURRENT_USER フルコントロール フルコントロール
上記以外 フルコントロール 読み取り権限のみ

RegistryKey.OpenSubKey(読み取り)、RegistryKey.CreateSubKey(更新)で、該当操作のアクセス権限がない場合は、SecurityException の例外が発生します。

一部レジストリは、一般ユーザに対して読み取り権限も与えていないサブキーもあります。

HKEY_CURRENT_USER 以外

設定されている変数/値は、システム全体に対しての設定値で、システムツール、および、インストーラで設定されます。
一般ユーザの場合、基本的に読み取り権限のみとなっているので、RegistryKey.OpenSubKey を用いてアクセスします。

サブキー/変数の存在有無を確認後、対象のデータ型で値を確認という手順は以下の通りです。

  • RegistryKey.OpenSubKey で null 取得の場合は、対象サブキーなし
  • RegistryKey.GetValue で null 取得の場合は、対象変数なし
  • RegistryKey.GetValueKind でデータ型を取得して、該当データ型で値確認
string path = @"Software\Foo\Bar";
// 参照のみなので OpenSubKey
using (var regKey = Registry.LocalMachine.OpenSubKey(path))
{
  if (regKey == null)
  {
    // 対象サブキーなし - TODO
  }
  else
  {
    // 変数/値の確認
    CheckRegValue(regKey, "Hoge");
    CheckRegValue(regKey, "piyo");
  }
}
// 変数/値の確認
private void CheckRegValue(RegistryKey regKey, string name)
{
  // 対象変数の値取得
  var obj = regKey.GetValue(name);
  if (obj == null)
  {
    // 対象変数なし - TODO
  }
  else
  {
    // 対象変数のデータ型取得
    var kind = regKey.GetValueKind(name);
    if (kind == RegistryValueKind.String
     || kind == RegistryValueKind.ExpandString)
    {
      var value = obj as string;
       // TOOO
    }
    else if (kind == RegistryValueKind.DWord)
    {
      var value = (Int32)obj;
      // TODO
    }
    else if (kind == RegistryValueKind.QWord)
    {
      var value = (Int64)obj;
      // TODO
    }
    else if (kind == RegistryValueKind.MultiString)
    {
      var values = obj as string [];
      // TODO
    }
    else if (kind == RegistryValueKind.Binary)
    {
      var values = obj as byte [];
      // TODO
    }
    else
    {
      // TODO
    }
  }
}

サブキー/変数の存在が前提で、データ型が自明の場合は、シンプルな記述にできます。

string path = @"Software\Foo\Bar";
using (var regKey = Registry.LocalMachine.OpenSubKey(path))
{
  // システムツール、および、インストーラでサブキー/変数/値を設定するので、
  // 対象サブキー/変数(設定値)は存在することが前提
  if (regKey != null)
  {
    var hoge =  regKey.GetValue("Hoge") as string;
    var piyo =  (Int32)regKey.GetValue("Piyo");
  }
}

サブキー内で参照する変数が1つの場合には、下記コードにもできます。

string root = "HKEY_LOCAL_MACHINE";  // Registry.LocalMachine
string path = root + "\\" + @"Software\Foo\Bar";
var hoge =  Registry.GetValue(path, "Hoge", null) as string;

HKEY_CURRENT_USER

パッケージソフトで、ユーザ毎の設定情報を、レジストリ KEY_CURRENT_USER で管理するケースがあります。
このようなケースでは、

  • 対象サブキーに更新権限がある
  • 対象サブキーが存在しない場合は、作成しても構わない
  • 変数のデータ型が自明
  • 変数が存在しない場合は、既定値取得

という仕様で良いので、下記コードを用いることがありました。

string path = @"Software\Foo\Bar";
// 対象サブキーがない場合は、作成しても構わないので CreateSubKey 利用
using (var regKey = Registry.CurrentUser.CreateSubKey(path))
{
  // サブキー作成しているので、サブキーが存在しない場合の既定値も以下で対処可能
  var hoge =  regKey.GetValue("Hoge", "既定値") as string;
  var piyo =  (Int32)regKey.GetValue("Piyo", 512);  // 512 は Int32 の規定値
}

サブキー内で参照する変数が1つの場合には、下記コードにもできます。

string root = "HKEY_CURRENT_USER";  // Registry.CurrentUser
string path = root + "\\" + @"Software\Foo\Bar";
string defaultValue = "既定値";
var hoge =  (Registry.GetValue(path, "Hoge", defaultValue) ?? defaultValue) as string;

Registry.GetValue では RegistryKey.OpenSubKey を内部で実施するので、該当サブキーが存在しない場合は、サブキーを作成せず null 取得となります。
このため、null 合体演算子を利用して、既定値の記載が必要となります。

ExpandString

RegistryValueKind.ExpandString(REG_EXPAND_SZ:環境変数を含む展開可能な文字列値)の扱いは特殊です。

RegistryValueOptions.DoNotExpandEnvironmentNames を指定しない場合、環境変数を展開した値を取得します。
環境変数を展開せず取得するには、RegistryValueOptions.DoNotExpandEnvironmentNames の指定が必要で、このように取得した値の環境変数展開は ExpandEnvironmentVariables で可能です。

string path = "Environment";
string name = "TEMP";

using (var regKey = Registry.CurrentUser.OpenSubKey(path))
{
  if (regKey != null)
  {
    if (regKey.GetValueKind(name) == RegistryValueKind.ExpandString)
    {
      // 環境変数を展開して取得
      var foo = regKey.GetValue(name) as string;
      // → C:\Users\hoge\AppData\Local\Temp

      // 環境変数を展開せず取得
      var bar = regKey.GetValue(name, null,
                         RegistryValueOptions.DoNotExpandEnvironmentNames) as string;
      // → %USERPROFILE%\AppData\Local\Temp
      if (bar != null)
      {
        // 環境変数を展開
        var baz = Environment.ExpandEnvironmentVariables(bar);
        // → C:\Users\hoge\AppData\Local\Temp
      }
    }
  }
}

設定

RegistryKey.OpenSubKey でなく、RegistryKey.CreateSubKey を利用して、RegistryValueKind に一致した型の値を設定します。

string path = @"Software\Foo\Bar";
// 更新時は CreateSubKey 利用
using (var regKey = Registry.CurrentUser.CreateSubKey(path))
{
  regKey.SetValue("Hoge", "値1", RegistryValueKind.String);
  regKey.SetValue("Piyo", 512", RegistryValueKind.DWord);
}

サブキー内で設定する変数が1つの場合には、下記コードにもできます。

string root = "HKEY_CURRENT_USER";  // Registry.CurrentUser
string path = root + "\\" + @"Software\Foo\Bar";
Registry.SetValue(path, "Hoge", "値1", RegistryValueKind.String);

Registry.SetValue では RegistryKey.CreateSubKey を内部で実施するので、該当サブキーが存在しない場合は、サブキー作成した後、値を設定します。

削除

変数削除

string path = @"Software\Foo\Bar";
// 更新時は CreateSubKey 利用
using (var regKey = Registry.CurrentUser.CreateSubKey(path))
{
  regKey.DeleteValue("hoge", false);   // 対象が存在しない場合、例外を発生させない
  regKey.DeleteValue("piyo", false);   // 対象が存在しない場合、例外を発生させない
}

サブキー削除

サブキー削除は、RegistryKey.DeleteSubKey、RegistryKey.DeleteSubKeyTree の2つのメソッドがあります。
前者は、対象サブキーに子サブキーが存在すると削除に失敗しますが、後者は、サブキーとその子サブキーを再帰的に削除します。

string path = @"Software";
// 更新時は CreateSubKey 利用
using (var regKey = Registry.CurrentUser.CreateSubKey(path))
{
  regKey.DeleteSubKeyTree("Foo", false); // 対象が存在しない場合、例外を発生させない
}

セキュリティ権限付与

パッケージソフトとして、更新可能な設定情報をレジストリで管理する場合、HKEY_CURRENT_USER\Software を用いて、ユーザ毎に管理することが一般的です。

しかしながら、全てのユーザを対象とする設定情報で、かつ、アプリから更新も可能という要求仕様に出会うこともありました。
この場合、HKEY_LOCAL_MACHINE\Software を利用することになりますが、更新には管理者権限が必要となります。
アプリに管理者権限付与するのは望ましくないので、インストール処理(管理者権限有り)で、対象レジストリキーを EveryOne フルコントールとすることもありました。

そもそも、レジストリ利用でなく、可搬性が高い XML 利用でも良いはずですが、、、
XML 利用するにしても、EveryOne フルコントールのフォルダを用意することになるので、手間は同じですね。

using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.Win32;
string regPath = @"Software\Foo\Bar\Baz";
using (var regBase = RegistryKey.OpenBaseKey(
  RegistryHive.LocalMachine, RegistryView.Registry32))
{
  SetFullAccessForRegistry(regBase, regPath);
}
private bool SetFullAccessForRegistry(RegistryKey regBase, string regPath)
{
  bool bResult = false;
  
  // 対象キーが存在しなければ作成する
  using (var regKey =  regBase.CreateSubKey(regPath))
  {
    // Everyone
    var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
    if (sid != null)
    {
      var account = sid.Translate(typeof(NTAccount)) as NTAccount;
      if (account != null)
      {
        RegistrySecurity rs = regKey.GetAccessControl();
        RegistryAccessRule rar = new RegistryAccessRule(
                    account.ToString(),
                    RegistryRights.FullControl,
                    InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                    PropagationFlags.None,
                    AccessControlType.Allow);
        // AddAccessRule と SetAccessRule は仕様が異なる
        rs.AddAccessRule(rar);
        regKey.SetAccessControl(rs);

        // 正常終了
        bResult = true;
      }
    }
  }
  return bResult;
}

2
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?