はじめに
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;
}