【テクニカル・上級編】【上級プロフェッショナル向け】大量の小規模テキストファイルをFSOで1つの独自アーカイブファイルにパッキング&相互復元するツール – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【上級プロフェッショナル向け】FSOの限界を突破する:数千の小規模ファイルを単一コンテナへパッキング&復元する極限のアーキテクチャ

レガシーシステムの保守、あるいはサンドボックス化された閉域網の端末間におけるデータ移行。私たちはしばしば、数千・数万という極小サイズのテキストファイル群のハンドリングという悪夢に直面する。

Windowsのファイルシステム(NTFS)において、数KB規模のファイルを数千個生成・走査することは、MFT(Master File Table)の断片化、I/Oのボトルネック、そして何よりもファイルシステム・オーバーヘッドによる致命的なパフォーマンス低下を招く。

ここで提示する解は、外部DLLやサードパーティ製ツールへの依存を一切排し、標準コンポーネントである `Scripting.FileSystemObject (FSO)` と ADODB Stream のみを武器に、数千のファイルを単一の「独自アーカイブファイル(コンテナ)」へ高効率に集約(パッキング)し、完全な状態で相互復元する、プロダクション品質のVBScriptアーキテクチャである。

—

1. アーキテクチャの設計思想:なぜFSO単体では不十分で、何が必要なのか?

FSOはフォルダの列挙やファイルのテキスト読み書きには優れているが、純粋なバイナリ結合や任意のストリーム制御においては、そのままでは力不足だ。特に、複数ファイルを1つに束ねる場合、以下の課題をクリアしなければならない。

1. メタデータ(ファイル名、サイズ、オフセット)の管理
どの領域がどのファイルのものかを識別するためのヘッダー構造が必要。
2. 文字コードの混在とバイナリ透過性
Shift-JIS、UTF-8 (BOMあり/なし)、UTF-16など、多様な文字コードを持つテキストファイルを、データ損失なく安全にコンテナへ格納する必要がある。
3. FSOテキストストリームの限界
`OpenTextFile` はテキスト処理に特化しているため、バイナリデータの結合には向かない。ここで `ADODB.Stream` をバイナリモード(Type = 1)で組み合わせることで、メモリ上での高速なストリーム結合を実現する。

独自コンテナのバイナリレイアウト

+——————————————————-+
| シグネチャ (4Bytes: “VBSF”) |
+——————————————————-+
| ファイル総数 (4Bytes: Long) |
+——————————————————-+
| — [ヘッダーブロック 1] —————————–+
| パス長 (2Bytes) | 相対パス文字列 (可変長) |
| データサイズ (4Bytes: Long) |
+——————————————————-+
| — [ヘッダーブロック 2] … |
+——————————————————-+
| — [データペイロード 1] (バイナリデータ) |
+——————————————————-+
| — [データペイロード 2] … |
+——————————————————-+

—

2. 実装コード:パッカー&アンパッカー統合スクリプト

以下のコードは、指定したディレクトリ内のすべてのテキストファイルを単一の `.arc` ファイルに圧縮(パッキング)し、そこから元のファイル群を完全に再構築(アンパッキング)するための実用スクリプトである。

エラーハンドリング、オブジェクトの明示的な破棄(メモリリークの完全防止)、そしてストリームのバイナリ操作を極限まで最適化している。

‘ ==============================================================================
‘ FileArchiver.vbs
‘ Architecture: FSO + ADODB.Stream Container Engine
‘ Description: 数千の小規模テキストファイルを単一の独自バイナリコンテナに集約・復元
‘ ==============================================================================
Option Explicit

Const adTypeBinary = 1
Const adSaveCreateOverWrite = 2

‘ メイン処理の実行
Call Main()

Sub Main()
Dim fso, targetDir, archivePath, restoreDir
Set fso = CreateObject(“Scripting.FileSystemObject”)

targetDir = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), “SourceFiles”)
archivePath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), “package.arc”)
restoreDir = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), “RestoredFiles”)

‘ 1. 検証用ディレクトリの準備
If Not fso.FolderExists(targetDir) Then
WScript.Echo “[ERROR] パッキング対象のフォルダが存在しません: ” & targetDir
Exit Sub
End If

If Not fso.FolderExists(restoreDir) Then
fso.CreateFolder(restoreDir)
End If

WScript.Echo “=== [1/2] パッキング処理を開始 ===”
Dim startTime: startTime = Timer
Call PackFiles(targetDir, archivePath)
WScript.Echo “パッキング完了. 経過時間: ” & FormatNumber(Timer – startTime, 2) & ” 秒”

WScript.Echo vbCrLf & “=== [2/2] 復元(アンパッキング)処理を開始 ===”
startTime = Timer
Call UnpackFiles(archivePath, restoreDir)
WScript.Echo “復元完了. 経過時間: ” & FormatNumber(Timer – startTime, 2) & ” 秒”

Set fso = Nothing
WScript.Echo vbCrLf & “すべてのプロセスが正常に終了しました。”
End Sub

‘ ——————————————————————————
‘ パッキング関数 (Pack)
‘ ——————————————————————————
Sub PackFiles(srcDir, destArchive)
Dim fso, folder, files, file
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set folder = fso.GetFolder(srcDir)
Set files = folder.Files

Dim headerStream, dataStream, masterStream
Set headerStream = CreateObject(“ADODB.Stream”)
Set dataStream = CreateObject(“ADODB.Stream”)
Set masterStream = CreateObject(“ADODB.Stream”)

headerStream.Type = adTypeBinary
headerStream.Open
dataStream.Type = adTypeBinary
dataStream.Open
masterStream.Type = adTypeBinary
masterStream.Open

Dim fileCount: fileCount = files.Count

‘ シグネチャ書き込み (“VBSF” = 0x56, 0x42, 0x53, 0x46)
Call WriteStringAsBytes(headerStream, “VBSF”)
‘ ファイル総数を書き込み (4バイト Long)
Call WriteLongToStream(headerStream, fileCount)

Dim tempStream, relPath, pathBytes, pathLen, fileBytes, dataLen
For Each file in files
‘ 相対パスの取得(ここでは直下ファイルを想定。階層化する場合は要拡張)
relPath = file.Name

‘ 個別ファイルをバイナリとして読み込む
Set tempStream = CreateObject(“ADODB.Stream”)
tempStream.Type = adTypeBinary
tempStream.Open
tempStream.LoadFromFile file.Path
fileBytes = tempStream.Read
tempStream.Close
Set tempStream = Nothing

dataLen = LenB(fileBytes)
pathBytes = StringToBytes(relPath)
pathLen = UBound(pathBytes) + 1

‘ ヘッダー情報の書き込み: [パス長(2Bytes)][パス文字列][データサイズ(4Bytes)]
Call WriteIntToStream(headerStream, pathLen)
headerStream.Write pathBytes
Call WriteLongToStream(headerStream, dataLen)

‘ ペイロードデータの書き込み
dataStream.Write fileBytes

WScript.Echo “Packed: ” & relPath & ” (” & dataLen & ” bytes)”
Next

‘ マスターストリームの構築 (ヘッダー + データペイロード)
headerStream.Position = 0
dataStream.Position = 0

masterStream.Write headerStream.Read()
masterStream.Write dataStream.Read()

‘ アーカイブファイルとして保存
masterStream.SaveToFile destArchive, adSaveCreateOverWrite

‘ クリーンアップ(メモリの即時解放)
headerStream.Close
dataStream.Close
masterStream.Close
Set headerStream = Nothing
Set dataStream = Nothing
Set masterStream = Nothing
Set fso = Nothing
End Sub

‘ ——————————————————————————
‘ アンパッキング関数 (Unpack)
‘ ——————————————————————————
Sub UnpackFiles(srcArchive, destDir)
바이ナリ解析と復元
Dim masterStream, fso
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set masterStream = CreateObject(“ADODB.Stream”)

masterStream.Type = adTypeBinary
masterStream.Open
masterStream.LoadFromFile srcArchive

‘ シグネチャ検証
Dim sigBytes: sigBytes = masterStream.Read(4)
If BytesToString(sigBytes) <> “VBSF” Then
WScript.Echo “[ERROR] 無効なアーカイブフォーマットです。”
masterStream.Close
Exit Sub
End If

‘ ファイル総数取得
Dim fileCount: fileCount = ReadLongFromStream(masterStream)

Dim i, pathLen, pathBytes, relPath, dataLen, fileBytes, outStream
For i = 1 to fileCount
pathLen = ReadIntFromStream(masterStream)
pathBytes = masterStream.Read(pathLen)
relPath = BytesToString(pathBytes)
dataLen = ReadLongFromStream(masterStream)

fileBytes = masterStream.Read(dataLen)

‘ ファイルとして書き出す
Set outStream = CreateObject(“ADODB.Stream”)
outStream.Type = adTypeBinary
outStream.Open
outStream.Write fileBytes
outStream.SaveToFile fso.BuildPath(destDir, relPath), adSaveCreateOverWrite
outStream.Close
Set outStream = Nothing

WScript.Echo “Restored: ” & relPath
Next

masterStream.Close
Set masterStream = Nothing
Set fso = Nothing
End Sub

‘ ==============================================================================
‘ バイナリ操作ヘルパー関数群 (Little Endian Implementation)
‘ ==============================================================================

Sub WriteStringAsBytes(stream, str)
Dim i, b
For i = 1 To Len(str)
b = Asc(Mid(str, i, 1))
stream.Write ChrB(b)
Next
End Sub

Function StringToBytes(str)
Dim i, arr()
ReDim arr(Len(str) – 1)
For i = 1 To Len(str)
arr(i – 1) = Asc(Mid(str, i, 1))
Next
StringToBytes = arr
End Function

Function BytesToString(bytes)
Dim i, str
str = “”
For i = LBound(bytes) To UBound(bytes)
str = str & Chr(AscB(MidB(bytes, i + 1, 1)))
Next
BytesToString = str
End Function

Sub WriteIntToStream(stream, intVal)
‘ 2バイト整数 (Integer) をリトルエンディアンで書き込み
Dim b1, b2
b1 = intVal And &HFF
b2 = (intVal \ &H100) And &HFF
stream.Write ChrB(b1) & ChrB(b2)
End Sub

Function ReadIntFromStream(stream)
Dim bytes, b1, b2
bytes = stream.Read(2)
b1 = AscB(MidB(bytes, 1, 1))
b2 = AscB(MidB(bytes, 2, 1))
ReadIntFromStream = b1 + (b2 &H100)
End Function

Sub WriteLongToStream(stream, longVal)
‘ 4バイト整数 (Long) をリトルエンディアンで書き込み
Dim b1, b2, b3, b4
b1 = longVal And &HFF
b2 = (longVal \ &H100) And &HFF
b3 = (longVal \ &H10000) And &HFF
b4 = (longVal \ &H1000000) And &HFF
If b4 < 0 Then b4 = b4 + &H100 ' 符号補正 stream.Write ChrB(b1) & ChrB(b2) & ChrB(b3) & ChrB(b4) End Sub Function ReadLongFromStream(stream) Dim bytes, b1, b2, b3, b4 bytes = stream.Read(4) b1 = AscB(MidB(bytes, 1, 1)) b2 = AscB(MidB(bytes, 2, 1)) b3 = AscB(MidB(bytes, 3, 1)) b4 = AscB(MidB(bytes, 4, 1)) ReadLongFromStream = b1 + (b2 &H100) + (b3 &H10000) + (CDbl(b4) &H1000000) End Function ---

3. チーフアーキテクトが解説する:実運用上の重要知見とチューニング

このコードを実際の基幹システムや大規模運用に投入する際、知っておくべき「現場の知見」をいくつか共有する。

オブジェクトのライフサイクルとメモリプレッシャー

VBScriptのガベージコレクション(COMの参照カウント方式)は完全ではない。特に `ADODB.Stream` は内部に大規模なバイナリバッファを保持するため、ループ内でインスタンスを解放し忘れると、数千ファイル処理する過程でメモリ消費量が爆発的に増大する。上記のコードでは、ループのイテレーションごとに `tempStream` を明示的に `Close` し、`Nothing` を代入して即座にメモリを解放する設計にしている。

ファイルシステムI/Oの最小化

数千のファイルを個別にネットワーク共有フォルダ経由で読み書きすると、SMBプロトコルのオーバーヘッドにより処理時間が数倍に跳ね上がる。このアーカイブツールを用いて「ローカルで1つの大容量ファイルに固めてから転送し、宛先で一括展開する」というパイプラインを組むことで、ネットワークI/Oの効率を劇的に改善できる。

文字コードの透過性

テキストファイルを扱う際、ありがちなトラブルが文字化けである。このスクリプトでは、ファイルを文字列としてではなく「完全なバイナリ(Byte Array / Stream)」としてコンテナに格納しているため、Shift-JIS、UTF-8、さらにはBOMの有無に関わらず、一切のデータ変質を起こさずに完全なロスレス(可逆)復元が担保される。

—

総括

VBScriptはレガシーな言語として片付けられがちだが、OS標準機能のみで完結するアジリティと堅牢性は、現在でも特定のインフラ自動化や閉域網メンテナンスにおいて無類の強さを誇る。FSOとADODB.Streamの特性を極限まで引き出すことで、専用のアーカイバソフトがない環境であっても、高度なデータ集約パイプラインを構築することが可能だ。現場のエンジニア諸賢の武器庫に、ぜひこの知見を加えてほしい。

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