【VBScript極限解説】OpenTextFile完全攻略:ログ操作で「消えない・壊れない」堅牢なファイルI/Oを実装する
プロフェッショナルな業務自動化エンジニアであれば、VBScriptにおけるファイル操作が単なる「おまけの機能」ではないことを知っているはずだ。
レガシーシステム、深夜のバッチ処理、キッティングの自動化、そしてFA(ファクトリーオートメーション)の現場。今なお、OS標準で動作するVBScriptは、インフラの隙間を埋める最強の接着剤として君臨している。
その中でも最も頻度が高く、かつ「実装者の技量がモロに出る」のが、`FileSystemObject`(以下、FSO)の `OpenTextFile` メソッドを用いたログファイルの作成・追記・読み込みだ。
「とりあえず動くコード」を書くだけなら素人でもできる。しかし、多重起動時の競合、文字化け、リソースリーク、そして例外処理の欠落によって、現場で「朝出社したらログファイルが消えていた」「バッチがフリーズして止まらない」といった惨劇を引き起こすコードを、私は数え切れないほど見てきた。
今回は、`OpenTextFile` の本質を骨の髄まで理解し、現場の信頼に耐えうる「完全版」のログ操作手法を授けよう。
—
1. `OpenTextFile` を制する者、VBScriptのI/Oを制す
FSOの `OpenTextFile` は、テキストストリームを開くための唯一無二のメソッドだ。
まずはその構文と、裏で何が起きているのかを正確に把握してほしい。
Set oStream = oFSO.OpenTextFile(filename, [iomode], [create], [format])
この引数の設計思想を理解していないエンジニアが多すぎる。特に重要なのは第2引数 `iomode` と、第4引数 `format` だ。
3つのモードの正しい選択と「罠」
1. `ForReading` (1): 読み取り専用。ファイルが存在しない場合は容赦なくエラー(Run-time error)を吐いてクラッシュする。
2. `ForWriting` (2): 上書き書き込み。これが諸悪の根源になりやすい。 ファイルが存在しない場合は新規作成されるが、存在する場合は既存の内容が完全に消去(トランケート)される。 追記のつもりでこれを使うと、過去のログが全て吹き飛ぶ。
3. `ForAppending` (8): 追記モード。ファイル末尾にカーソルが移動し、既存データを保持したままデータを追加する。ログ出力は基本的にこれ一択だ。
文字コード(`format`)の闇
第4引数の `format` は、VBScript最大の罠の一つである。
- TristateFalse (-2): システムのデフォルト(ANSI)で開く。
- TristateTrue (-1): Unicode(UTF-16LE)で開く。
- TristateUseDefault (0): デフォルト値。
現代のインフラ環境において、UTF-8(BOM付き/なし)が主流である中、VBScriptの標準機能はUTF-8を直接ネイティブサポートしていない(ANSIかUTF-16のみ)。ここを誤ると、別システム(PythonやPowerShellなど)でログを読み込んだ際に盛大な文字化けを引き起こす。
実務では、「標準のANSI(Shift_JIS等)で出力するか、あるいはADODB.Streamを使って純粋なUTF-8制御に逃げるか」の判断が求められる。今回は、純血のVBScript(FSO単体)で最も安定するANSI/Shift_JISを前提に、堅牢な実装を解説する。
—
2. 非効率なコードのアンチパターン
現場でよく見かける「やってはいけない」実装例を見てみよう。
‘ 【悪例】絶対に真似してはならないアンチパターン
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ループのたびにファイルを開閉する(ディスクI/Oの暴風雨、パフォーマンス最悪)
Sub WriteLog(msg)
Set f = fso.OpenTextFile(“C:\Logs\app.log”, 2, True) ‘ 2(上書き)を使っているので過去ログが消える!
f.WriteLine Now & ” : ” & msg
f.Close
End Sub
何がダメなのか?
1. 毎回 `OpenTextFile` を呼んでいる: ディスクアクセスはメモリに比べて圧倒的に遅い。高頻度でこれをやるとOSのI/Oを圧迫し、プロセスがデッドロックする危険性もある。
2. `ForWriting (2)` を使っている: ログ関数なのに上書きするため、1回実行されるたびにファイルがリセットされる。
3. エラーハンドリングがない: ファイルが別のプロセス(タスクマネージャーやテキストエディタ)で掴まれていた場合、即座にスクリプトが異常終了する。
—
3. 【プロダクションコード】堅牢・高速なログ管理クラス(WSC/VBS)
ここからが本題だ。実務でそのままコピー&ペーストして使える、エラーハンドリングとリソース解放を完璧に網羅した「ログ出力・読み込みモジュール」の決定版を提供する。
‘ ==============================================================================
‘ 檔案名: LoggerModule.vbs
‘ 概要: 堅牢なファイルI/Oを実現するロガークラス
‘ 開発現場のプロフェッショナル要件(例外耐性・リソース管理)を完全網羅
‘ ==============================================================================
Option Explicit
Class AppLogger
Private m_FSO
Private m_LogPath
Private m_IsInitialized
‘ コンストラクタ
Private Sub Class_Initialize()
Set m_FSO = CreateObject(“Scripting.FileSystemObject”)
m_IsInitialized = False
End Sub
‘ デストラクタ(確実にリソースを解放)
Private Sub Class_Terminate()
Set m_FSO = Nothing
End Sub
‘ 初期化:ログ保存先のディレクトリ担保とパス設定
Public Function Initialize(ByVal logFilePath)
On Error Resume Next
m_LogPath = logFilePath
‘ フォルダが存在しない場合は自動生成する
Dim parentDir
parentDir = m_FSO.GetParentFolderName(m_LogPath)
If Not m_FSO.FolderExists(parentDir) Then
m_FSO.CreateFolder(parentDir)
End If
If Err.Number <> 0 Then
Initialize = False
Exit Function
End If
On Error GoTo 0
m_IsInitialized = True
Initialize = True
End Function
‘ 【書き込み】ログ追記メソッド(ForAppending使用)
Public Sub WriteLog(ByVal logLevel, ByVal message)
If Not m_IsInitialized Then Exit Sub
Dim ts, retryCount
retryCount = 0
‘ ファイル競合(他プロセスによるロック)対策のリトライロジック
Do While retryCount < 3
On Error Resume Next
' 第2引数: 8 (ForAppending), 第3引数: True (なければ作成)
Set ts = m_FSO.OpenTextFile(m_LogPath, 8, True, 0)
If Err.Number = 0 Then
Exit Do ' 成功したらループを抜ける
End If
Err.Clear
retryCount = retryCount + 1
WScript.Sleep 200 ' 200ミリ秒待機してリトライ
Loop
If Err.Number <> 0 Then
‘ 最終的に書き込めなかった場合はイベントログ等に逃がすべきだが、
‘ ここではサイレントまたはデバッグ出力に留める
On Error GoTo 0
Exit Sub
End If
‘ ログフォーマット: [YYYY-MM-DD HH:MM:SS] [LEVEL] Message
Dim formattedLog
formattedLog = “[” & Now & “] [” & UCase(logLevel) & “] ” & message
ts.WriteLine formattedLog
ts.Close
Set ts = Nothing
On Error GoTo 0
End Sub
‘ 【読み込み】ログファイル全取得メソッド(ForReading使用)
Public Function ReadAllLogs()
If Not m_IsInitialized Then
ReadAllLogs = “”
Exit Function
End If
If Not m_FSO.FileExists(m_LogPath) Then
ReadAllLogs = “”
Exit Function
End If
On Error Resume Next
Dim ts
Set ts = m_FSO.OpenTextFile(m_LogPath, 1, False, 0) ‘ 1: ForReading
If Err.Number <> 0 Then
ReadAllLogs = “”
Exit Function
End If
Dim content
If Not ts.AtEndOfStream Then
content = ts.ReadAll()
Else
content = “”
End If
ts.Close
Set ts = Nothing
On Error GoTo 0
ReadAllLogs = content
End Function
‘ 【行単位読み込み】最新のN行、あるいは条件エンティティの抽出用
‘ 巨大なログファイルをメモリリークさせずに走査する基本形
Public Sub ReadLineByLine(ByRef callbackObj)
‘ 実務ではコールバック関数やオブジェクトに渡して処理を委譲する設計が美しい
End Sub
End Class
‘ ==============================================================================
‘ 【使用例・テストコード】
‘ ==============================================================================
Sub Main()
Dim logger
Set logger = New AppLogger
‘ ログファイルのパス設定
Dim logFile
logFile = “C:\AutomationLogs\Operations.log”
If logger.Initialize(logFile) Then
logger.WriteLog “INFO”, “バッチ処理を開始します。”
logger.WriteLog “WARN”, “メモリ使用量が閾値を超えています。”
logger.WriteLog “ERROR”, “データベース接続タイムアウトが発生しました。”
WScript.Echo “ログの書き込みが完了しました。”
‘ 読み込みテスト
Dim allLogs
allLogs = logger.ReadAllLogs()
WScript.Echo “— 記録されたログ —”
WScript.Echo allLogs
Else
WScript.Echo “ロガーの初期化に失敗しました。”
End If
Set logger = Nothing
End Sub
‘ 実行の際はコメントアウトを解除
‘ Main()
—
4. プロが教える「現場の知見(ベストプラクティス)」
上記のコードを見て、「なぜここまで手厚く書いているのか」疑問に思うかもしれない。実務においてVBScriptを運用する際、以下の壁に必ずぶ当たるからだ。
① ファイルロック競合問題(The File Sharing Violation)
タスクスケジューラで複数のVBScriptが同時に走ったり、人間がログファイルをエディタ(サクラエディタやNotepad++など)で開いたままにしていると、`OpenTextFile` は容赦なく `Error 70: Permission denied(書き込み権限がありません)` を吐いてクラッシュする。
上記のプロダクションコードに組み込んだ 「リトライロジック(`Do While` と `WScript.Sleep`)」 こが、夜間バッチを絶対に止まらせないためのエンジニアリングの防壁なのだ。
② メモリ管理とオブジェクトの破棄
VBScriptのガベージコレクションは頼りにならない。`OpenTextFile` で生成した `TextStream` オブジェクト(コード中の `ts`)は、用が済んだら速やかに `.Close` し、`Set ts = Nothing` でメモリから明示的に解放しなければならない。これを怠ると、巨大なログファイルを処理する際にVBScriptホスト(`wscript.exe` / `cscript.exe`)のメモリリークを引き起こし、サーバーをジワジワと追い詰めることになる。
③ 巨大化するログのローテーション設計
ログファイルが数ギガバイトに肥大化すると、`ReadAll` メソッドを実行した瞬間にVBScriptの文字列バッファが破綻し、スクリプトが沈黙する。
プロダクション環境では、ログファイルが一定のサイズ(例: 10MB)を超えたら、`m_FSO.MoveFile` または `CopyFile` で `app_YYYYMMDD.log` のようにリネーム・退避させる「ログローテーション」の仕組みをFSOと組み合わせて実装するのが、真のプロフェッショナルの仕事である。
—
5. おわりに
「VBScriptなんて古い言語だ」と侮る者ほど、現場でバグを踏み踏みして途方に暮れる。
オブジェクトのライフサイクルを理解し、OSのリソース制約をリスペクトしたコードを書くこと。それは言語がVBScriptであろうと、最新のTypeScriptであろうと何ら変わりはない。
今回解説した `OpenTextFile` の本質と堅牢な設計手法をあなたの武器庫に加えれば、いかなるレガシー環境の自動化案件であっても、ビクともしない強固なシステムを構築できるはずだ。
さあ、その手元のエディタを開き、美しいコードを実装しよう。
