【実務中級者向け】FSOのファイル走査における「ノイズ」を排除する:堅牢なファイルフィルタリング術
こんにちは。現場の設計から実装までを統括しているアーキテクトです。
VBScriptを使って「フォルダ内のファイルを順番に処理する」というタスク。これは自動化の第一歩として非常に素晴らしいステップです。`FileSystemObject` (FSO) を使いこなし、フォルダの中身をスキャンできるようになったあなたは、すでに「マクロの記録」という殻を破り、エンジニアとしての歩みを確実に踏み出しています。
しかし、現場で「本物のツール」を動かそうとすると、必ずと言っていいほど、ある「見えない敵」に遭遇します。
それが、Officeの時限テンポラリファイル(`~$`で始まるファイル)や、システムが生成する一時ファイル(`.tmp`)です。
これらが混入すると、あなたの完璧なはずのスクリプトは「実行時エラー:書き込み禁止、またはアクセス拒否」という無慈悲な叫びを上げて止まってしまいます。今日は、この「ノイズ」をスマートに回避し、プロフェッショナルな、止まらないスクリプトを書くための「フィルタリング・フレームワーク」を伝授しましょう。
ここをクリアすれば、VBScriptの基本はバッチリですよ。
1. なぜ、ファイル操作スクリプトは「突然死」するのか?
まずは、敵の正体を知ることから始めましょう。
ExcelやWordなどのOffice製品を開いているとき、そのフォルダを覗いてみてください。ファイル名の先頭に `~$` が付いた、半透明のようなファイルが存在しませんか? これが「オーナーファイル(ロックファイル)」です。
陥りやすい3つの罠
1. アクセス権限エラー (Permission Denied):
Officeがファイルを「使用中」としてロックしているため、FSOでファイルを開こうとした瞬間にエラーが発生します。
2. 論理的な誤処理:
`.xlsx` を処理するつもりが、`~$example.xlsx` を処理しようとして、「ファイル形式が正しくありません」というエラーを吐いて止まります。
3. 無限ループや予期せぬ挙動:
システムが生成する一時ファイルは、人間が意図したデータではありません。これらを「処理対象」としてカウントしてしまうと、統計や集計の結果が狂ってしまいます。
一流のエンジニアは、「ファイルが存在すること」を確認するだけでなく、「そのファイルが、今、処理しても安全なものか?」を判断するロジックを必ず組み込みます。
2. 実装の鍵:再帰処理とフィルタリング・ロジック
フォルダを深く探索(サブフォルダまで辿る)する場合、「再帰処理(Recursive Call)」という技法を使います。自分自身の関数の中で、自分自身を呼び出す手法です。
これに、「スキップ判定」というフィルターを挟み込むのが今回の設計思想です。
プロフェッショナルなフィルタリングの設計図
以下のコードは、そのまま実務に投入できるレベルの堅牢性を備えたテンプレートです。
' ==========================================================================
' 堅牢なファイル走査フレームワーク
' 目的: 指定フォルダ以下のファイルを再帰的に探索し、不要なファイルをスキップする
' ==========================================================================
Option Explicit
Dim fso, targetPath
Set fso = CreateObject("Scripting.FileSystemObject")
' --- 設定エリア ---
targetPath = "C:\Your\Target\Folder" ' ここを調査したいパスに変更してください
' ------------------
' メイン処理の開始
If fso.FolderExists(targetPath) Then
WScript.Echo "スキャンを開始します: " & targetPath
ScanFolder fso.GetFolder(targetPath)
WScript.Echo "スキャンが完了しました。"
Else
WScript.Echo "エラー: 指定されたフォルダが見つかりません。"
End If
' オブジェクトの解放(メモリ管理はエンジニアの嗜みです)
Set fso = Nothing
' ==========================================================================
' 関数: ScanFolder (再帰的にフォルダを走査)
' 引数: objFolder (FileSystemObject.Folder オブジェクト)
' ==========================================================================
Sub ScanFolder(objFolder)
Dim objFile, objSubFolder
' 1. フォルダ内のファイルを走査
For Each objFile In objFolder.Files
' 【ここが重要!】フィルタリング・ロジック
If IsValidFile(objFile.Name) Then
' 有効なファイルの場合のみ、実際の処理を行う
ProcessFile objFile
Else
' 不要なファイルは無視して次へ
' WScript.Echo "スキップ: " & objFile.Name ' デバッグ時に活用
End If
Next
' 2. サブフォルダを再帰的に走査
For Each objSubFolder In objFolder.SubFolders
' 隠し属性のフォルダなどは必要に応じてスキップする設計も可能
ScanFolder objSubFolder
Next
End Sub
' ==========================================================================
' 関数: IsValidFile (ファイルが処理対象か判定)
' 戻り値: True (処理対象), False (スキップ対象)
' ==========================================================================
Function IsValidFile(fileName)
Dim ext, isTemp, isOfficeLock
' 拡張子の取得(小文字に統一して比較するのがコツ)
ext = LCase(CreateObject("Scripting.FileSystemObject").GetExtensionName(fileName))
' パターンA: Officeのロックファイル (~$ で始まるもの)
' Left関数で先頭2文字をチェック
isOfficeLock = (Left(fileName, 2) = "~$")
' パターンB: 一般的な一時ファイル (.tmp)
isTemp = (ext = "tmp")
' 判定: ロックファイルでもなく、一時ファイルでもなければ True
If isOfficeLock Or isTemp Then
IsValidFile = False
Else
IsValidFile = True
End If
End Function
' ==========================================================================
' 関数: ProcessFile (ファイルに対する実際の業務ロジック)
' ==========================================================================
Sub ProcessFile(objFile)
' ここに「ファイルをコピーする」「中身を読み取る」などの処理を書く
' エラーハンドリングを忘れずに
On Error Resume Next
WScript.Echo "処理中: " & objFile.Path
' 例: ファイルサイズを表示してみる
' WScript.Echo " Size: " & objFile.Size & " bytes"
If Err.Number <> 0 Then
WScript.Echo " [!] エラー発生: " & Err.Description
Err.Clear
End If
On Error GoTo 0 ' エラーハンドリングを通常状態に戻す
End Sub
3. コードの深層解説:なぜこの書き方なのか?
このコードには、単なる「動くコード」を超えた、設計上の意図がいくつか込められています。
① `IsValidFile` 関数による「関心の分離」
ファイルが「処理して良いものか」を判定するロジックを、メインの走査処理(`ScanFolder`)から切り離しました。これを「関心の分離」と呼びます。
もし将来、「`.log` ファイルも除外したい」という仕様変更が入っても、修正するのは `IsValidFile` 関数の中だけで済みます。メインのロジックを触らなくていい。これが保守性の高いコードの秘訣です。
② `Left(fileName, 2) = “~$”` の重要性
Officeのロックファイルは、拡張子が `.xlsx` であっても、ファイル名の先頭が `~$` になっています。そのため、拡張子だけで判定するとすり抜けてしまいます。`Left` 関数を使って、ファイル名の「顔(先頭部分)」を確認することが、防御の要となります。
③ `On Error Resume Next` の「正しい」使い方
多くの初心者は、スクリプトの冒頭に `On Error Resume Next` を書いて、すべてのエラーを無視しようとします。これは「地雷原を目の見えない状態で歩く」ような非常に危険な行為です。
私のコードでは、`ProcessFile` という、「エラーが起きる可能性がある最小単位の処理」の直前で、直後で使用しています。これにより、予期せぬエラー(権限不足など)が起きても、そのファイルだけをスキップして、全体の処理を継続させることができます。
4. アーキテクトからのアドバイス:さらに高みを目指すために
ここまでマスターできれば、実務でのファイル操作スクリプトの信頼性は劇的に向上します。さらに上を目指すなら、以下の視点を持ってみてください。
- 属性(Attributes)のチェック:
`objFile.Attributes` を使えば、システムファイル属性や隠しファイル属性を判定できます。
- ログ出力:
`WScript.Echo` ではなく、実行結果をテキストファイルに書き出すようにすると、夜間にバッチ処理を回した際の「何が起きたか」の証跡になります。
- オブジェクトのライフサイクル:
コードの最後で `Set obj = Nothing` と書く癖をつけましょう。VBScriptはメモリ管理が比較的緩やかですが、大量のファイルを扱う際、明示的に解放する姿勢は、大規模システムを設計するエンジニアとしての「品格」です。
最後に
自動化の道は、単に「楽をするため」だけのものではありません。「人間がミスをしやすい、不安定なプロセスを、論理と規律によって制御可能なものに変える」という、非常にクリエイティブな仕事です。
今回学んだフィルタリングの考え方は、VBScriptに限らず、PythonやC#、あるいはあらゆるプログラミング言語に通じる普遍的な知恵です。
一歩ずつ、確実に。あなたのコードが、現場の信頼を勝ち取る武器になることを願っています。応援していますよ!
