【プロへの第一歩】VBScriptで「やり直し(Undo)」を実現する:FSOによる安全なファイル操作エンジンの作り方
こんにちは。自動化の世界へようこそ。
皆さんは、VBScript(Visual Basic Scripting Edition)を使って「大量のファイル名を一括で書き換えたい」「フォルダを整理したい」と考えたことはありませんか?
FileSystemObject(FSO)を使えば、それは驚くほど簡単に実現できます。しかし、ここで一つ、プロのエンジニアとして絶対に忘れてはならない「恐怖」があります。
それは、「一度実行してしまったファイル操作は、Ctrl+Z(元に戻す)が効かない」ということです。
プログラムが暴走して、大切なファイルをすべて別の名前に変えてしまったら……。あるいは、意図しないフォルダへ移動させてしまったら……。その瞬間、あなたの自動化ツールは「便利な道具」から「破壊兵器」へと変貌します。
今日は、そんな恐怖を克服し、初級者のマクロ記録レベルから、「安全性を設計できるプロフェッショナル」へとステップアップするための極意を伝授します。テーマは「Snapshot(スナップショット)によるUndo(元に戻す)機構の実装」です。
1. コンセプト:なぜ「Snapshot」が必要なのか?
プロのエンジニアは、コードを書く前に「失敗した時にどうやって戻すか」を必ず設計します。
今回私たちが作る仕組みは、以下の3ステップです。
1. Snapshot(記録): 操作を行う直前に、対象ファイルの「現在の状態(元のパス、サイズ、更新日時など)」をCSVファイルに書き出す。
2. Execution(実行): ファイルのリネームや移動を行う。
3. Undo(復元): 万が一の際、Snapshot(CSV)を読み込み、記録された「元のパス」へ逆方向に操作を適用する。
この「事前の記録」こそが、あなたの身を守る盾となります。
2. 実装の設計図
イメージ図として、以下のようなデータの流れを考えてみましょう。
[実行前]
File_A.txt (Size: 10KB, Date: 2023/01/01)
File_B.txt (Size: 20KB, Date: 2023/01/02)
↓ [Snapshot作成]
[snapshot.csv]
OriginalPath, NewPath, Size, LastModified
C:\Data\File_A.txt, C:\Data\AAA_File_A.txt, 10240, 2023/01/01
C:\Data\File_B.txt, C:\Data\BBB_File_B.txt, 20480, 2023/01/02
↓ [一括リネーム実行]
File_AAA_File_A.txt
File_BBB_File_B.txt
↓ [もし失敗したら? → Undo実行]
[snapshot.csvを読み取って逆操作を実行]
File_AAA_File_A.txt -> File_A.txt に戻す!
3. 実践:安全なファイル操作エンジン
それでは、実際に動くコードを見ていきましょう。初心者の方でも理解しやすいよう、機能を分割して丁寧にコメントを入れました。
このスクリプトは、指定したフォルダ内の全ファイルの先頭に「[Fixed]_」という文字を付けるリネーム処理を行い、その履歴を保存します。
' ===========================================================================
' プロフェッショナル向け:FSO Snapshot & Undo Engine
' 概要:ファイル操作前にメタデータを保存し、後から復元を可能にする
' ===========================================================================
Option Explicit
Dim fso, targetFolder, snapshotFile, logFile
Set fso = CreateObject("Scripting.FileSystemObject")
' --- 設定エリア ---
targetFolder = "C:\TestFolder" ' 操作対象のフォルダパス(必ず存在すること)
snapshotFile = "C:\TestFolder\operation_snapshot.csv" ' 履歴保存先
' ---------------------------------------------------------------------------
' メインルーチン
' ---------------------------------------------------------------------------
Sub Main()
' 1. フォルダの存在確認(基本中の基本!)
If Not fso.FolderExists(targetFolder) Then
MsgBox "エラー: 対象フォルダが見つかりません。", vbCritical
Exit Sub
End If
' 2. Snapshotの作成(実行前の状態を保存)
If CreateSnapshot(targetFolder, snapshotFile) Then
MsgBox "Snapshotを作成しました。操作を開始します。", vbInformation
' 3. ファイル操作の実行(ここではリネーム)
If ExecuteRename(targetFolder) Then
MsgBox "操作が完了しました。Snapshot: " & snapshotFile & " を使ってUndo可能です。", vbInformation
Else
MsgBox "操作中にエラーが発生しました。Undoを検討してください。", vbExclamation
End If
Else
MsgBox "Snapshotの作成に失敗したため、操作を中断しました。", vbCritical
End If
End Sub
' ---------------------------------------------------------------------------
' 関数: Snapshot作成
' 引数: folderPath (対象フォルダ), csvPath (保存先CSV)
' 戻り値: 成功ならTrue
' ---------------------------------------------------------------------------
Function CreateSnapshot(folderPath, csvPath)
On Error Resume Next
Dim objFolder, objFile, objLog
Set objFolder = fso.GetFolder(folderPath)
Set objLog = fso.CreateTextFile(csvPath, True)
' CSVヘッダーの書き込み
objLog.WriteLine "OriginalPath,NewPath,Size,LastModified"
For Each objFile In objFolder.Files
' Snapshot対象(ファイルのみ)
' 元のパス, 変更後のパス(仮), サイズ, 更新日時 を記録
' ※ここではリネーム後のパスを予測して記録しておく
objLog.WriteLine objFile.Path & "," & _
fso.BuildPath(folderPath, "[Fixed]_" & objFile.Name) & "," & _
objFile.Size & "," & _
objFile.DateLastModified
Next
objLog.Close
If Err.Number = 0 Then
CreateSnapshot = True
Else
CreateSnapshot = False
End If
On Error GoTo 0
End Function
' ---------------------------------------------------------------------------
' 関数: ファイルリネーム実行
' ---------------------------------------------------------------------------
Function ExecuteRename(folderPath)
On Error Resume Next
Dim objFolder, objFile, newName
Set objFolder = fso.GetFolder(folderPath)
For Each objFile In objFolder.Files
' "[Fixed]_" が付いていないファイルのみ処理
If InStr(objFile.Name, "[Fixed]_") = 0 Then
newName = "[Fixed]_" & objFile.Name
' ファイルのリネーム実行
objFile.Name = newName
If Err.Number <> 0 Then
Err.Clear
ExecuteRename = False
Exit Function
End If
End If
Next
ExecuteRename = True
On Error GoTo 0
End Function
' ---------------------------------------------------------------------------
' [番外編] 関数: Undo(元に戻す)実行
' この関数を呼び出すことで、Snapshot(CSV)に基づき元の名前に戻します
' ---------------------------------------------------------------------------
Sub UndoOperation(csvPath)
Dim objLog, strLine, arrData
If Not fso.FileExists(csvPath) Then
MsgBox "Snapshotファイルが見つかりません。", vbCritical
Exit Sub
End If
Set objLog = fso.OpenTextFile(csvPath, 1)
' ヘッダー行をスキップ
If Not objLog.AtEndOfStream Then objLog.ReadLine
Do While Not objLog.AtEndOfStream
strLine = objLog.ReadLine
' カンマで分割 (OriginalPath, NewPath, Size, LastModified)
arrData = Split(strLine, ",")
Dim oldPath, newPath
oldPath = arrData(0)
newPath = arrData(1)
' 変更後のファイルが存在する場合のみ、元の名前に戻す
If fso.FileExists(newPath) Then
fso.MoveFile newPath, oldPath
End If
Loop
objLog.Close
MsgBox "Undo処理が完了しました。", vbInformation
End Sub
' 実行
Main()
4. ここが「プロ」の視点:ハマりやすい落とし穴と回避策
コードを読み解く中で、「なぜこう書くのか?」という疑問が湧いてきたはずです。ここが、マクロ記録レベルから脱却するための重要なポイントです。
① `On Error Resume Next` の使い方
初心者の方は、エラーが出るとプログラムが止まってしまうのを嫌い、何でもかんでも `On Error Resume Next`(エラーを無視して進む)を書いてしまいがちです。これは非常に危険です。
プロは、「エラーが起きる可能性がある特定の箇所」に限定してこれを使います。今回のように、ファイル操作(`MoveFile` や `Name` 変更)の直後に `Err.Number` をチェックし、失敗した瞬間に処理を中断(`Exit Function`)させるのが正しい作法です。
② オブジェクトのライフサイクル
`Set fso = CreateObject(“Scripting.FileSystemObject”)` で作成したオブジェクトは、スクリプトが終了するまでメモリに居座ります。大量のファイルをループで処理する場合、ループの中で毎回 `CreateObject` をしてしまうと、メモリ消費が激増し、動作が劇的に重くなります。「オブジェクトは一度作ったら使い回す」。これがパフォーマンスの鉄則です。
③ Undoの順序問題(高度な知見)
今回のサンプルは同じフォルダ内でのリネームですが、もし「フォルダごと移動」させる処理だった場合、Undoの順番が重要になります。
例えば、`A/file.txt` を `B/file.txt` に移動し、次に `A` を `C` に移動した場合、Undoする時は「移動の逆順」で行わないと、元のパスが見つからずエラーになります。
「変更の履歴は、スタック(後入れ先出し)のように管理する」という意識を持つと、さらに複雑なシステムも構築できるようになります。
まとめ:一歩ずつ、安全な自動化へ
いかがでしたでしょうか?
単に「ファイルをリネームする」だけなら、数行のコードで済みます。しかし、そこに「Snapshot」という概念を組み込むことで、あなたのスクリプトは「責任ある、信頼できるシステム」へと進化しました。
1. まず記録する(Snapshot)
2. 慎重に実行する(Execution)
3. いつでも戻せる準備をする(Undo)
このサイクルを意識するだけで、あなたのプログラミングの質は劇的に変わります。
「失敗しても大丈夫」という確信があるからこそ、エンジニアは大胆に、かつ正確に自動化の翼を広げることができるのです。さあ、恐れずにコードを書いていきましょう。応援していますよ!
