【入門編】【上級プロフェッショナル向け】FSO一括変更・削除実行前に「全変更対象メタデータ」のSnapshot(JSON/CSV)を保持し一括復元(Undo)を可能にする安全機構 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

【プロへの第一歩】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)

このサイクルを意識するだけで、あなたのプログラミングの質は劇的に変わります。

「失敗しても大丈夫」という確信があるからこそ、エンジニアは大胆に、かつ正確に自動化の翼を広げることができるのです。さあ、恐れずにコードを書いていきましょう。応援していますよ!

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