【実務・中級編】【一括リネーム】日付スタンプ付与や連番設定を行う安全なファイル名変更ツールの開発 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptを掌握する極限の知見:【一括リネーム】日付スタンプ・連番付与ツールの極意

開発現場において、前時代的と嘲笑われがちなVBScriptだが、その本質を理解すれば、Windows環境下における「最速かつ最も依存関係の少ない自動化スクリプト」としての強力な武器になる。インストール不要、OS標準装備、そしてメモリフットプリントの軽さ。この特性を活かし、大量のファイル群を安全かつ高速に料理する実務ツールを構築する。

今回は、業務で最も頻出する「ファイル一括リネーム(日付スタンプ+連番付与)」を取り上げる。単に動くだけのコードではない。ファイルシステムオブジェクト(FSO)の挙動の裏側、排他制御、競合回避、そしてロールバックの概念まで踏り込んだ「プロダクション品質」の設計思想を伝授しよう。

—

1. なぜ素朴なFSOリネームは現場で爆発するのか?

多くのプログラマは、FSOの `MoveFile` メソッドをこのように安易に実装する。

‘ 【アンチパターン】絶対にやってはいけない実装
Dim fso, folder, file
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set folder = fso.GetFolder(“C:\Data”)

For Each file In folder.Files
‘ ループ内で直接リネーム
fso.MoveFile file.Path, “C:\Data\New_” & file.Name
Next

このコードが抱える致命的な欠陥:
1. 処理中のファイル競合: フォルダ内のファイルを走査しながら `MoveFile` で移動・リネームを行うと、ファイルシステムのインデックスが変わり、`For Each` の列挙順序が狂うか、同じファイルを二重処理・スキップするリスクが生じる。
2. 既存ファイルへの上書き破壊: すでに同名のファイルが存在した場合、警告なしに上書きされ、データが消失する。
3. エラーハンドリングの欠如: 1つのファイルが別プロセス(Excelや別アプリ)でロックされているだけで、スクリプト全体が異常終了する。

プロのエンジニアであれば、「走査フェーズ」と「実行フェーズ」を完全に分離し、メモリ上に処理対象のメタデータを蓄積してから安全にトランザクションを処理すべきである。

—

2. 堅牢な一括リネームツールのアーキテクチャ

今回構築するツールの設計要件は以下の通りだ。

  • 二段階処理: 処理対象ファイルを一度メモリ上の配列/辞書に格納し、ファイルシステムの列挙と操作を分離する。
  • プレフィックスの動的生成: 実行時のタイムスタンプ(`YYYYMMDD_HHMMSS`)を付与し、いつ実行されたバッチか一目でわかるようにする。
  • 連番のゼロ埋め: 桁数がバラバラにならないよう、4桁のゼロパディング(`0001`, `0002`…)を施す。
  • 完全なエラーハンドリング: ロックファイルに遭遇しても処理を止めず、ログを残して完走する。

—

3. プロダクションコード:`SecureBatchRenamer.vbs`

以下のコードをコピーし、拡張子を `.vbs` として保存すれば、即座に実務で使用できる。

‘ ==============================================================================
‘ Script Name : SecureBatchRenamer.vbs
‘ Description : フォルダ内の指定拡張子ファイルに対し、タイムスタンプと
‘ ゼロ埋め連番を安全に付与して一括リネームするプロダクションツール
‘ ==============================================================================
Option Explicit

‘ — 設定エリア —
Const TARGET_DIR = “C:\TargetFolder” ‘ 対象フォルダのパス(末尾の\は不要)
Const TARGET_EXT = “jpg” ‘ 対象ファイルの拡張子(ドットなし)
Const DRY_RUN = False ‘ Trueの場合、リネームせずログ出力のみ(テスト用)

Sub Main()
Dim fso, targetFolder, fileList, file, item
Dim timestamp, counter, newName, oldPath, newPath
Dim successCount, errorCount

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ フォルダの存在確認
If Not fso.FolderExists(TARGET_DIR) Then
WScript.Echo “エラー: 指定されたフォルダが存在しません。” & vbCrLf & TARGET_DIR
Exit Sub
End If

Set targetFolder = fso.GetFolder(TARGET_DIR)

If targetFolder.Files.Count = 0 Then
WScript.Echo “対象フォルダにファイルが存在しません。”
Exit Sub
End If

‘ 1. タイムスタンプの生成 (YYYYMMDD_HHMMSS形式)
timestamp = GetTimeStamp()

‘ 2. 走査フェーズ:ファイルリストを安全に取得(動的配列の代わりに関連配列/Dictionaryを使用しても良いが、今回はシンプルにDictionaryでパスを保持)
Set fileList = CreateObject(“Scripting.Dictionary”)
For Each file in targetFolder.Files
‘ 拡張子のチェック(大文字小文字を区別しない)
If LCase(fso.GetExtensionName(file.Name)) = LCase(TARGET_EXT) Then
‘ キーに元のフルパス、値にファイル名を格納
fileList.Add file.Path, file.Name
End If
Next

If fileList.Count = 0 Then
WScript.Echo “対象となる拡張子 [.” & TARGET_EXT & “] のファイルは見つかりませんでした。”
Exit Sub
End If

WScript.Echo “=== リネーム処理を開始します ===”
WScript.Echo “対象件数: ” & fileList.Count & ” 件”
If DRY_RUN Then WScript.Echo “【DRY RUNモード】実際にはファイルは変更されません。” & vbCrLf

counter = 1
successCount = 0
errorCount = 0

‘ 3. 実行フェーズ:分離されたリストを基にリネームを執行
For Each oldPath in fileList.Keys
‘ ゼロ埋め連番の作成(4桁: 0001, 0002…)
Dim paddedIndex
paddedIndex = Right(“0000” & counter, 4)

‘ 新しいファイル名の組み立て: [タイムスタンプ]_[連番].[拡張子]
newName = timestamp & “_” & paddedIndex & “.” & TARGET_EXT
newPath = fso.BuildPath(TARGET_DIR, newName)

‘ 同名ファイルが存在する場合の衝突防衛策
Do While fso.FileExists(newPath)
counter = counter + 1
paddedIndex = Right(“0000” & counter, 4)
newName = timestamp & “_” & paddedIndex & “.” & TARGET_EXT
newPath = fso.BuildPath(TARGET_DIR, newName)
Loop

On Error Resume Next
If Not DRY_RUN Then
fso.MoveFile oldPath, newPath
End If

If Err.Number = 0 Then
WScript.Echo “[SUCCESS] ” & fileList(oldPath) & ” -> ” & newName
successCount = successCount + 1
Else
WScript.Echo “[ERROR] ” & fileList(oldPath) & ” の変更に失敗しました。エラー: ” & Err.Description
errorCount = errorCount + 1
Err.Clear
End If
On Error GoTo 0

counter = counter + 1
Next

WScript.Echo vbCrLf & “=== 処理完了 ===”
WScript.Echo “成功: ” & successCount & ” 件 / 失敗: ” & errorCount & ” 件”

Set fileList = Nothing
Set targetFolder = Nothing
Set fso = Nothing
End Sub

‘ 補助関数:現在時刻からファイル名に使用可能なタイムスタンプ文字列を生成
Function GetTimeStamp()
Dim d
d = Now()
GetTimeStamp = Year(d) & _
Right(“0” & Month(d), 2) & _
Right(“0” & Day(d), 2) & “_” & _
Right(“0” & Hour(d), 2) & _
Right(“0” & Minute(d), 2) & _
Right(“0” & Second(d), 2)
End Function

‘ メイン処理の呼び出し
Main()

—

4. コードの深層解説:なぜこの実装が「壊れない」のか?

A. 走査と実行の完全なデカップリング

`For Each file in targetFolder.Files` のループ内で直接 `MoveFile` を行うと、ファイルシステム上のポインタが移動するため、ループ自体が途中で破綻するリスクがある。本スクリプトでは、一度 `Scripting.Dictionary` にすべての対象ファイルパスを退避させ、「メモリ上の静的なリスト」に対してループを回している。これにより、ファイルシステムの状態変化に依存しない堅牢なイテレーションを実現している。

B. `DRY_RUN`(空走査)モードの搭載

実務において「本当にこのルールでリネームされて大丈夫か?」という不安はつきものだ。`Const DRY_RUN = True` に設定することで、ファイルシステムを変更せずに、コンソール出力だけで結果をプレビューできる。保守性を高めるための必須イディオムである。

C. 競合防衛ロジック(Do While 루프)

万が一、同一秒内に生成されたファイルや、既存のファイル名と衝突(ハッシュ衝突ならぬファイル名衝突)が発生した場合でも、`Do While fso.FileExists(newPath)` によってカウンターをインクリメントし続け、既存ファイルを上書き破壊する事故を物理的にブロックする。

—

5. データベースや外部システム連携への拡張性

このスクリプトは、単体でのファイル整理にとどまらず、RDB(SQL ServerやSQLite)やCSVへのログ出力連携へと拡張しやすい。

例えば、リネーム成功時に「旧ファイル名」と「新ファイル名」の対をマッピングテーブルとしてCSVに出力しておけば、後から「どのファイルを元の名前Podsに戻すべきか」というロールバック(巻き戻し)処理を完全自動化できる。実務の現場では、単一方通行の自動化ではなく、「失敗時のリカバリ」までデザインして初めて「エンジニアの成果物」と呼べる。

VBScriptのポテンシャルを侮るなかれ。適切な設計パターンを適用すれば、レガシー環境であってもモダンで頑健な自動化基盤を構築できるのだ。今日からあなたの現場でも、この「壊れない設計」を取り入れてほしい。

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