NGW開発BLOG
← 記事一覧へ
·#017

インストーラを自分で管理者昇格させるWindows UACと共存の実際

セットアップexeが更新時にAccess is deniedで落ちる。マニフェストでなくコードで自己昇格を選んだ理由と、実機で確かめたUAC共存のポイントを綴る。

「アクセスが拒否されました」から始まった

役所内の職員PCに常駐する更新通知クライアントがある。Goで実装した小さな常駐アプリで、その導入と更新を担うのがセットアップexeだ。ある日、更新を走らせると Access is denied(アクセスが拒否されました)で失敗するようになった。

原因は素直だった。このツールはインストール・更新・アンインストールのいずれでも、Cドライブ直下のフォルダにファイルを書いたり消したりする。システム的な場所への書き込みには管理者権限が要る。ところがセットアップexeは、ふつうにダブルクリックされると通常ユーザーの権限で起動する。権限が足りないまま書き込もうとして弾かれていた、というだけの話だ。

問題は「どう権限を得るか」である。方法は大きく二つある。実行ファイルにマニフェスト(requireAdministrator)を焼き込んでOSに「これは管理者権限で起動せよ」と宣言させるか、あるいは起動後にコード自身が昇格するか。結論から言うと、後者の自己昇格を選んだ。

なぜマニフェストでなく自己昇格にしたか

理想論だけならマニフェスト埋め込みが王道だ。OSが起動前に判断してくれるので、コードは常に管理者で動いていると仮定できる。

だが現実のビルド環境が設計を決めた。このexeは別OS上での純粋なクロスビルドで作っている。実行ファイルにWindowsのマニフェストを焼き込むには相応のツールチェーンが要るが、それをこのパイプラインに持ち込んでいなかった。マニフェストのために別OSのビルド作法をひとつ増やすか、コードだけで完結させるか——天秤にかけて、コードで閉じる方を取った。

自己昇格の骨子はこうだ。起動直後に自分が管理者トークンで動いているかを判定し、そうでなければ ShellExecuterunas 動詞で自分自身を起動し直す。このとき初めて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との協働作業での学び

得た教訓は三つに畳める。

  1. 権限が要る操作は必要な工程だけを昇格させる。全体を常時管理者で走らせるのは過剰で、危うい。攻撃面も操作ミスの被害も、管理者で動く時間に比例して膨らむ。
  2. プラットフォームの作法(UACや昇格)は、実機での挙動確認を伴って初めて確信できる。昇格ループが起きるか、二重プロンプトが出ないか——ドキュメントを読んで安心するより、一台で動かして目で見る方が確かだった。
  3. ビルド環境の制約が設計選択を左右することがある。マニフェストか自己昇格かは純粋な技術的優劣だけでは決まらず、「いま持っているツールチェーンで無理なく閉じるか」という現実が答えを引き寄せた。制約を嘆くより、制約に合う解を選ぶ。小さなセットアップexeひとつでも、そういう判断の積み重ねでできている。