【実務・中級編】【権限昇格】UAC(ユーザーアカウント制御)を検知し管理者権限へ自動昇格して再実行するブートストラップコード – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【極限のVBScript】UACを掌握せよ:カレントディレクトリ喪失と引数消失を防ぐ「完全無欠」の管理者権限昇格ブートストラップ

Windowsの運用自動化やレガシーシステムのマイグレーションにおいて、今なお現場の最前線で使われ続けるVBScript (WSH)。しかし、現代のセキュアなWindows環境(Windows 10/11、Windows Server)において、避けて通れない最大の壁がUAC(ユーザーアカウント制御)です。

インターネット上に転がっている「UAC昇格スクリプト」を安易にコピー&ペーストして使っていませんか?
それらの多くは、実務のプロダクション環境では確実にバグを引き起こす「地雷」を抱えています。

  • 「管理者権限で再起動したら、カレントディレクトリが `C:\Windows\System32` に強制変更され、相対パスで記述していたファイル処理が全滅した」
  • 「スクリプト起動時に渡したコマンドライン引数(Arguments)が、昇格後にすべて消失した」
  • 「ユーザーがUACプロンプトで『いいえ』を選んだ瞬間、無限にポップアップが出現する無限ループ(フォーク爆弾)に陥った」

本稿では、これらすべての課題を完璧にクリアし、データベース連携やファイルサーバー操作などの実務にも耐えうる、極限まで堅牢性を高めた「UAC自動昇格ブートストラップテンプレート」を提示します。なぜその設計が必要なのか、アーキテクチャの深部まで徹底的に解説します。

—

1. 既存の「簡易コード」が孕む3つの致命的欠陥

まずは、なぜよくある「簡易的な昇格コード」が業務自動化の現場で大事故を引き起こすのか、その技術的要因をロジカルに解剖します。

欠陥①:カレントディレクトリの「System32化」現象

VBScriptが `Shell.Application` の `ShellExecute` メソッドを用いて「`RunAs`」動詞で自身を再起動する際、Windowsのセキュリティ仕様により、プロセスの作業ディレクトリ(Current Directory)は強制的に `C:\Windows\System32` へリセットされます。
これにより、スクリプトと同一フォルダに配置した構成ファイル(`.ini` や `.json`、SQLite等のローカルDBファイル)を相対パスで参照している場合、すべて「ファイルが見つかりません」というエラーで異常終了します。

欠陥②:引数(Arguments)のパース崩壊と完全消失

一般ユーザー権限で `cscript.exe my-script.vbs “C:\My Path\data.txt” –verbose` のように引数付きで実行されたスクリプトが、単純に自身を再起動すると、引数の情報が一切引き継がれません。
引数を引き継ごうと単純に文字列結合(`Join`)するだけの設計では、「スペースを含むパス」を囲むダブルクォーテーションが脱落し、引数がバラバラに分解されるバグを引き起こします。

欠陥③:UACキャンセル時の「無限再帰(フォーク爆弾)」

ユーザーがUACダイアログで「いいえ(キャンセル)」を選択した場合、プログラム側で適切に制御(トラップ)をかけないと、スクリプトが「権限がないから再実行」を無限に試み、画面がフリーズするか、最悪の場合はシステムリソースを食いつぶす無限プロセス生成に陥ります。

—

2. 権限昇格ブートストラップの極限アーキテクチャ

これらの課題を克服するため、我々が実装すべき堅牢なブートストラップは以下のライフサイクルを辿る必要があります。

[一般ユーザー権限で起動]
│
├─① 管理者権限の有無を判定(net session コマンドの成否を利用)
│ ├─ [既に管理者] ────→ ③【本処理(Main)】へ移行
│ └─ [一般ユーザー] ──→ ② へ
│
├─② 再起動用パラメータの精緻な構築
│ ├─ カレントディレクトリの明示的保存
│ ├─ コマンドライン引数の完全な再シリアライズ(エスケープ処理)
│ └─ UACキャンセル時のクリーン終了ハンドリング
│
└─ [RunAsによる特権昇格実行] ─→ (再起動) ─→ [①へ戻る(今度は管理者)]

確実な管理者判定手法:`net session` の活用

よく「特定のシステムフォルダに書き込みテストをして判定する」手法が見られますが、これはディスクに不要なI/Oを発生させ、セキュリティソフトの誤検知を招くため非効率です。
Windowsにおいて、最も高速かつ確実、かつサイドエフェクトがない判定方法は、`net session` コマンドの実行です。このコマンドは管理者権限がなければ必ず終了コード `1`(アクセス拒否)を返すため、この戻り値(ErrorLevel)を捕捉するのがベストプラクティスです。

—

3. 【プロダクションコード】完全無欠の自動昇格テンプレート

以下に、実戦にそのまま投入できるテンプレートコードを示します。このコードは、上記の欠陥をすべて克服し、本処理記述エリアにあなたのロジックを書くだけで、極めて堅牢な管理者向けツールへと変貌します。

‘ ==============================================================================
‘ 極限堅牢設計:UAC自動昇格ブートストラップ テンプレート
‘ ユーザーの権限を検知し、カレントディレクトリと引数を完全維持したまま
‘ 管理者権限へ自動昇格して再実行します。
‘ ==============================================================================
Option Explicit

‘ ——————————————————————————
‘ エントリーポイント
‘ ——————————————————————————
Call Bootstrap()

Sub Bootstrap()
Dim objShell, objFSO
Set objShell = CreateObject(“WScript.Shell”)
Set objFSO = CreateObject(“Scripting.FileSystemObject”)

‘ 1. 管理者権限のチェック
If IsAdministrator() Then
‘ すでに管理者権限で実行されている場合は、カレントディレクトリを復元して本処理へ
Call RestoreCurrentDirectory(objShell, objFSO)
Call Main()
Else
‘ 一般ユーザー権限の場合、特権昇格を試みる
Call ElevatePrivileges(objShell, objFSO)
End If

‘ オブジェクトの明示的解放
Set objFSO = Nothing
Set objShell = Nothing
End Sub

‘ ——————————————————————————
‘ 関数: IsAdministrator
‘ 説明: 現在の実行プロセスが管理者権限(Administratorsグループ)か判定する
‘ ——————————————————————————
Function IsAdministrator()
Dim objShell, intExitCode
Set objShell = CreateObject(“WScript.Shell”)

‘ “net session” コマンドを実行(管理者権限がないとエラーコード 1 を返す)
‘ 画面をフラッシュさせないよう、非表示(0)かつ同期実行(True)で実行
On Error Resume Next
intExitCode = objShell.Run(“cmd.exe /c net session >nul 2>&1”, 0, True)
On Error GoTo 0

If intExitCode = 0 Then
IsAdministrator = True
Else
IsAdministrator = False
End If
Set objShell = Nothing
End Function

‘ ——————————————————————————
‘ 関数: ElevatePrivileges
‘ 説明: 自身を「RunAs」で再起動し、UAC昇格を要求する
‘ ——————————————————————————
Sub ElevatePrivileges(objShell, objFSO)
Dim objShellApp, strScriptPath, strArgs, i, strArg
Dim strOriginalDir, strCombinedArgs

Set objShellApp = CreateObject(“Shell.Application”)

‘ 自身のスクリプトのフルパスを取得
strScriptPath = WScript.ScriptFullName

‘ 現在のカレントディレクトリを保存(昇格後のプロセスのために第一引数として埋め込む)
strOriginalDir = objShell.CurrentDirectory

‘ 引数の再構築(スペース混じりの引数をダブルクォーテーションで保護する)
strArgs = “”
‘ 第一引数に「元のカレントディレクトリ」を隠し引数として強制挿入
strArgs = strArgs & ” ” & EscapeArgument(“/ORIGINAL_DIR:” & strOriginalDir)

‘ 起動時に渡された本来の引数をシリアライズ
For i = 0 To WScript.Arguments.Count – 1
strArg = WScript.Arguments(i)
strArgs = strArgs & ” ” & EscapeArgument(strArg)
Next

‘ UAC昇格を伴う再起動を実行
On Error Resume Next
‘ “runas” 動詞を用いて、WScript.exe経由で自身を起動
‘ 第4パラメータ(”runas”)がUACプロンプトをトリガーする
objShellApp.ShellExecute “wscript.exe”, Chr(34) & strScriptPath & Chr(34) & strArgs, “”, “runas”, 1

If Err.Number <> 0 Then
‘ ユーザーがUACプロンプトで「いいえ」を選んだ、または昇格に失敗した場合
MsgBox “このスクリプトを実行するには管理者権限が必要です。” & vbCrLf & _
“処理を中止します。”, vbCritical + vbSystemModal, “実行権限エラー”
WScript.Quit 1
End If
On Error GoTo 0

Set objShellApp = Nothing
‘ 昇格を要求した側の親プロセスは速やかに終了する
WScript.Quit 0
End Sub

‘ ——————————————————————————
‘ 関数: EscapeArgument
‘ 説明: 引数にスペースや特殊文字が含まれる場合、ダブルクォーテーションで安全に包む
‘ ——————————————————————————
Function EscapeArgument(strArg)
‘ 既にダブルクォーテーションで囲まれているかチェック
If InStr(strArg, ” “) > 0 Then
‘ 内部のダブルクォーテーションをエスケープ(VBScriptでは2つのダブルクォーテーションで表現)
strArg = Replace(strArg, Chr(34), Chr(34) & Chr(34))
EscapeArgument = Chr(34) & strArg & Chr(34)
Else
EscapeArgument = strArg
End If
End Function

‘ ——————————————————————————
‘ 関数: RestoreCurrentDirectory
‘ 説明: 昇格時に失われたカレントディレクトリを、第一引数の情報から復元する
‘ ——————————————————————————
Sub RestoreCurrentDirectory(objShell, objFSO)
Dim strFirstArg, strRestoredPath

If WScript.Arguments.Count > 0 Then
strFirstArg = WScript.Arguments(0)
‘ 自作の隠し引数「/ORIGINAL_DIR:」を検出
If Left(strFirstArg, 14) = “/ORIGINAL_DIR:” Then
strRestoredPath = Mid(strFirstArg, 15)
‘ 復元対象のディレクトリが存在するか検証した上で適用
If objFSO.FolderExists(strRestoredPath) Then
objShell.CurrentDirectory = strRestoredPath
End If
End If
End If
End Sub

‘ ==============================================================================
‘ メイン本処理(ここに実務ロジックを記述する)
‘ ==============================================================================
Sub Main()
Dim objShell, objFSO
Set objShell = CreateObject(“WScript.Shell”)
Set objFSO = CreateObject(“Scripting.FileSystemObject”)

‘ — 以下に管理者権限で実行したい任意のコードを記述 —

Dim strCurrentDir, strMsg
strCurrentDir = objShell.CurrentDirectory

strMsg = “管理者権限への昇格に成功しました!” & vbCrLf & vbCrLf & _
“【現在の実行権限】” & vbCrLf & ” -> Administrator” & vbCrLf & vbCrLf & _
“【復元されたカレントディレクトリ】” & vbCrLf & ” -> ” & strCurrentDir

MsgBox strMsg, vbInformation + vbSystemModal, “処理完了”

‘ —————————————————–
Set objFSO = Nothing
Set objShell = Nothing
End Sub

—

4. コードの深部解説:なぜこの設計なのか?

プログラミングにおいて、「動けばいい」コードと「プロダクションに耐えうる」コードの差は、例外処理とエッジケースへの対応力にあります。このコードが極めて堅牢である理由を解説します。

① `RestoreCurrentDirectory` による仮想セッション制御

昇格後のプロセスは、引数の先頭に仕込まれた `/ORIGINAL_DIR:[元のパス]` を解析します。

strArgs = strArgs & ” ” & EscapeArgument(“/ORIGINAL_DIR:” & strOriginalDir)

これにより、どのフォルダから実行されたかという「文脈(コンテキスト)」を確実に新しい特権プロセスに引き継がせます。この処理を行わないと、スクリプト内で `objFSO.OpenTextFile(“config.json”)` のような記述をした瞬間に、`C:\Windows\System32\config.json` を見に行ってしまい、エラーを誘発します。

② `EscapeArgument` によるコマンドライン再構成

引数を何も考えずに再結合すると、パスの中にスペース(例:`C:\Program Files\…`)があった場合、再起動時に引数が分割されてしまいます。
このコードでは、引数にスペースが含まれている場合、文字列内部のダブルクォーテーションをエスケープ処理しつつ、全体を `Chr(34)`(ダブルクォーテーション)で確実に包み直して `ShellExecute` に渡しています。これにより、引数の欠損やパースエラーを完全に防ぎます。

③ UACキャンセル時の安全なシャットダウン

ユーザーがUACのプロンプトで「いいえ」を押すと、`ShellExecute` は内部的にエラー(エラーコード 78: `ERROR_CANCELLED` など)を発生させます。
多くのスクリプトはこれらを考慮せず、`On Error Resume Next` でエラーを無視した結果、「権限がない状態でそのまま処理を続行し、結局エラーになる」、あるいは「再び昇格処理を呼んでループする」という最悪の挙動を示します。

本コードでは:

On Error Resume Next
objShellApp.ShellExecute …
If Err.Number <> 0 Then
‘ 失敗を検知して安全に終了
MsgBox …
WScript.Quit 1
End If

エラーをトラップした瞬間に、ユーザーへ親切なダイアログを表示し、ステータスコード `1` で安全に実行プロセスを自死させます。

—

5. 実務連携における極めて重要な注意点(ファイル・ネットワーク・データベース)

このブートストラップを用いてスクリプトを昇格させた場合、WSH特有の「セキュリティの仕様」による制約がいくつか発生します。これらを事前に把握しておかないと、開発の最終局面で手戻りが発生します。

注意点①:ネットワークドライブ(Map Drive)の「非表示」問題

実務において最も遭遇しやすい罠が、「一般ユーザー権限で割り当ててあるネットワークドライブ(例: `Z:\`)が、管理者権限に昇格した瞬間に見えなくなる」というWindowsの仕様です(UACのセキュリティ分離によるもの)。

  • 原因: Windowsは、同一ユーザーであっても「一般ユーザーのアクセス・トークン」と「管理者のアクセス・トークン」で、ネットワーク接続のセッションを分離しています。
  • 対策:

1. スクリプト内では `Z:\` のようなドライブレターを使用せず、UNCパス(`\\server\share\file`)を直接指定する。
2. レジストリ `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System` に `EnableLinkedConnections` (DWORD: 1) を設定してセッション間でドライブを共有する(ただし、クライアント環境ごとの設定変更が必要となるため、UNCパスの利用が最も現実的です)。

注意点②:環境変数の変化

管理者(SYSTEMまたは特権を付与されたAdministrator)として実行されるため、一部の環境変数(特に `%TEMP%` や `%USERPROFILE%`)が、期待している一般ユーザーのものから変化する場合があります。
ユーザー固有のフォルダにファイルを書き出す必要がある場合は、環境変数に依存せず、親プロセスから引数経由でパスを渡すか、`WScript.Shell` の `SpecialFolders` コレクションから明示的に取得してください。

—

6. まとめ:マスターピースとしてのブートストラップ

自動化ツールの開発において、「ユーザーの環境に依存せず、常に期待通りに動くこと」こそが、最高峰のエンジニアリングです。

今回紹介したブートストラップは、ただ単に「権限を上げる」だけのコードではありません。WindowsのUAC仕様、プロセス間通信(引数の受け渡し)、ファイルシステムのパス解決を緻密に計算し尽くした「プロ仕様のスケルトン(骨格)」です。

今後の業務効率化ツール開発において、管理者権限が必要なすべてのVBScriptにこのテンプレートを組み込んでください。これにより、エラーの発生しない、真に堅牢で信頼されるシステム運用が実現することをお約束します。

タイトルとURLをコピーしました