【一時プロファイル検出とクリーンアップ】WMI (`Win32_UserProfile`) による不要ユーザー環境の動的診断
開発現場やインフラ運用において、Windowsサーバーや仮想デスクトップ(VDS/WVD)のディスク容量枯渇エラーほど不毛なアラートはない。その主犯格の一つが、ログオン時のトラブル等で生成される「壊れた一時プロファイル(`.bak`拡張子付きなど)」や、長期間放置された不要なユーザープロファイルである。
GUIで「システムのプロファイル詳細」からポチポチ削除する? 冗談ではない。そんな運用はスケールしないし、深夜のバッチ処理にも組み込めない。
今回は、WMI(Windows Management Instrumentation)の `Win32_UserProfile` クラスを完全に手なづけ、不要なプロファイルを安全かつ動的に診断・クリーンアップするプロダクション品質のVBScriptアーキテクチャを伝授する。
—
なぜ「フォルダを直接消すな」なのか?(オブジェクトライフサイクルの真実)
素人がやりがちな最大のミスがこれだ。
`C:\Users\username` というフォルダを、VBScriptの `FileSystemObject (FSO)` でいきなり `DeleteFolder` する。
絶対にやってはいけない。
Windowsのユーザープロファイルは、単なるフォルダの集まりではない。以下の3つの要素が複雑に絡み合った「ライフサイクルを持つシステムリソース」である。
1. ファイルシステム実体 (`C:\Users\…`)
2. レジストリハイブ (`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList`)
3. WMI上の管理オブジェクト (`Win32_UserProfile`)
フォルダだけを消しても、レジストリやWMIにゴミが残り、OSは「壊れたプロファイル」として認識し続ける。結果として、次回ログオン時に強制的に一時プロファイル(`TEMP.000`など)が生成されるという負の無限ループに陥る。
プロファイルを安全に消去するには、WMIの `Win32_UserProfile` が持つ `Remove` メソッドを叩かなければならない。これこそが、OSの整合性を保った唯一の正しい作法なのだ。
—
堅牢なプロファイル管理スクリプトの設計思想
プロダクション環境で動かすスクリプトには、以下の要件が絶対的に求められる。
- 誤爆の防止: 特権アカウント(Administrator, SYSTEM, ネットワークサービス等)や、現在ログオン中のユーザーを絶対に巻き込まないフィルタリング。
- 「一時プロファイル」の正確な特定: `.bak` がついたゾンビプロファイル、および `Special = True` かつ長期間使用されていないものの判定。
- トランザクション的ログ出力: 何を検知し、何を削除に成功し、どこで弾かれたのかをイベントログまたはファイルに正確に残すトレーサビリティ。
—
【プロダクションコード】実用版プロファイルクリーンアップ・スクリプト
以下のコードは、エラーハンドリング(`On Error Resume Next` の正しい局所化)と、WMIクエリの最適化を施した実戦投入可能なVBScriptである。
‘ ==============================================================================
‘ Script Name: Cleanup-StaleUserProfiles.vbs
‘ Purpose : WMI (Win32_UserProfile) を用いた不要・一時プロファイルの動的診断と削除
‘ Author : Chief Automation Architect
‘ ==============================================================================
Option Explicit
Const wbemFlagForwardOnly = 16
Const wbemFlagReturnImmediately = 48
Const LOG_FILE_PATH = “C:\Logs\UserProfile_Cleanup.log”
‘ ログ出力用FSOの準備
Dim fso, logFile
Set fso = CreateObject(“Scripting.FileSystemObject”)
Call InitializeLogSystem()
WriteLog “INFO”, “=== プロファイル診断・クリーンアップ処理を開始します ===”
‘ WMIサービスへの接続 (cimv2)
Dim objWMIService, colProfiles, objProfile
Dim strComputer
strComputer = “.”
Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)
‘ 特殊プロファイル(システムアカウント等)および現在使用中のものを除外して取得
‘ 特別なフラグやSIDを持つものを安全にクエリ
Set colProfiles = objWMIService.ExecQuery( _
“Select From Win32_UserProfile Where Special = False AND Loaded = False”, , wbemFlagForwardOnly + wbemFlagReturnImmediately)
Dim deleteCount, errorCount
deleteCount = 0
errorCount = 0
For Each objProfile In colProfiles
‘ LocalPathが取得できない異常なオブジェクトはスキップ
If IsNull(objProfile.LocalPath) Then
VBContinue
End If
Dim profilePath, profileSID
profilePath = objProfile.LocalPath
profileSID = objProfile.SID
WriteLog “DEBUG”, “診断中: ” & profilePath & ” (SID: ” & profileSID & “)”
‘ 判定ロジック 1: フォルダ名に “.bak” が含まれているか(壊れた一時プロファイル)
‘ 判定ロジック 2: 最終使用日 (LastUseTime) から一定期間(例: 90日)経過しているか
‘ ※WMIのLastUseTimeはUTCのWMI日時はすんなり比較できないため、FSOでフォルダの最終更新日を見るか、
‘ あるいは明示的な一時プロファイル名義 (.bak) をターゲットにするのが安全。
If InStr(LCase(profilePath), “.bak”) > 0 Or IsProfileStale(profilePath) Then
WriteLog “WARN”, “不要プロファイルを検知しました -> 削除対象: ” & profilePath
‘ WMIメソッドによる安全な削除実行
On Error Resume Next
objProfile.Remove()
If Err.Number = 0 Then
deleteCount = deleteCount + 1
WriteLog “INFO”, “削除成功: ” & profilePath
Else
errorCount = errorCount + 1
WriteLog “ERROR”, “削除失敗: ” & profilePath & ” (エラーコード: &H” & Hex(Err.Number) & ” – ” & Err.Description & “)”
Err.Clear
End If
On Error GoTo 0
End If
Next
WriteLog “INFO”, “=== 処理完了. 削除成功件数: ” & deleteCount & “, 失敗件数: ” & errorCount & ” ===”
‘ クリーンアップ終了
logFile.Close
Set logFile = Nothing
Set fso = Nothing
WScript.Quit(0)
‘ ==============================================================================
‘ 補助関数群
‘ ==============================================================================
Sub InitializeLogSystem()
Dim logDir
logDir = fso.GetParentFolderName(LOG_FILE_PATH)
If Not fso.FolderExists(logDir) Then
fso.CreateFolder(logDir)
End If
‘ 追記モードでオープン (Unicode: True)
Set logFile = fso.OpenTextFile(LOG_FILE_PATH, 8, True, -1)
End Sub
Sub WriteLog(level, message)
Dim logLine
logLine = “[” & Now & “] [” & level & “] ” & message
WScript.Echo logLine ‘ コンソール実行時用
If Not logFile Is Nothing Then
logFile.WriteLine logLine
End If
End Sub
Function IsProfileStale(path)
‘ ここでは例として、特定の保持期間を過ぎた判定を実装する
‘ 実運用では FSO を使って LastModifiedDate を評価するなど高度な条件を付与可能
IsProfileStale = False
‘ 例: C:\Users\Temp_ のようなフォルダ名パターンもここでキャッチできる
Dim folderName
folderName = fso.GetFileName(path)
If LCase(Left(folderName, 5)) = “temp.” Then
IsProfileStale = True
End If
End Function
—
チーフアーキテクトからの実務アドバイス:運用時の注意点
1. 実行権限の壁
このスクリプトは、レジストリハイブの操作およびWMIの `Win32_UserProfile.Remove()` を叩くため、完全なローカルAdministrator権限(またはSYSTEM権限)が必須である。Task Scheduler(タスクスケジューラ)に登録する際は、「最上位の特権で実行する」にチェックを入れることを忘れるな。
2. `Loaded = True` の厳守
クエリ条件に `Loaded = False` を入れているのは極めて重要だ。現在まさにログオンしているユーザーのプロファイルを誤って削除しようとすると、WMIは容赦なくエラーを吐くか、最悪の場合OSのセッションが不安定になる。
3. リモート監視への拡張
今回はローカル実行を想定したが、`strComputer` の部分を変更し、適切なWinRM / WMIのファイアウォール設定を行えば、踏み台サーバーからドメイン内配下の複数サーバーのプロファイル状況をリモートで一斉診断・回収するマスター・スクリプトへと昇華できる。
手動でのメンテナンスという無駄な労働をコードで焼き払い、インフラの信頼性を極限まで高めろ。これこそがプロの自動化エンジニアの仕事だ。
