【VBScript究極奥義】CertUtilとDictionaryでディスクの重複ファイルを一掃する!
皆さん、こんにちは! 最前線の業務自動化エンジニアとして、今日も皆さんのデジタルライフをより快適にするヒントをお届けします。
PCのディスク容量、気づけばパンパン…なんてこと、ありませんか? ダウンロードしたファイル、何気なくコピーした資料、昔のバックアップ… 知らないうちに同じファイルが複数存在し、貴重なストレージを圧迫しているケースは本当に多いんです。
手動で一つ一つ確認するのは骨が折れますし、時間もかかりますよね。そこで今回は、VBScript (Visual Basic Scripting Edition) の力を借りて、そんな悩みを一瞬で解決する「重複ファイル検出・整理ツール」を一緒に作り上げていきましょう!
今回の記事では、プログラミング初学者の方や、マクロの記録からもう一歩踏み出したい方に向けて、以下のテーマを深掘りしていきます。
- VBScriptの基本的な使い方:スクリプトの書き方、実行方法の基礎から。
- WSH(Windows Script Host)実行環境の理解:VBScriptが動く舞台を知る。
- `CertUtil` コマンドの活用:ファイルの「指紋」であるハッシュ値の取得方法。
- `Scripting.Dictionary` オブジェクト:超高速な重複判定の秘密兵器。
- 実用的な重複ファイル検出スクリプト:安全にファイルを整理する具体的なコード。
ここをクリアすれば、VBScriptを使ったファイル操作の基本はバッチリですよ! さあ、一緒にVBScriptの深淵へと足を踏み入れましょう。
—
1. VBScriptとWSH:Windowsを意のままに操る鍵
まず、VBScriptとWSH(Windows Script Host)について、基本的な知識から確認しておきましょう。
1.1 VBScriptって、どんな魔法?
VBScriptは、Microsoftが提供するスクリプト言語の一つで、Windows環境で様々な自動化処理を行うために設計されました。Officeアプリケーションのマクロ(VBA)と兄弟のような関係なので、VBAに触れたことがある方なら、きっと親しみやすいはずです。
「もう古い言語じゃないの?」と思う方もいるかもしれません。しかし、Windowsの根幹に深く統合されているため、外部ソフトウェアのインストールなしに、OSレベルの操作を非常に手軽に記述できるという、現代でも替えの効かない強力なメリットを持っています。ファイル操作、レジストリ操作、ネットワーク設定、そして今回のように外部コマンドとの連携まで、あなたのPCを意のままに操る「魔法の呪文」だと思ってください。
1.2 WSH(Windows Script Host)とは?
VBScriptは単体では動きません。スクリプトを実行するための「舞台」が必要です。それがWSH(Windows Script Host)です。WSHは、VBScriptやJScriptなどのスクリプト言語を解釈し、実行するための環境を提供します。
通常、VBScriptファイル(`.vbs` 拡張子)をダブルクリックすると、自動的にWSHが起動し、スクリプトが実行されます。WSHには主に2つの実行エンジンがあります。
- `WScript.exe`: GUI環境での実行に適しています。メッセージボックス表示などが得意です。
- `CScript.exe`: コマンドプロンプト(CUI)環境での実行に適しています。標準入出力(コンソールへのテキスト表示など)が得意です。
今回のツールでは、処理の進行状況をコンソールに表示したいので、主に `CScript.exe` での実行を想定します。(ダブルクリックでも動きますが、メッセージボックスが頻繁に表示されることになります)
1.3 スクリプトの実行方法
ごく簡単なVBScriptを例に、実行方法を確認しましょう。
1. メモ帳などのテキストエディタを開きます。
2. 以下のコードを記述します。
‘ Hello.vbs
MsgBox “Hello, VBScript World!”
WScript.Echo “コンソールにも表示されますよ!”
3. `Hello.vbs` という名前で保存します。
4. `Hello.vbs` をダブルクリックすると、`MsgBox` が表示されます。
5. コマンドプロンプトを開き、`Hello.vbs` を保存したディレクトリに移動して、以下のコマンドを実行します。
cscript Hello.vbs
すると、`MsgBox` と共に、コマンドプロンプトの画面にも「コンソールにも表示されますよ!」と表示されるはずです。
これでVBScriptの基本的な準備は完了です。
—
2. ディスク容量を圧迫する「見えない敵」:重複ファイル問題
なぜディスクに重複ファイルが生まれるのでしょうか?
- ダウンロードの重複: 同じファイルを何度もダウンロードして、名前を変えて保存。
- バックアップの多重化: 重要なファイルを別フォルダにコピーしたつもりが、元ファイルを残してしまっている。
- 異なるフォルダ間のコピー: 別々のプロジェクトで同じ素材を使っているが、それぞれにコピーしてしまっている。
- アプリケーションのインストール残骸: アンインストールしきれなかった一時ファイルやキャッシュ。
これらはすべて、ディスク容量をひっそりと食いつぶし、システムのパフォーマンスにも悪影響を与えかねません。
2.1 なぜハッシュ値で「完全一致」を判定するのか?
重複ファイルを検出する際、「ファイル名が同じ」とか「ファイルサイズが同じ」という基準では不十分です。
- ファイル名: `document.docx` と `document(1).docx` は別名ですが、中身は同じかもしれません。逆に、`report.pdf` と `report.pdf` は同じ名前でも、中身が全く違うこともあります。
- ファイルサイズ: サイズが同じでも、中身のデータが全く異なるファイルはたくさんあります。
そこで登場するのが「ハッシュ値」です。ハッシュ値とは、ファイルの全データから一意な「指紋」のようなものを生成するアルゴリズムによって算出される、短い文字列のことです。
例えば、SHA256というハッシュアルゴリズムで計算すると、どんなに巨大なファイルでも、たった64文字の文字列に圧縮されたような形で表現されます。
同じファイルからは、常に同じハッシュ値が生成されます。
少しでもファイルの内容が異なれば、ハッシュ値は全く異なるものになります。
この性質を利用すれば、ファイル名やサイズに関わらず、内容が完全に一致するファイルを確実に検出できるのです。
—
3. 【核心技術1】CertUtilでファイルの「指紋」を採取する
ファイルのハッシュ値を計算するために、今回はWindowsに標準で搭載されている強力なコマンド「`CertUtil`」を使います。
3.1 `CertUtil` コマンドの使い方
`CertUtil` は、本来は証明書サービスを管理するためのコマンドですが、ファイルのハッシュ値を計算する機能も持っています。
コマンドプロンプトで以下の形式で実行します。
CertUtil -hashfile “対象ファイルのパス” SHA256
例:`C:\Users\Public\Documents\sample.txt` のハッシュ値を計算する場合
CertUtil -hashfile “C:\Users\Public\Documents\sample.txt” SHA256
実行結果は以下のようになります。
SHA256 ハッシュ (ファイル):
a3e5c9b0e1d2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8
CertUtil: -hashfile コマンドは正常に完了しました。
この出力から、`a3e5c9b0e1d2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8` というハッシュ値を取得することになります。
3.2 VBScriptから外部コマンドを実行し、結果を取得する
VBScriptから外部コマンドを実行するには、`WshShell` オブジェクトの `Run` メソッドまたは `Exec` メソッドを使います。
- `Run` メソッド: コマンドを実行しますが、その実行結果(標準出力)を直接取得することはできません。主に結果を必要としないコマンド実行に使います。
- `Exec` メソッド: コマンドを実行し、そのプロセスをオブジェクトとして取得できます。このオブジェクトを通じて、標準出力(コマンドの実行結果)を読み取ることが可能です。
今回は `CertUtil` の実行結果であるハッシュ値を取得したいので、`Exec` メソッドを使います。
‘ WshShell オブジェクトを作成
Set objShell = CreateObject(“WScript.Shell”)
‘ CertUtil コマンドを実行し、結果を取得するプロセスを生成
Dim strCommand
strCommand = “CertUtil -hashfile “”C:\path\to\your\file.txt”” SHA256″
Set objExec = objShell.Exec(strCommand)
‘ コマンドの終了を待機
Do While objExec.Status = 0 ‘ WshRunning (0)
WScript.Sleep 100 ‘ 100ミリ秒待つ
Loop
‘ 標準出力から結果を読み取る
Dim strOutput
strOutput = objExec.StdOut.ReadAll
‘ 出力を表示
WScript.Echo strOutput
‘ オブジェクトの解放
Set objExec = Nothing
Set objShell = Nothing
`objExec.StdOut.ReadAll` でコマンドの出力を丸ごと取得できます。あとはこの文字列から、ハッシュ値の部分だけを抽出する処理をVBScriptで記述すればOKです。
—
4. 【核心技術2】Scripting.Dictionaryで超高速重複判定
ハッシュ値を取得したら、次にそのハッシュ値を使って重複を判定する必要があります。ここで大活躍するのが `Scripting.Dictionary` オブジェクトです。
4.1 Dictionaryって何?
`Dictionary` オブジェクトは、簡単に言うと「キー(Key)と値(Item)のペアを保持するリスト」です。
- キー: 一意である必要があります。これで特定のデータを検索します。
- 値: キーに関連付けられたデータです。
イメージとしては、電話帳のようなものです。
| キー (名前) | 値 (電話番号) |
| :———- | :———— |
| 山田 | 090-XXXX-XXXX |
| 佐藤 | 080-YYYY-YYYY |
| 田中 | 070-ZZZZ-ZZZZ |
Dictionaryの最大の特徴は、キーを使って高速にデータを検索できることです。配列やコレクションのように一つ一つを順番に探す必要がなく、膨大なデータの中からでも瞬時に目的のキーが存在するかどうかを判定できます。
4.2 なぜDictionaryが重複判定に最適なの?
今回のケースでは、ファイルのハッシュ値を「キー」として、そのファイルのパスを「値」としてDictionaryに登録していきます。
1. 最初のファイルを処理するとき、そのハッシュ値がDictionaryに存在しないことを確認し、Dictionaryに `ハッシュ値:ファイルパス` のペアを追加します。
2. 次に別のファイルを処理するとき、そのハッシュ値がDictionaryに存在するかどうかをチェックします。
- もし存在すれば、それは「同じハッシュ値を持つファイルが既に見つかっている」ということ、つまり重複ファイルであると判定できます。
- もし存在しなければ、Dictionaryに新しい `ハッシュ値:ファイルパス` のペアを追加します。
この `Exists` メソッドを使ったチェックが驚くほど高速なため、大量のファイルを扱う重複判定にDictionaryはまさにうってつけなのです。
4.3 Dictionaryの基本的な使い方
‘ Dictionary オブジェクトを作成
Set objDict = CreateObject(“Scripting.Dictionary”)
‘ データを追加 (キー, 値)
objDict.Add “apple”, “りんご”
objDict.Add “orange”, “みかん”
‘ キーが存在するかどうかをチェック
If objDict.Exists(“apple”) Then
WScript.Echo “apple は存在します。”
Else
WScript.Echo “apple は存在しません。”
End If
If objDict.Exists(“grape”) Then
WScript.Echo “grape は存在します。”
Else
WScript.Echo “grape は存在しません。” ‘ こちらが表示される
End If
‘ キーを使って値を取得
WScript.Echo “orange の意味は: ” & objDict.Item(“orange”) ‘ または objDict(“orange”)
‘ 登録されているアイテムの数
WScript.Echo “登録されているアイテム数: ” & objDict.Count
‘ Dictionaryの解放
Set objDict = Nothing
—
5. 実装フェーズ:重複ファイル検出スクリプトを構築する
それでは、いよいよCertUtilとDictionaryを組み合わせた重複ファイル検出スクリプトを作成していきましょう。
5.1 スクリプトの全体像
作成するスクリプトは、以下の流れで処理を進めます。
1. 設定: 対象フォルダ、重複ファイルの移動先フォルダなどを定義。
2. 初期化: `FileSystemObject` と `Dictionary`、`WshShell` オブジェクトを作成。
3. ファイル走査: 指定されたフォルダ内の全ファイルを再帰的に走査。
4. ハッシュ値取得: 各ファイルについて `CertUtil` でSHA256ハッシュ値を計算。
5. 重複判定: 取得したハッシュ値をキーとして `Dictionary` に登録。既に存在すれば重複と判定。
6. 重複ファイルの処理: 重複と判定されたファイルを、指定されたフォルダに移動(削除も可能ですが、まずは安全な移動を推奨します)。
7. レポート: 処理結果をコンソールに表示。
5.2 コード例:重複ファイル検出ツール
以下のコードをメモ帳などに貼り付け、「`DuplicateFileRemover.vbs`」などの名前で保存してください。
Option Explicit ‘ 変数の宣言を強制し、スペルミスによるエラーを防ぎます。
‘ ==============================================================================
‘ 設定セクション: ここを編集して、スクリプトの動作をカスタマイズしてください。
‘ ==============================================================================
‘ ★重要★ 検索対象とするルートフォルダのパスを指定してください。
‘ 例: “C:\Users\YourUser\Documents” や “D:\Download” など
Const TARGET_FOLDER = “C:\temp\MyFiles”
‘ 重複ファイルを移動するフォルダのパスを指定してください。
‘ このフォルダが存在しない場合、スクリプトが自動で作成します。
Const DUPLICATE_DEST_FOLDER = “C:\temp\Duplicates”
‘ 重複ファイルを移動する際、元のフォルダ構造を維持するかどうか。
‘ True: “C:\temp\MyFiles\Sub1\file.txt” の重複は “C:\temp\Duplicates\Sub1\file.txt” へ移動。
‘ False: 全ての重複が “C:\temp\Duplicates” 直下へ移動。
Const KEEP_FOLDER_STRUCTURE = True
‘ ファイルを実際に移動するかどうか。
‘ True: ファイルを移動します。
‘ False: 移動せずに、どのファイルが重複であるかコンソールに表示するだけです。(安全確認用)
Const PERFORM_MOVE = False ‘ ★最初は False にして、動作を確認することを強く推奨します!
‘ ==============================================================================
‘ グローバル変数およびオブジェクトの宣言
‘ ==============================================================================
Dim objFSO ‘ FileSystemObject: ファイルやフォルダの操作に使用
Dim objShell ‘ WScript.Shell: 外部コマンドの実行などに使用
Dim objDict ‘ Scripting.Dictionary: ハッシュ値とファイルパスのペアを管理し、重複判定に使用
Dim colDuplicateFiles ‘ Collection: 重複と判定されたファイルのパスを格納
Dim objTargetFolder ‘ 検索対象のルートフォルダオブジェクト
‘ ==============================================================================
‘ メイン処理の開始
‘ ==============================================================================
Call Main()
‘ ==============================================================================
‘ メイン処理サブルーチン
‘ ==============================================================================
Sub Main()
WScript.Echo “==================================================”
WScript.Echo ” 重複ファイル検出・整理ツール (VBScript版)”
WScript.Echo “==================================================”
WScript.Echo “検索対象フォルダ: ” & TARGET_FOLDER
WScript.Echo “重複移動先フォルダ: ” & DUPLICATE_DEST_FOLDER
WScript.Echo “フォルダ構造維持: ” & IIf(KEEP_FOLDER_STRUCTURE, “はい”, “いいえ”)
WScript.Echo “ファイル移動実行: ” & IIf(PERFORM_MOVE, “はい (注意!)”, “いいえ (安全確認モード)”)
WScript.Echo “————————————————–”
‘ オブジェクトの初期化
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objShell = CreateObject(“WScript.Shell”)
Set objDict = CreateObject(“Scripting.Dictionary”)
Set colDuplicateFiles = CreateObject(“Scripting.Collection”)
‘ 対象フォルダの存在チェック
If Not objFSO.FolderExists(TARGET_FOLDER) Then
WScript.Echo “[エラー] 指定された検索対象フォルダが存在しません: ” & TARGET_FOLDER
Call Cleanup()
WScript.Quit
End If
Set objTargetFolder = objFSO.GetFolder(TARGET_FOLDER)
‘ 重複ファイルの移動先フォルダを作成(存在しない場合)
If Not objFSO.FolderExists(DUPLICATE_DEST_FOLDER) Then
On Error Resume Next ‘ エラーが発生してもスクリプトを続行
objFSO.CreateFolder DUPLICATE_DEST_FOLDER
If Err.Number <> 0 Then
WScript.Echo “[エラー] 重複ファイル移動先フォルダの作成に失敗しました: ” & DUPLICATE_DEST_FOLDER
WScript.Echo “エラー詳細: ” & Err.Description
Call Cleanup()
WScript.Quit
Else
WScript.Echo “[情報] 重複ファイル移動先フォルダを作成しました: ” & DUPLICATE_DEST_FOLDER
End If
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
End If
WScript.Echo “[情報] ファイルのハッシュ値計算と重複判定を開始します…”
‘ ルートフォルダから再帰的にファイルを走査
Call ProcessFolder(objTargetFolder)
WScript.Echo “————————————————–”
WScript.Echo “[処理完了] 重複ファイルの検出が完了しました。”
WScript.Echo “検出された重複ファイル数: ” & colDuplicateFiles.Count
If colDuplicateFiles.Count > 0 Then
WScript.Echo “— 重複ファイルリスト —”
Dim strFilePath, strDestPath, strOriginalPath
For Each strFilePath In colDuplicateFiles
‘ 移動先パスを決定
If KEEP_FOLDER_STRUCTURE Then
‘ ルートフォルダからの相対パスを計算
strOriginalPath = objFSO.GetParentFolderName(strFilePath)
Dim strRelativePath
strRelativePath = Replace(strOriginalPath, objTargetFolder.Path, “”)
‘ 先頭の “\” を削除
If Left(strRelativePath, 1) = “\” Then strRelativePath = Mid(strRelativePath, 2)
strDestPath = objFSO.BuildPath(DUPLICATE_DEST_FOLDER, strRelativePath)
If Not objFSO.FolderExists(strDestPath) Then
On Error Resume Next
objFSO.CreateFolder strDestPath
If Err.Number <> 0 Then
WScript.Echo “[警告] 移動先サブフォルダ作成失敗: ” & strDestPath & ” エラー: ” & Err.Description
‘ 作成失敗しても処理は続行(親フォルダ直下への移動を試みる)
strDestPath = DUPLICATE_DEST_FOLDER
Err.Clear
End If
On Error GoTo 0
End If
strDestPath = objFSO.BuildPath(strDestPath, objFSO.GetFileName(strFilePath))
Else
strDestPath = objFSO.BuildPath(DUPLICATE_DEST_FOLDER, objFSO.GetFileName(strFilePath))
End If
WScript.Echo ” 重複: “”” & strFilePath & “”” -> 移動先: “”” & strDestPath & “”””
If PERFORM_MOVE Then
On Error Resume Next
objFSO.MoveFile strFilePath, strDestPath
If Err.Number <> 0 Then
WScript.Echo ” [エラー] ファイルの移動に失敗しました: ” & strFilePath
WScript.Echo ” エラー詳細: ” & Err.Description
Err.Clear
Else
WScript.Echo ” [移動完了] ” & objFSO.GetFileName(strFilePath)
End If
On Error GoTo 0
End If
Next
Else
WScript.Echo “[情報] 重複ファイルは見つかりませんでした。”
End If
WScript.Echo “==================================================”
WScript.Echo “処理が終了しました。”
Call Cleanup()
End Sub
‘ ==============================================================================
‘ 再帰的にフォルダ内のファイルを処理するサブルーチン
‘ ==============================================================================
Sub ProcessFolder(objFolder)
WScript.Echo ” [走査中] ” & objFolder.Path
‘ フォルダ内のファイルを処理
Dim objFile
For Each objFile In objFolder.Files
If objFile.Size = 0 Then ‘ サイズが0バイトのファイルはスキップ(ハッシュ計算不要なため)
WScript.Echo ” [スキップ] 0バイトファイル: ” & objFile.Path
GoTo NextFile
End If
‘ ファイルのハッシュ値を計算
Dim strHash
strHash = GetFileSHA256Hash(objFile.Path)
If Len(strHash) = 64 Then ‘ SHA256ハッシュは64文字であることを確認
‘ Dictionaryで重複を判定
If objDict.Exists(strHash) Then
‘ 重複ファイルが見つかった!
colDuplicateFiles.Add objFile.Path ‘ 重複ファイルリストに追加
WScript.Echo ” [重複検出] ” & objFile.Path & ” (オリジナル: ” & objDict.Item(strHash) & “)”
Else
‘ 初めて見つかったファイルなので、Dictionaryに登録
objDict.Add strHash, objFile.Path ‘ ハッシュ値をキー、ファイルパスを値として登録
End If
Else
WScript.Echo ” [警告] ハッシュ値の取得に失敗、または不正なハッシュ値: ” & objFile.Path & ” (” & strHash & “)”
End If
NextFile:
Next
‘ サブフォルダを再帰的に処理
Dim objSubFolder
For Each objSubFolder In objFolder.SubFolders
Call ProcessFolder(objSubFolder)
Next
End Sub
‘ ==============================================================================
‘ CertUtilを使ってファイルのSHA256ハッシュを取得する関数
‘ ==============================================================================
Function GetFileSHA256Hash(strFilePath)
GetFileSHA256Hash = “” ‘ 初期値を設定
Dim strCommand
‘ コマンド文字列の構築: パスにスペースが含まれる可能性があるので、ダブルクォーテーションで囲む
strCommand = “CertUtil -hashfile “”” & strFilePath & “”” SHA256″
On Error Resume Next ‘ 外部コマンド実行時のエラーを捕捉
Dim objExec
Set objExec = objShell.Exec(strCommand)
‘ コマンドの終了を待機
‘ CertUtilが大量のファイルを扱う際に、完了まで時間がかかる可能性があるため、
‘ WScript.Sleepで待機時間を設けることで、CPU負荷を適切に管理します。
Do While objExec.Status = 0 ‘ WshRunning (0)
WScript.Sleep 50 ‘ 50ミリ秒待つ
Loop
If Err.Number <> 0 Then
WScript.Echo ” [エラー] CertUtil実行中にエラーが発生しました: ” & strFilePath
WScript.Echo ” エラー詳細: ” & Err.Description
Err.Clear ‘ エラーをクリアして続行
Exit Function
End If
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
Dim strOutput
strOutput = objExec.StdOut.ReadAll
‘ CertUtilの出力からハッシュ値だけを抽出
‘ 出力例:
‘ SHA256 ハッシュ (ファイル):
‘ a3e5c9b0e1d2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8
‘ CertUtil: -hashfile コマンドは正常に完了しました。
Dim arrLines
arrLines = Split(strOutput, vbCrLf) ‘ 改行コードで分割
If UBound(arrLines) >= 1 Then
Dim strHashLine
strHashLine = Trim(arrLines(1)) ‘ 2行目(インデックス1)がハッシュ値の行
‘ ハッシュ値は通常64文字の16進数
If Len(strHashLine) = 64 And IsHex(strHashLine) Then
GetFileSHA256Hash = strHashLine
End If
End If
Set objExec = Nothing ‘ オブジェクトの解放
End Function
‘ ==============================================================================
‘ 文字列が16進数であるかを確認するヘルパー関数
‘ ==============================================================================
Function IsHex(strInput)
Dim i
For i = 1 To Len(strInput)
Dim char
char = Mid(strInput, i, 1)
If Not ((char >= “0” And char <= "9") Or _
(char >= “a” And char <= "f") Or _
(char >= “A” And char <= "F")) Then
IsHex = False
Exit Function
End If
Next
IsHex = True
End Function
' ==============================================================================
' オブジェクトの解放処理
' ==============================================================================
Sub Cleanup()
If Not objDict Is Nothing Then Set objDict = Nothing
If Not objFSO Is Nothing Then Set objFSO = Nothing
If Not objShell Is Nothing Then Set objShell = Nothing
If Not colDuplicateFiles Is Nothing Then Set colDuplicateFiles = Nothing
If Not objTargetFolder Is Nothing Then Set objTargetFolder = Nothing
End Sub
5.3 コードの解説とポイント
一つずつ、重要な部分を掘り下げていきましょう。
`Option Explicit` の重要性
Option Explicit
これはスクリプトの冒頭に書くべき「おまじない」のようなものです。これを記述すると、全ての変数を `Dim` で明示的に宣言しなければならなくなります。初学者の方には少し面倒に感じるかもしれませんが、これによってスペルミスによるエラーや、意図しない変数の上書きを防ぐことができ、デバッグの手間を大幅に削減できます。プロの現場では必須中の必須です。
設定セクション
Const TARGET_FOLDER = “C:\temp\MyFiles”
Const DUPLICATE_DEST_FOLDER = “C:\temp\Duplicates”
Const KEEP_FOLDER_STRUCTURE = True
Const PERFORM_MOVE = False ‘ ★最初は False にして、動作を確認することを強く推奨します!
ここが、スクリプトの動作を決定する最も重要な部分です。
- `TARGET_FOLDER`: 重複ファイルを検索したいルートフォルダを指定します。
- `DUPLICATE_DEST_FOLDER`: 検出された重複ファイルを移動する先のフォルダです。
- `KEEP_FOLDER_STRUCTURE`: `True` にすると、移動先でも元のフォルダ階層を維持しようとします。例えば `TARGET_FOLDER\A\B\file.txt` の重複は `DUPLICATE_DEST_FOLDER\A\B\file.txt` へ移動されます。
- `PERFORM_MOVE`: ここが特に重要です! 最初は必ず `False` に設定してください。この設定だと、スクリプトは重複ファイルを検出してリストアップするだけで、実際のファイル移動は行いません。リストを確認し、意図通りに動作することを確認してから `True` に変更し、ファイルを移動させましょう。
オブジェクトの初期化と解放
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objShell = CreateObject(“WScript.Shell”)
Set objDict = CreateObject(“Scripting.Dictionary”)
Set colDuplicateFiles = CreateObject(“Scripting.Collection”)
‘ … 処理 …
Call Cleanup()
‘ … Cleanupサブルーチン …
Sub Cleanup()
If Not objDict Is Nothing Then Set objDict = Nothing
If Not objFSO Is Nothing Then Set objFSO = Nothing
‘ … 他のオブジェクトも同様に解放 …
End Sub
VBScriptでオブジェクト(`FileSystemObject` や `Dictionary` など)を使う際は、`CreateObject` でインスタンスを作成し、使い終わったら `Set obj = Nothing` で明示的に解放することが良い習慣です。これにより、メモリの無駄遣いを防ぎ、特に長時間実行するスクリプトや、大量のオブジェクトを扱うスクリプトでの安定性を高めます。伝説的なアーキテクトとしては、このオブジェクトのライフサイクル管理は非常に重視する点です。
`ProcessFolder` サブルーチン (再帰処理)
Sub ProcessFolder(objFolder)
For Each objFile In objFolder.Files
‘ ファイル処理 …
Next
For Each objSubFolder In objFolder.SubFolders
Call ProcessFolder(objSubFolder) ‘ ここで自身を呼び出す!
Next
End Sub
このサブルーチンが、指定されたフォルダとその全てのサブフォルダ内のファイルを走査する「再帰処理」を行っています。
1. 現在のフォルダ内のファイルを一つ一つ処理します。
2. 次に、現在のフォルダにあるサブフォルダを一つ一つ取り出し、そのサブフォルダを引数として、もう一度 `ProcessFolder` サブルーチン自身を呼び出します。
この繰り返しによって、フォルダの階層がどれだけ深くても、全てのファイルにアクセスできるようになります。
`GetFileSHA256Hash` 関数 (CertUtil連携)
Function GetFileSHA256Hash(strFilePath)
strCommand = “CertUtil -hashfile “”” & strFilePath & “”” SHA256″
Set objExec = objShell.Exec(strCommand)
‘ … 待機 …
strOutput = objExec.StdOut.ReadAll
‘ … 出力からハッシュ値を抽出 …
End Function
ここで `WshShell.Exec` メソッドを使って `CertUtil` コマンドを実行し、その標準出力(`objExec.StdOut.ReadAll`)を丸ごと取得しています。取得した文字列からハッシュ値だけを抽出するために、`Split` 関数で改行を区切り文字として文字列を配列に分割し、適切な行(2行目)を取り出しています。
また、`Do While objExec.Status = 0` のループでコマンドの終了を待機する際、`WScript.Sleep 50` を挟んでいます。これは、CPUへの過度な負荷を避けるための配慮です。特に大量のファイルを扱う場合、外部コマンドの実行と待機を繰り返すため、この小さな工夫がシステムの安定性に寄与します。
重複判定ロジック
If objDict.Exists(strHash) Then
colDuplicateFiles.Add objFile.Path
WScript.Echo ” [重複検出] ” & objFile.Path & ” (オリジナル: ” & objDict.Item(strHash) & “)”
Else
objDict.Add strHash, objFile.Path
End If
これがDictionaryの真骨頂です。
1. 取得した `strHash` が `objDict` に既にキーとして存在するか (`objDict.Exists(strHash)`) をチェックします。
2. 存在すれば (`True`)、それは重複ファイルなので、`colDuplicateFiles` (重複ファイルのパスを格納するコレクション) にそのファイルのパスを追加し、コンソールにメッセージを表示します。
3. 存在しなければ (`False`)、それは初めて見つかったハッシュ値なので、`objDict.Add strHash, objFile.Path` でDictionaryにハッシュ値とファイルパスのペアを登録します。このファイルパスが、そのハッシュ値を持つ「最初のファイル」として記録されます。
ファイル移動処理
If PERFORM_MOVE Then
On Error Resume Next
objFSO.MoveFile strFilePath, strDestPath
‘ … エラーハンドリング …
End If
`PERFORM_MOVE` が `True` の場合にのみ、`FileSystemObject.MoveFile` メソッドでファイルを移動します。
ここでも `On Error Resume Next` を使用して、ファイル移動中に発生しうるエラー(アクセス権限がない、ファイルがロックされているなど)を捕捉し、スクリプトが途中で停止しないように配慮しています。エラーが発生した場合でも、そのファイルだけをスキップして処理を続行し、警告メッセージを表示するようにしています。
—
6. 陥りやすいエラーとデバッグのヒント
VBScriptを書き始めたばかりの頃は、思わぬエラーに遭遇することもあるでしょう。
6.1 よくあるエラーとその対策
- 「オブジェクトが必要です」エラー (実行時エラー ‘424’):
`Set` キーワードを忘れていたり、`CreateObject` でオブジェクトが正しく作成されていない場合に発生します。特に `Dim` 宣言はしているが `Set` で初期化していない場合に起こりがちです。
- 「プロパティまたはメソッドをサポートしていません」エラー:
存在しないプロパティやメソッドを呼び出そうとした場合に発生します。例えば、`objFSO.Move` のようにタイプミスしている場合や、`objFSO.Files` のようにコレクションを直接移動しようとした場合などです。オブジェクトが何のメソッドやプロパティを持っているか、確認しましょう。
- パスが見つからない、アクセス拒否、ファイルがロックされている:
ファイル操作でよくあるエラーです。
- `TARGET_FOLDER` や `DUPLICATE_DEST_FOLDER` のパスが間違っていないか確認してください。
- 対象ファイルやフォルダにスクリプトを実行しているユーザーの読み書き権限があるか確認してください。
- 開いているファイルは移動/削除できません。対象ファイルを閉じているか確認してください。
- これらのエラーは `On Error Resume Next` と `Err.Number` で捕捉し、処理を続行できます。
- CertUtilコマンドが見つからない、ハッシュ値が取得できない:
- `CertUtil` が正しく実行されているか、コマンドプロンプトで手動で実行して確認してください。
- ファイルパスに特殊文字が含まれていないか確認してください。ダブルクォーテーションで囲むことで多くの問題は解決します。
- `GetFileSHA256Hash` 関数の内部で `WScript.Echo strOutput` を一時的に追加して、`CertUtil` がどんな出力をしているか確認すると良いでしょう。
6.2 デバッグの味方:`WScript.Echo` と `MsgBox`
何かおかしいな、と思ったら、スクリプトの途中に `WScript.Echo` または `MsgBox` を挿入して、変数の値や処理の進行状況を確認するのが最も手軽なデバッグ方法です。
- `WScript.Echo “現在のハッシュ値: ” & strHash`
- `MsgBox “ファイル処理中: ” & objFile.Path`
`WScript.Echo` はコマンドプロンプトで `cscript` を使って実行した場合に、画面に表示されるので、大量の情報を見るのに適しています。`MsgBox` はGUIでメッセージボックスが表示されるので、特定の重要な箇所で処理を一時停止させ、確認するのに便利です。
—
7. さらに磨きをかける:応用と改善のアイデア
今回のスクリプトは基本的な機能を提供しますが、さらにプロのツールに近づけるための改善点や応用もたくさんあります。
- ファイルサイズのフィルタリング:
ハッシュ値を計算する前に、ファイルサイズが同じファイルだけを比較対象にする。これにより、`CertUtil` の実行回数を減らし、処理時間を大幅に短縮できる可能性があります。
`If objFile.Size = objDict.Item(strHash).Size Then` のように、Dictionaryにファイルサイズも保存しておくことで実現できます。
- レポートの強化:
重複ファイルをコンソールに表示するだけでなく、CSVファイルやHTMLファイルとして詳細なレポートを出力するようにする。重複ファイルのサイズ合計、削除候補のファイルリストなどをわかりやすく提示できます。
- GUI化(HTAへの発展):
VBScriptはHTA (HTML Application) と組み合わせることで、Webページのようなグラフィカルユーザーインターフェースを持つアプリケーションに変身させることができます。これにより、対象フォルダの選択をボタンクリックで行ったり、進行状況をプログレスバーで表示したりと、より使いやすいツールに進化させられます。
- ファイル削除機能の追加:
`PERFORM_MOVE` とは別に、ユーザーの確認を経て、重複ファイルを直接削除する機能を追加することも可能です。(ただし、非常に慎重な実装と利用が必要です!)
- 他のハッシュアルゴリズムの利用:
CertUtilはSHA256だけでなく、MD5, SHA1などのハッシュアルゴリズムもサポートしています。セキュリティ要件に応じて使い分けることもできますが、SHA256が現代では最も推奨される堅牢なアルゴリズムです。
—
8. まとめ:VBScriptの基本を掌握し、自動化の世界へ!
お疲れ様でした! 今回は、VBScriptを使ってディスク内の重複ファイルを検出・整理する実用的なツールを一緒に作成しました。
- `CertUtil` コマンドでファイルの「指紋」であるハッシュ値を取得する方法。
- `Scripting.Dictionary` オブジェクトを使って高速に重複を判定するロジック。
- `FileSystemObject` によるファイル・フォルダ操作と、再帰処理によるフォルダ走査。
- そして、VBScriptのスクリプト構造、変数の宣言 (`Option Explicit`)、オブジェクトのライフサイクル管理 (`Set obj = Nothing`) といった、VBScriptの基本的ながらも本質的な概念を深く学ぶことができたと思います。
今回学んだ知識は、ファイルの整理だけでなく、ログファイルの解析、設定ファイルの自動更新、定期的なレポート作成など、様々なWindows環境での自動化タスクに応用できます。
これまでのマクロの記録から一歩踏み出し、VBScriptを使ってOSを直接操作する力を手に入れましたね。ここをクリアすれば、VBScriptの基本はバッチリですよ!
このツールをあなたの環境に合わせてカスタマイズし、ぜひ業務や日々のPC管理に役立ててください。
VBScriptの可能性はまだまだ無限大です。これからも一緒に、自動化の探求を続けていきましょう!
