インストーラを自分で管理者昇格させるWindows UACと共存の実際
セットアップexeが更新時にAccess is deniedで落ちる。マニフェストでなくコードで自己昇格を選んだ理由と、実機で確かめたUAC共存のポイントを綴る。
「アクセスが拒否されました」から始まった
役所内の職員PCに常駐する更新通知クライアントがある。Goで実装した小さな常駐アプリで、その導入と更新を担うのがセットアップexeだ。ある日、更新を走らせると Access is denied(アクセスが拒否されました)で失敗するようになった。
原因は素直だった。このツールはインストール・更新・アンインストールのいずれでも、Cドライブ直下のフォルダにファイルを書いたり消したりする。システム的な場所への書き込みには管理者権限が要る。ところがセットアップexeは、ふつうにダブルクリックされると通常ユーザーの権限で起動する。権限が足りないまま書き込もうとして弾かれていた、というだけの話だ。
問題は「どう権限を得るか」である。方法は大きく二つある。実行ファイルにマニフェスト(requireAdministrator)を焼き込んでOSに「これは管理者権限で起動せよ」と宣言させるか、あるいは起動後にコード自身が昇格するか。結論から言うと、後者の自己昇格を選んだ。
なぜマニフェストでなく自己昇格にしたか
理想論だけならマニフェスト埋め込みが王道だ。OSが起動前に判断してくれるので、コードは常に管理者で動いていると仮定できる。
だが現実のビルド環境が設計を決めた。このexeは別OS上での純粋なクロスビルドで作っている。実行ファイルにWindowsのマニフェストを焼き込むには相応のツールチェーンが要るが、それをこのパイプラインに持ち込んでいなかった。マニフェストのために別OSのビルド作法をひとつ増やすか、コードだけで完結させるか——天秤にかけて、コードで閉じる方を取った。
自己昇格の骨子はこうだ。起動直後に自分が管理者トークンで動いているかを判定し、そうでなければ ShellExecute の runas 動詞で自分自身を起動し直す。このとき初めてUACのプロンプトが出る。ユーザーが承認すれば、今度は管理者権限を持った二代目プロセスが立ち上がり、元のプロセスは静かに退場する。
main():
if 後処理モード(親から呼ばれたクリーンアップ):
実行して return # ← 昇格判定より前に抜ける
if not 自分は管理者():
ShellExecute("runas", self, args) # UACプロンプト、昇格して再起動
return # 元プロセスは終了
# ここから先は必ず管理者
install() / update() / uninstall()
実機でしか分からなかった四つのこと
作法として書けても、実際に効くかは端末で動かすまで確信できない。確かめて初めて腑に落ちた点が四つある。
(1) 互換フラグと明示 runas は共存する。 PCA(プログラム互換性アシスタント)の余計な介入を抑えるため、ある互換フラグを立てている。「互換フラグで昇格扱いにしつつ、コードでも runas する」と昇格ループになりそうに思えたが、ならなかった。明示的な runas はフルトークンを別経路で付与されるため、二重にはならない。ここは机上で悩むより一度動かした方が早かった。
(2) 昇格するのはセットアップ系だけ。 権限が要るのは導入・更新・削除の工程であって、常駐している本体アプリは通知を出すだけで昇格を要らない。昇格対象を絞り、本体は通常権限のまま動かす。
(3) 昇格済みの親から呼ばれる後処理は、昇格判定より前で抜ける。 アンインストールの仕上げクリーンアップは、すでに管理者になっている親プロセスが子として起動する。ここで再び昇格判定に入ると二度目のUACが出てしまう。だから後処理モードは関数の先頭で見分けて即座に処理し、昇格ロジックに触れさせない(擬似コードで判定より上に置いた理由がこれだ)。
(4) 標準ユーザーで昇格できない端末では導入できない。 UACで管理者資格を入力できない運用の端末では、そもそもこの操作が成立しない。これは実装で救う話ではなく仕様であり、マニュアルに注意書きとして明記した。「できないことを明記する」のも設計判断のうちだ。
AIとの協働作業での学び
得た教訓は三つに畳める。
- 権限が要る操作は必要な工程だけを昇格させる。全体を常時管理者で走らせるのは過剰で、危うい。攻撃面も操作ミスの被害も、管理者で動く時間に比例して膨らむ。
- プラットフォームの作法(UACや昇格)は、実機での挙動確認を伴って初めて確信できる。昇格ループが起きるか、二重プロンプトが出ないか——ドキュメントを読んで安心するより、一台で動かして目で見る方が確かだった。
- ビルド環境の制約が設計選択を左右することがある。マニフェストか自己昇格かは純粋な技術的優劣だけでは決まらず、「いま持っているツールチェーンで無理なく閉じるか」という現実が答えを引き寄せた。制約を嘆くより、制約に合う解を選ぶ。小さなセットアップexeひとつでも、そういう判断の積み重ねでできている。