【VBScript再入門】無限ループの悪夢を断つ!FSO×WScript.Shellでジャンクション/シンボリックリンクを安全にスキップする極意
みなさん、こんにちは!日々の業務自動化スクリプト作成、順調ですか?
VBScriptで「特定フォルダ以下のファイルを全検索して処理する」というプログラムを書いた際、スクリプトが応答なし(フリーズ)になり、CPU使用率が100%に張り付いてしまった…という経験はありませんか?
その原因の9割は、NTFSの特殊機能である「ジャンクション(Junction)」や「シンボリックリンク(Symbolic Link)」を追跡してしまい、フォルダ構造の循環参照(無限ループ)に陥っていることです。
今回は、標準の `FileSystemObject` (FSO) の限界を突破し、`WScript.Shell` の知見を組み合わせることで、特殊なリンクを完璧に判別・安全にスキップする実務直結の技術を解説します。
ここをクリアすれば、VBScriptの基本構造とWindowsシステムへの理解はバッチリですよ!一緒にマスターしていきましょう。
—
1. なぜ無限ループが起きるのか?(構造を図解で理解する)
まずは、問題が発生する仕組みを頭の中に入れておきましょう。
Windowsのファイルシステム(NTFS)には、あるフォルダを別のフォルダへ「ショートカット」のように結びつける機能(ジャンクションやシンボリックリンク)があります。
循環参照のイメージ図
[C:\Data] (走査対象フォルダ)
├── 📄 Document.docx
├── 📁 SubFolderA
└── 📁 LinkToData (※実は C:\Data 自体を指すジャンクション)
│
└─► [C:\Data] へ戻ってしまう!
├── 📄 Document.docx (重複処理)
├── 📁 SubFolderA
└── 📁 LinkToData
└─► [C:\Data] へ戻る… (無限ループ発生!💥)
普通の `FileSystemObject` でフォルダを再帰処理(自分自身を呼び出す処理)すると、FSOはこの「LinkToData」を普通のフォルダとして認識して突き進んでしまいます。結果、同じ場所を永遠にぐるぐる回り続け、メモリやスタック領域を食いつぶして強制終了してしまうのです。
—
2. 解決の鍵は「リパースポイント(Reparse Point)」属性
Windowsでは、ジャンクションやシンボリックリンクには「リパースポイント(Reparse Point)」という特別なファイル属性(Attribute)が付与されます。
VBScriptの `Folder.Attributes` プロパティを使って、この属性が含まれているかをビット演算(AND演算)で判定するのが最速かつ確実な方法です。
属性値の判定ロジック
- Reparse Point 属性の定数: `1024` (16進数で `0x400`)
‘ フォルダの属性に 1024 が含まれているか判定
If (objFolder.Attributes And 1024) = 1024 Then
‘ シンボリックリンク または ジャンクション です!
Else
‘ 通常のフォルダです
End If
> 💡 先輩からのワンポイントアドバイス
> 「`Attributes` プロパティ」は、複数の状態が足し算された数値で保持されています。単に `objFolder.Attributes = 1024` と比較するのではなく、必ず `And` 演算子を使って「1024のフラグが立っているか」を確認してくださいね。
—
3. なぜ `WScript.Shell` を組み合わせるのか?
「属性のチェックだけで十分では?」と思うかもしれません。しかし、実務では以下の壁にぶつかります。
1. アクセス拒否(Permission Denied)のエラー
`C:\Users\All Users` などのシステム系ジャンクションは、FSOで `Attributes` を読みに行こうとするだけでエラー(エラー番号: 70)を吐いて止まることがあります。
2. リンク先の解析が必要なケース
単にスキップするだけでなく、「ログにどこを指しているリンクかを記録したい」場合、FSO単体ではリンク先のパスを取得できません。
ここで登場するのが `WScript.Shell` です。
コマンドプロンプトの `dir /a` などのシステムコマンドを内部で安全に呼び出したり、アクセス権エラーを回避した高度な制御を行ったりするために補完として利用します。
オブジェクトのライフサイクルに関する重要注意点
`WScript.Shell` を再帰関数の中で毎回 `CreateObject` すると、パソコンのメモリがあっという間に枯渇し、劇的に処理速度が低下します。オブジェクトの生成はルーチンの外側で1度だけ行い、参照を使い回すのがプロの技です。
—
4. 【そのまま使える】完全版プログラミングコード
以下は、実務の現場でそのままコピペして使える、安全な再帰フォルダ走査スクリプトです。
エラーハンドリングとパフォーマンスに最大限配慮して作成されています。
‘ ====================================================================
‘ NTFSジャンクション・シンボリックリンク安全回避スクリプト
‘ 作成者: 業務自動化エンジニアリングチーム
‘ ====================================================================
Option Explicit
‘ — メイン処理 —
Sub Main()
Dim strTargetFolder
‘ 走査したいルートフォルダのパスを指定
strTargetFolder = “C:\TestFolder”
Dim objFSO, objShell
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objShell = CreateObject(“WScript.Shell”)
If objFSO.FolderExists(strTargetFolder) Then
WScript.Echo “=== 走査を開始します: ” & strTargetFolder & ” ===”
‘ ルートフォルダの取得
Dim objRootFolder
Set objRootFolder = objFSO.GetFolder(strTargetFolder)
‘ 安全な再帰走査ルーチンを呼び出し
TraverseFolderSafely objRootFolder, objFSO, objShell, 0
WScript.Echo “=== 走査が安全に完了しました ===”
Else
WScript.Echo “エラー: 指定されたフォルダが存在しません: ” & strTargetFolder
End If
‘ オブジェクトの明示的破棄 (リソース解放)
Set objShell = Nothing
Set objFSO = Nothing
End Sub
‘ — 安全なフォルダ再帰走査サブルーチン —
‘ @param objFolder : 走査対象のFolderオブジェクト
‘ @param objFSO : FileSystemObjectの参照
‘ @param objShell : WScript.Shellの参照
‘ @param intDepth : 階層の深さ(ログ表示用)
Sub TraverseFolderSafely(ByVal objFolder, ByRef objFSO, ByRef objShell, ByVal intDepth)
‘ アクセス拒否等のエラーを一時的に無視する設定
On Error Resume Next
‘ 1. インデント用の空白を作成
Dim strIndent
strIndent = String(intDepth 2, ” “)
‘ 2. ジャンクション / シンボリックリンクの判定 (最重要)
‘ 属性値 1024 (FILE_ATTRIBUTE_REPARSE_POINT) をチェック
If (objFolder.Attributes And 1024) = 1024 Then
WScript.Echo strIndent & “[SKIP (Link)] ” & objFolder.Name & ” (リンクのためスキップします)”
Err.Clear
Exit Sub
End If
‘ エラーチェック (フォルダ属性が読み取れなかった場合)
If Err.Number <> 0 Then
WScript.Echo strIndent & “[SKIP (Error)] ” & objFolder.Name & ” (アクセスエラー: ” & Err.Description & “)”
Err.Clear
Exit Sub
End If
‘ 3. 正常なフォルダの場合の処理
WScript.Echo strIndent & “[DIR] ” & objFolder.Name
‘ — A. 配下のファイルに対する処理 —
Dim objFile, colFiles
Set colFiles = objFolder.Files
If Err.Number = 0 Then
For Each objFile In colFiles
‘ ここにファイルに対する個別の自動化処理を記述します
WScript.Echo strIndent & ” └─ [FILE] ” & objFile.Name
Next
End If
Err.Clear
‘ — B. 配下のサブフォルダに対する再帰呼び出し —
Dim objSubFolder, colSubFolders
Set colSubFolders = objFolder.SubFolders
If Err.Number = 0 Then
For Each objSubFolder In colSubFolders
‘ 自分自身を再帰的に呼び出す(深さを+1)
TraverseFolderSafely objSubFolder, objFSO, objShell, intDepth + 1
Next
End If
‘ エラー状態をクリアして通常のエラー処理に戻す
Err.Clear
On Error GoTo 0
End Sub
‘ 処理の実行
Main
—
5. コードのポイント解説とハマりやすいエラー
ポイント①:`On Error Resume Next` の局所化
Windowsのシステムフォルダ配下(`C:\System Volume Information` など)に差し掛かると、管理者権限であってもアクセスが拒否されます。
コード内では `On Error Resume Next` を使い、エラーが発生してもスクリプトを強制終了させず、安全に `Exit Sub` で次のフォルダにスキップさせるガードを固めています。
