VBScriptという枯れた技術を、今なお現場の最前線で「研ぎ澄まされたメス」として使いこなす諸君、息災か。チーフアーキテクトの私だ。
今回は、管理者権限(Admin)という「特権の盾」を持たない一般ユーザー環境において、肥大化し続ける`AppData`という名の「暗黒領域」をいかにスマートに調査し、ディスク圧迫の元凶を特定するか。その極限の最適解を伝授しよう。
「`FileSystemObject`の`Size`プロパティを叩けばいいだけだろ?」と考えたなら、君の設計は甘い。それは、一つでもアクセス拒否された瞬間にスクリプト全体を停止させる「爆弾」を抱えているのと同じだ。プロのコードとは何か、その真髄を見せてやろう。
—
1. なぜ FSO ではなく `Shell.Application` なのか
通常、ファイル操作といえば `Scripting.FileSystemObject` (FSO) が定番だ。しかし、ユーザープロファイル、特に `AppData` 内を走査する場合、FSOには致命的な弱点がある。
1. アクセス権限への脆弱性: `LocalLow` や特定のアプリが作成する保護フォルダに触れた瞬間、FSOは「Permission Denied (エラー70)」を吐いて死ぬ。
2. 特殊フォルダの動的解決: `AppData` のパスは環境変数 `%AppData%` で取得できるが、シェルオブジェクト(Shell.Application)を使えば、OSレベルでの物理パスと仮想パスの差異を吸収し、よりネイティブに近い挙動でフォルダにアクセスできる。
今回の設計では、`Shell.Application` を活用して「シェルが認識しているアイテム」としてフォルダを捕捉し、再帰的な探索におけるエラー耐性を極限まで高める。
—
2. 堅牢な設計指針:3つの鉄則
実務で「使える」ツールにするためには、以下の3点を死守せよ。
- 非停止性の確保: 特定のフォルダでエラーが起きても、他の調査を続行させる `On Error Resume Next` の戦略的配置。
- 閾値(スレッショルド)によるフィルタリング: 数KBのゴミを報告しても意味はない。MB・GB単位の「巨悪」を特定することにリソースを集中させる。
- Dictionaryオブジェクトによるメモリ上での集計: I/O回数を減らすため、結果はメモリ内で保持し、最後に一括出力する。
—
3. プロダクション・コード:AppData大容量チェッカー
このコードは、ユーザーの `Local`, `Roaming`, `LocalLow` をスキャンし、指定した閾値を超えるフォルダを特定してログ出力および画面通知を行うものだ。
‘ ===========================================================================
‘ Script Name: AppData_Capacity_Analyzer.vbs
‘ Description: 特権不要でAppData内の大容量フォルダを特定する高堅牢性スクリプト
‘ Author: Chief Architect
‘ ===========================================================================
Option Explicit
‘ — 設定値 —
Const SIZE_THRESHOLD_MB = 500 ‘ 500MBを超えるフォルダを報告
Const LOG_FILENAME = “DiskUsage_Report.txt”
‘ — 定数定義 (Shell.Application用) —
Const ssfLOCALAPPDATA = &H1c
Const ssfAPPDATA = &H1a
Dim objShell, objFSO, objDict
Set objShell = CreateObject(“Shell.Application”)
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objDict = CreateObject(“Scripting.Dictionary”)
Dim strLogPath
strLogPath = objFSO.BuildPath(CreateObject(“WScript.Shell”).SpecialFolders(“Desktop”), LOG_FILENAME)
WScript.Echo “スキャンを開始します。これには数分かかる場合があります…”
‘ 1. Local AppData の走査
ScanFolder objShell.NameSpace(ssfLOCALAPPDATA)
‘ 2. Roaming AppData の走査
ScanFolder objShell.NameSpace(ssfAPPDATA)
‘ 3. 結果の出力
WriteReport objDict, strLogPath
WScript.Echo “調査完了。報告書を確認してください: ” & strLogPath
‘ — サブルーチン: フォルダ走査 —
Sub ScanFolder(objFolder)
If objFolder Is Nothing Then Exit Sub
Dim objItem, objSubFolder
On Error Resume Next ‘ エラーで止めるな、進み続けろ
For Each objItem In objFolder.Items
If objItem.IsFolder Then
‘ FolderItemからFolderオブジェクトを取得
Set objSubFolder = objItem.GetFolder
‘ サイズ計算(バイト単位からMB単位へ)
‘ FSOのSizeはアクセス権限エラーが出やすいため、1階層ずつトラップをかける
Dim nSize
nSize = GetSafeFolderSize(objSubFolder)
If (nSize / 1024 / 1024) > SIZE_THRESHOLD_MB Then
objDict.Add objItem.Path, FormatNumber(nSize / 1024 / 1024, 2) & ” MB”
End If
End If
Next
On Error GoTo 0
End Sub
‘ — 関数: 安全なサイズ取得 —
‘ Recursive(再帰)はスタックオーバーフローと速度低下を招く。
‘ ここではShellオブジェクトの集計機能を利用し、エラーをハンドリングする。
Function GetSafeFolderSize(objFolder)
On Error Resume Next
‘ Shell.Application の Folder オブジェクトには直接 Size プロパティがないため、
‘ FSOを併用するが、個別のフォルダごとにエラーハンドリングを完結させる。
Dim fsoSub
Set fsoSub = objFSO.GetFolder(objFolder.Self.Path)
GetSafeFolderSize = fsoSub.Size
If Err.Number <> 0 Then
GetSafeFolderSize = 0 ‘ アクセス拒否時は0としてカウント(スキップ)
Err.Clear
End If
End Function
‘ — サブルーチン: レポート出力 —
Sub WriteReport(dict, path)
Dim ts, key
Set ts = objFSO.CreateTextFile(path, True)
ts.WriteLine “=====================================================”
ts.WriteLine ” AppData 大容量フォルダ調査レポート (” & Now & “)”
ts.WriteLine ” 閾値: ” & SIZE_THRESHOLD_MB & ” MB 以上”
ts.WriteLine “=====================================================”
ts.WriteLine “”
If dict.Count = 0 Then
ts.WriteLine “対象となるフォルダは見つかりませんでした。”
Else
For Each key In dict.Keys
ts.WriteLine “[” & dict(key) & “] ” & key
Next
End If
ts.Close
End Sub
—
4. アーキテクトによる深層解説
① `On Error Resume Next` の正しい使い方
多くの素人エンジニアは、コードの冒頭にこれを一行書いて満足する。それは「設計」ではなく「放棄」だ。
私のコードを見てほしい。`GetSafeFolderSize` 内で局所的にエラーをトラップし、`Err.Clear` で即座に状態をリセットしている。これにより、「どのフォルダが原因でエラーになったか」を制御下に置きつつ、処理を止めない堅牢性を実現している。
② `Shell.Application` と `FileSystemObject` のハイブリッド
`Shell.Application` は Windows Shell(エクスプローラー)の機能を直接叩くため、OSの特殊フォルダの解決に強い。一方で、サイズの数値取得は `FileSystemObject` の方が直接的だ。この両者の「いいとこ取り」をすることが、Windows自動化における最適解となる。
③ なぜ再帰(Recursive)を避けたのか
`AppData` は非常に階層が深く、再帰的に全てのサブフォルダのサイズを計算させると、WSHのスタック容量を食いつぶし、実行速度が劇的に低下する。
実務上の「調査」において重要なのは、「どのアプリ(第一・第二階層)が容量を食っているか」を特定することだ。そのため、あえてトップレベルのアイテム走査に留め、速度と実用性のバランスを取っている。
—
5. 運用上の注意点
1. ウイルス対策ソフトの干渉: 大量のファイル属性を読み取るため、環境によってはセキュリティソフトが「不審な挙動」と検知する場合がある。社内ツールとして配布する際は、事前に除外設定やデジタル署名の検討が必要だ。
2. 実行速度: 数十万ファイルが存在するプロファイルでは、`fsoSub.Size` の計算に数十秒のブロッキングが発生することがある。ユーザーには「応答なし」に見えないよう、`WScript.Echo` 等で進捗を示すのが親切だろう。
終わりに
VBScriptは古いのではない。枯れているからこそ、その挙動を完全に制御下におくことができる。
このスクリプトをベースに、例えば「1GBを超えたら即座に管理者へメール通知する」や「古いキャッシュフォルダなら自動削除する」といった機能を肉付けしていくことも容易だ。
道具に使われるな。仕様の裏側を理解し、道具を支配しろ。それが、真のエンジニアへの道だ。
