Windows Vista 以降,User Account Control (UAC) と言う機能が導入されましたが,これによって,注意しておかないと一部の処理が失敗すると言う事例が発生するようになりました.例えば,レジストリの操作に関しては,HKEY_LOCAL_MACHINE や HKEY_CLASSES_ROOT 以下への 書き込み は admin 権限がないと失敗してしまいます(面倒な事に,「管理ユーザ」であっても通常はこれらの値を変更する権限が付与されていないようで失敗します).
UAC での動的な昇格に関しては,Windows Vista の新しい UAC の機能 (セキュリティ) でコードが例外を出す場合の対処について 【Part 2】 で言及がなされていました.しかし,runas で実行すると WindowsXP でも以下のようなダイアログが表示されてしまうので「Windows Vista 以降の UAC の権限昇格が必要な場合のみダイアログを表示する(WindowsXP では何も表示せずにそのまま実行する)」と言う UI が実現できませんでした.
結局,今のところ以下のような手順で回避する事にしています.
- UAC が有効な場合に権限昇格が必要な処理だけをまとめて一つ(or 必要な数だけ)の関数にする.
- 関数(群)を実行するだけのプログラムを別に作成する.
- そのプログラムの「UAC の実行レベル」を requireAdministrator に設定する(プロジェクトのプロパティの「構成プロパティ」→「リンカ」→「マニフェストファイル」).
- 該当部分を変更する必要が出てきたら,ShellExecuteEx() で上記のプログラムを実行する.
ShellExecuteEx() 関数を用いているのは,CreateProcess() 関数では UAC の権限昇格が必要なプログラムの実行に失敗するからと言う理由です.
Win32 CreateProcess() 関数の呼び出しで、現在のプロセスより上位の実行レベルを要求するように構成されている実行可能ファイルを開始する場合、この呼び出しから UAC の昇格メカニズムは起動できません。この結果、CreateProcess() の呼び出しは失敗し、GetLastError() から ERROR_ELEVATION_REQUIRED が返されます。CreateProcess() は、呼び出し先の実行レベルが呼び出し元の実行レベル以下の場合にのみ正常に実行されます。昇格されたプロセスを生成する必要のある昇格されていないプロセスは、ShellExecute() 関数を使用して、生成する必要があります。この関数によって UAC の昇格メカニズムがシェルからトリガーされるようになります。
http://msdn.microsoft.com/ja-jp/library/bb206295(VS.85).aspx
もう少しスマート?にやる方法として,プログラムとして ShellExecute() で実行する代わりに COM オブジェクトにして目的の関数が実行されるときに権限昇格を行うようなもの (COM Elevation) があるらしいのですが,ちょっとまだうまく動いていないので保留にしています.時間が空いたらもう少し調べてみようかと思います.
UAC の動的昇格
- 実行する処理を、COM オブジェクト化する
- 呼び出す関数をエキスポートする
- 実行のトリガーとなるコントロールを、シールドアイコンで修飾する
- CoCreateInstanceAsAdmin() API 関数にて COM オブジェクトを作成する
- エキスポートしてある関数を呼び出す