C# - C:\ProgramData と Everyoneフルコントール

## はじめに

本記事では、Windows の `C:\ProgramData` フォルダについて記載します。
その後、関連する情報として、フォルダのアクセス権限を Everyone フルコントールにする 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

::: note
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
:::

## データ格納フォルダ

### 特殊フォルダ

https://learn.microsoft.com/ja-jp/dotnet/api/system.environment.specialfolder

プロダクト（アプリケーション）のデータ格納フォルダとして、以下が定義されています。

- Environment.SpecialFolder.CommonApplicationData
  - すべてのユーザが使用するプロダクト固有のデータを格納する共通フォルダ
  - 通常は全ユーザで書き込み可能ですが、特定の条件下では管理者権限が必要となる場合もあります
  - 実体バス：`C:\ProgramData`
  - 隠しフォルダとして扱われているため、エクスプローラでは「隠しファイル：表示」にしないと表示されません
- Environment.SpecialFolder.ApplicationData
  - カレントユーザのプロダクト固有のデータを格納するフォルダ
  - 該当ユーザにフルコントールが付与されています
  - 実体バス：`C:\Users\<ユーザ名>\AppData\Roaming`

```csharp
string programdata = Environment.GetFolderPath(
                       Environment.SpecialFolder.CommonApplicationData);
string appdata = Environment.GetFolderPath(
                       Environment.SpecialFolder.ApplicationData);
Console.WriteLine($"CommonApplicationData: {programdata}");
Console.WriteLine($"ApplicationData: {appdata}");
```

各プロダクトでは、これらのフォルダ下に `会社名\プロダクト名` もしくは `プロダクト名` というフォルダを作成して、各種データを格納します。

```
会社名：Hoge、プロダクト名：FooBar
 C:\ProgramData\Hoge\Foobar
 C:\Users\<ユーザ名>\AppData\Roaming\Hoge\Foobar
```

データ格納フォルダをインストールフォルダと別に用意する理由のひとつは、プロダクトのアンインストール時にデータを維持できるようにするためです。

::: note
インストール時に選択した情報を CommonApplicationData に格納して、アンインストール時にデータを保持することで、再インストール時に前回選択した情報を再利用することができます。 
CommonApplicationData を利用するプロダクトの場合、アンインストール時に、保存されているデータを削除するかを確認して、削除を行うこともあります。
:::

### CommonApplicationData

一般的には、インストーラ（UAC により昇格された管理者権限で実行）によって、CommonApplicationData 配下にプロダクト用のフォルダを作成し、用途に応じたサブフォルダを作成します。
これらは、用途にあわせて、次のようなアクセス権限を設定します。

| 対象サブフォルダの用途                | アクセス権限               |
| ----------------------------------- | -------------------------- |
| 構成ファイルやテンプレートなど参照のみのデータを格納   | 一般ユーザは読み取りのみ可 |
| ログや共有データなど更新するデータを格納 | Everyone フルコントール    |


Windows の基本的な設計方針としては、ApplicationData を使用してユーザごとに変化するデータ（設定ファイル、キャッシュなど）を管理することが推奨されています。
この場合、インストール時に作成された初期設定を CommonApplicationData 配下に格納し、アプリケーション初回起動時にユーザの ApplicationData にコピーして、以降はユーザごとに個別に管理するといった方法が考えられます。

一方で、CommonApplicationData を用いて、すべてのユーザが共有して更新可能なデータを扱うこともあります。
この場合、インストーラによって対象フォルダやそのサブフォルダに Everyone フルコントールを明示的に付与することで、全ユーザでの書き込みを保証することができます。

## Everyone フルコントール

```csharp
// 指定フォルダを Everyone フルコントール
// 権限不足とならないように管理者権限で実行が望ましい
private void SetFullAccessForFolder(string folder)
{
  // フォルダ存在確認
  if (!Directory.Exists(folder))
  {
    try
    {
      // 対象フォルダ存在しないので作成
      Directory.CreateDirectory(folder);
    }
    catch (UnauthorizedAccessException)
    {
      // TODO - 権限不足
      Console.WriteLine("フォルダ作成エラー: 権限不足");
      return;
    }
    catch (SecurityException)
    {
      // TODO - セキュリティ例外
      Console.WriteLine("フォルダ作成エラー: セキュリティ例外");
      return;
    }
  }

  try
  {
    // Everyone
    var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);

    // 現在のアクセス権取得
    var directoryInfo = new DirectoryInfo(folder);
    var security = directoryInfo.GetAccessControl();
    // LINQ を利用してすでに Everyone フルコントロール権限があるか判定
    bool bAlready = security
            .GetAccessRules(true, true, typeof(SecurityIdentifier))
            .Cast<FileSystemAccessRule>()
            .Any(rule => rule.IdentityReference.Value == everyone.Value
              && (rule.FileSystemRights & FileSystemRights.FullControl)
                                           == FileSystemRights.FullControl);
    // 未付与時にフルコントロール付与
    if (!bAlready)
    {
      var newRule = new FileSystemAccessRule(
            everyone, FileSystemRights.FullControl,
            InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
            PropagationFlags.None, AccessControlType.Allow);
      // アクセス権限付与
      security.AddAccessRule(newRule);
      directoryInfo.SetAccessControl(security);
    }
  }
  catch (UnauthorizedAccessException)
  {
    // TODO - 権限不足
    Console.WriteLine("アクセス権制御エラー: 権限不足");
    return;
  }
  catch (SecurityException)
  {
    // TODO - セキュリティ例外
    Console.WriteLine("アクセス権制御エラー: セキュリティ例外");
    return;
  }
}
```

::: note
directoryInfo.GetAccessControl という記述にすることで、.NET Framework と .NET で利用可能なサンプルコードとしています。
このコードは、.NET Framework と .NET で機能は等価ですが、実装が異なります。
.NET Framework の場合、[DirectoryInfo インスタンスメソッド](https://learn.microsoft.com/ja-jp/dotnet/api/system.io.directory.getaccesscontrol) として提供されていて、直接呼び出す形態です。
.NET の場合、DirectoryInfo インスタンスメソッドは廃止され、[FileSystemAclExtensions](https://learn.microsoft.com/ja-jp/dotnet/api/system.io.filesystemaclextensions) による拡張メソッドとして提供されます。

```csharp
public static DirectorySecurity GetAccessControl(
                                 this DirectoryInfo directoryInfo)
```

ここで this DirectoryInfo という記述が、拡張メソッドであることを示しています。
つまり、DirectoryInfo のインスタンスに対して直接呼び出せるように見えますが、実際には FileSystemAclExtensions クラスの静的メソッドです。

FileSystemAclExtensions は、.NET Framework では標準サポートされていません。
※ [NuGet Gallery | System.IO.FileSystem.AccessControl](https://www.nuget.org/packages/system.io.filesystem.accesscontrol/) を .NET Framework に導入すれば利用可能となります。

directoryInfo.SetAccessControl も同様です。

.NET 限定、もしくは、`NuGet Gallery | System.IO.FileSystem.AccessControl` を .NET Framework に導入するならば、FileSystemAclExtensions を明示的に利用したほうが万人向けのコードかもしれません。

```csharp
＜省略＞
// 現在のアクセス権取得 - FileSystemAclExtensions 利用可
var dirInfo = new DirectoryInfo(folder);
var security = FileSystemAclExtensions.GetAccessControl(dirInfo);

＜省略＞
// アクセス権限付与 - FileSystemAclExtensions 利用可
security.AddAccessRule(newRule);
FileSystemAclExtensions.SetAccessControl(dirInfo, security);
```
:::


以下のように利用します。

```csharp
string programdata = Environment.GetFolderPath(
                       Environment.SpecialFolder.CommonApplicationData);
string company = "Hoge";            // TODO
string product = "FooBar";          // TODO
string settings = "CommonSettings"; // TODO

SetFullAccessForFolder($@"{programdata}\{company}\{product}\{settings}");
```