読者です 読者をやめる 読者になる 読者になる

syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

ProgramDataフォルダーの使い道

MicrosoftWindows Vistaで追加・変更されたいくつかのフォルダーや新しいセキュリティモデルに関して、下記のガイドラインを公開しています(英語)。

※2016-03-21追記:
上記はすでにリンク切れしてしまっているようです。ガイドラインどこ行った……

現状では英語版しかなく、公式に日本語に訳した版をぜひ作成して欲しいんですが、これはWindowsデスクトップ アプリケーションを作るときに非常に重要なガイドラインで、「XPまではきちんと動作していたのにVista以降のWindows OSで動かなくなったアプリケーション」というのはたいていこのガイドラインを無視して好き勝手にフォルダーやファイルを作成しているものが多いです。特にVistaでセキュリティ強化のために追加されたUAC(User Account Control)やWRP(Windows Resource Protection)のもとで適切に動作するアプリを設計するためには、データ(アプリ設定、ユーザー設定、ユーザードキュメントなど)の保存場所というのは最も基本的かつ重要な検討事項になります。
本当はWindows XPの頃にも同様のガイドラインは存在したのですが、たぶんこれを守って開発されたアプリっていうのはAdobeとかを除いて皆無に近いのではなかろうか。
話を元に戻しますが、インストーラーでアプリの実行プログラム(EXEやDLL)をインストールするフォルダーというのは通例%ProgramFiles%や%ProgramFiles(x86)%の配下になり、その下に会社名やアプリ名のフォルダー階層を作ってインストールします。で、基本的に以降はこのフォルダーの中身は読み取り専用になるわけですが、昔(Win9x時代)はアプリ設定をこの場所にINIファイルなどで直接保存してしまうものが多く、そういったアプリをVista以降のOSで、特権モードに昇格していない通常のユーザー権限(AdministratorsでもUsersでも同じ)にて実行すると、設定ファイルの読み書き処理がOSによって仮想ストレージ(Virtual Store)にリダイレクトされます(%LocalAppData%\VirtualStore)。これはOSによる一時救済措置で、不作法なアプリでも一応動作できますよ、というものであり、本来はアプリ設定やユーザー設定はWindowsログインユーザーごとのプロファイルが保存される専用の場所(フォルダーorレジストリ)にルールを守って作成するべきです*1。実行プログラムファイルのような読み取り専用データと、設定ファイルやドキュメントファイル、ゲームのセーブデータファイルのようなユーザーデータを分けて管理することで、重要なデータのバックアップもしやすくなります。

Windowsログインユーザーごとのアプリ設定の保存

こちらに関しては話は比較的簡単で、
ローカルコンピューターのみでユーザーごとに設定データを管理する場合は

%LoalAppData%
もしくは
%UserProfile%\AppData\Local
(CSIDL_LOCAL_APPDATA、FOLDERID_LocalAppDataに相当。
.NETではSystem.Environment.SpecialFolder.LocalApplicationData)

を使います。たいていの場合はこちらで十分です。Visual Studioで作成した.NETアプリの設定ファイル(user.config)もデフォルトでこの場所に保存されます。
ドメイン参加しているなど、Windowsユーザーアカウントがネットワーク共有される場合に、すべての端末で設定を共有したい場合は、

%AppData%
もしくは
%UserProfile%\AppData\Roaming
(CSIDL_APPDATA、FOLDERID_RoamingAppDataに相当。
.NETではSystem.Environment.SpecialFolder.ApplicationData)

を使います。WindowsXboxでゲーマーアカウントを共有できるGames for Windows/Windows Live/Xbox Liveなんかはこのあたりが関連するはずです。
実際にはこれらのルートフォルダーの下に会社名やアプリ名のフォルダー階層を作って設定ファイルを保存します。

Windowsログインユーザー全体で共有されるアプリ設定の保存

問題なのはこちらで、実はガイドラインにはローカルコンピューターで全ユーザーに共有されるデータの保存場所のルートとして

%ProgramData%
もしくは
%AllUsersProfile%
(CSIDL_COMMON_APPDATA、FOLDERID_ProgramDataに相当。
.NETではSystem.Environment.SpecialFolder.CommonApplicationData)

を挙げていますが、そもそも全ユーザー共通の設定というのが誰でも書き換え可能だったらマズいと思いませんか?
インストーラーによってフォルダーorファイルに対して管理者権限グループ(Administrators)のみに書き込み権限を付与して、管理者権限を持つユーザーのみが設定を書き換えられる」という形にするならば少しはマシになりますが、それでもある管理者ユーザーAが自由に共通アプリ設定を書き換えることができて、別のユーザーBは(管理者権限を持つにもかかわらず)その共通アプリ設定を使わなければいけない、というのは正直どうかと思います。
要するに、個人的には全ユーザー共通設定(ハードウェア通信設定など)というのは、管理者アカウントだからといっていつでもホイホイ変更できて良いものではなく、インストール時に一括設定してしまうか、特権モードに移行して後から設定を書き換える機能(シールドアイコンを付けるヤツです)をアプリに持たせるか、もしくはグループポリシーみたいなもので一括管理するようにするべきであって、%ProgramData%その他をうかつに使うべきではない、という考えです。

ちなみにゲーム開発者向けには下記のようなガイドラインが存在します。

上記ページでは、%ProgramData%の説明として、「ユーザーが作成でき、すべてのユーザーが読み取ることができるゲーム ファイル。書き込みアクセス権は、ファイルの作成者 (所有者) のみに付与されます。」と記述されていますが、これが曲者です(後述)。

余談:フォルダーパスを取得するAPI

%LocalAppData%などに相当する実際のパス文字列をアプリケーションのプログラムコードで取得する際、ネイティブC/C++の場合は下記のWin32 APIを使います。

XP対応が必要な場合はSHGetSpecialFolderPath()もしくはSHGetFolderPath()を使いますが、今後はSHGetKnownFolderPath()でもよいでしょう。なお、APIヘッダーをインクルードする前にWINVERおよび_WIN32_IEシンボルを適切に定義していないと使用できません。

.NETの場合はP/Invokeを使うか、System.Environment.GetFolderPath()メソッドを使いますが、.NETのバージョンによって使用可能なSystem.Environment.SpecialFolder列挙体のメンバーが若干異なるため注意が必要です。

環境変数を展開して文字列を取得するAPIも用意されています。

そのほか、%UserProfile%\AppDataや%ProgramData%は隠しフォルダーになっていることにも注意が必要です。これらの場所に保存された設定ファイルやログファイルの回収を、バグ解析などの目的でエンドユーザーに依頼する場合はきちんと配慮しましょう。

本当にProgramDataフォルダーを使うべきか?

で、今自分が仕事で遭遇しているのは、まさにその共通アプリ設定やログファイルを%ProgramData%配下に保存するような設計にしたアプリを押しつけてテストしてくれとかのたまう開発者がいること*2。そして何より最悪なのはそのログファイルが固定ファイル名なのにもかかわらず、ログファイルへの書き込み権限がファイルのオーナー(インストール直後に最初にアプリを実行してファイルを作成したユーザー)だけにしかないということ。つまりログファイルの保存先にProgramDataフォルダーを使おうとしている思想というか仕様そのものがバグってます。たった一人のWindowsログインユーザーしか正常に実行できないアプリとか、わざわざ%ProgramFiles%配下にインストールする意味がありますか? というかたった一人のログインユーザーのために%ProgramData%にファイルを作るくらいならば、最初から各ユーザー固有データとして保存すればいいだけの話。あるいはもしシングルユーザーを想定した、旧態依然とした時代遅れのアプリをどうしてもWindowsで動かしたかったら、そもそもインストーラーとか%ProgramFiles%を使わずにユーザードキュメントフォルダー(%UserProfile%配下)にxcopyで配置しろ、ということです。
実は他のアプリの仕様をみても、ProgramDataに書き換え可能なデータを保存しているものは少数派です。基本的に(32bitアプリと64bitアプリで共有される)SDKヘッダーやライブラリ、リソース、ヘルプなどの読み取り専用データをProgramDataに配置していたり、そもそもProgramDataフォルダーそのものを使っていない。
確かに産業系などの特殊用途では、Microsoftが想定しているセキュリティモデルはそぐわないこともありますが、できるだけWindowsの作法に従っておくほうが幸せになれることは確かです。一般論で言えば、俺様ルールを勝手に適用する前に、対象プラットフォームに関する下調べをきちんと行なってから設計・実装するのは鉄則です。最後に自分が好きな言葉を添えておきましょう。

When in Rome do as the Romans do.
(郷に入っては郷に従え!)

*1:ちなみにValveのSteamはかなりイレギュラーで、%ProgramFiles(x86)%\Steam配下に関してUsersグループにフルコントロール権限を与えることでおもいっきりユーザーデータを作成しまくっていますが、Steamクライアントを簡単にハッキングされかねない危険思想なので良い子の開発者は真似しないようにしましょう。

*2:後で判明したんですが、このアプリはUACが全部OFFになってないとまともに動作しないというひどくお粗末な設計でした。Vistaがリリースされてから何年経っていると思ってるのか……