【INI設定ファイル操作】API非依存で動作する自作のINIファイル読み込み・書き込み・セクション解析モジュール
レジストリ(Registry)の肥大化を避け、業務自動化ツールの構成をクリーンに保つために、いまだにINIファイルは強力な選択肢だ。しかし、Windows標準の `GetPrivateProfileString` といったWin32 APIを `DllImport` や `Declare` で呼び出すアプローチは、VBScript(WSH環境)においてしばしばセキュリティポリシーや32bit/64bitのアーキテクチャ差異による壁に阻まれる。
余計な外部依存を排除し、純粋なテキストストリーム処理だけで、堅牢かつ高速にセクションとキーを支配する――今回は、実務の現場で即座に使える「完全API非依存のINIファイル操作モジュール」を伝授する。
—
なぜAPI依存や安易な文字列分割は破綻するのか?
多くのプログラマブルなスクリプト初心者や、場当たり的なコードを書くエンジニアは、INIファイルを読み込む際に `Split(line, “=”)` といった短絡的な手法をとる。しかし、これでは以下の実務的な罠に必ず足を取られる。
1. 文字コードとBOMの罠: `Scripting.FileSystemObject (FSO)` の `OpenTextFile` は、デフォルトでUnicode (UTF-16) やShift-JISの判定を誤ることがあり、BOM付きファイルで文字化けを起こす。
2. コメントと空白のノイズ: “;“ や “#“ で始まるコメント行、前後の無駄な空白、キーが存在しない空行やセクションヘッダー `[Section]` の判定漏れによるインデックスエラー。
3. 書き込み時の構造破壊: 単にファイル末尾に追記するだけでは、既存のキーが重複して肥大化するか、セクションの概念が崩壊する。
これを解決するためには、「ファイルを一度仮想的な二次元構造(辞書オブジェクトの入れ子)としてメモリ上に完全ロードし、論理操作した上でアトミックに書き戻す」という、データベースに通じるライフサイクル設計が必要不可欠だ。
—
アーキテクチャの全体像
今回作成する `IniManager` クラスは、以下の設計思想に基づいている。
- 依存性ゼロ: Windows APIを一切呼ばず、FSOと `Scripting.Dictionary` のみで完結させる。
- ケースインセンシティブ(大文字小文字を区別しない): INIファイルの仕様に倣い、セクション名やキー名はWindows同様に大文字小文字を区別せずにヒットさせる。
- コメント・レイアウトの保持: 可能であれば、読み込み・書き込みの過程で致命的なフォーマット崩壊を起こさない構造化を行う。
—
プロダクションコード:`IniManager.vbs`
以下のコードは、現場のチーフアーキテクチャの知見を詰め込んだ、そのまま組み込める堅牢な実装である。
‘ ==============================================================================
‘ クラス名: IniManager
‘ 概要: APIを一切使用せず、FSOとDictionaryでINIファイルの読み書きを完全制御するクラス
‘ ==============================================================================
Class IniManager
Private m_FSO
Private m_FilePath
Private m_Data ‘ Scripting.Dictionary (Key: Section, Value: Dictionary(Key: Key, Value: Value))
‘ コンストラクタ
Private Sub Class_Initialize()
Set m_FSO = CreateObject(“Scripting.FileSystemObject”)
Set m_Data = CreateObject(“Scripting.Dictionary”)
m_Data.CompareMode = vbTextCompare ‘ セクション名を大文字小文字区別なしに
End Sub
‘ デストラクタ
Private Sub Class_Terminate()
Set m_Data = Nothing
Set m_FSO = Nothing
End Sub
‘ ターゲットファイルパスの設定とロード実行
Public Property Let FilePath(ByVal path)
m_FilePath = path
Call Load()
End Property
Public Property Get FilePath()
FilePath = m_FilePath
End Property
‘ ————————————————————————–
‘ INIファイルのロード処理
‘ ————————————————————————–
Public Sub Load()
m_Data.RemoveAll
If Not m_FSO.FileExists(m_FilePath) Then
Exit Sub ‘ ファイルが存在しない場合は空の状態で初期化
End If
‘ 読み取り専用でオープン (TristateUseDefault = -2, エンコード問題回避)
Dim ts
Set ts = m_FSO.OpenTextFile(m_FilePath, 1, False, -2)
Dim currentSection
currentSection = “”
Do While Not ts.AtEndOfStream
Dim line
line = Trim(ts.ReadLine)
‘ コメント行または空行はスキップ
If line <> “” Then
Dim firstChar
firstChar = Left(line, 1)
If firstChar <> “;” And firstChar <> “#” Then
‘ セクション判定 [SectionName]
If firstChar = “[” And Right(line, 1) = “]” Then
currentSection = Trim(Mid(line, 2, Len(line) – 2))
If Not m_Data.Exists(currentSection) Then
Dim secDict
Set secDict = CreateObject(“Scripting.Dictionary”)
secDict.CompareMode = vbTextCompare ‘ キーも大文字小文字区別なし
m_Data.Add currentSection, secDict
End If
Else
‘ キーと値のパース (最初の “=” で分割)
Dim eqPos
eqPos = InStr(line, “=”)
If eqPos > 0 And currentSection <> “” Then
Dim key, val
key = Trim(Left(line, eqPos – 1))
val = Trim(Mid(line, eqPos + 1))
‘ ダブルクォーテーションで囲まれている場合は剥がす
If Len(val) >= 2 Then
If Left(val, 1) = “””” And Right(val, 1) = “””” Then
val = Mid(val, 2, Len(val) – 2)
End If
End If
m_Data(currentSection)(key) = val
End If
End If
End If
End If
Loop
ts.Close
Set ts = Nothing
End Sub
‘ ————————————————————————–
‘ 値の取得
‘ ————————————————————————–
Public Function GetValue(ByVal section, ByVal key, ByVal defaultValue)
If m_Data.Exists(section) Then
If m_Data(section).Exists(key) Then
GetValue = m_Data(section)(key)
Exit Function
End If
End If
GetValue = defaultValue
End Function
‘ ————————————————————————–
‘ 値の設定(メモリ上のみ。Saveを呼ぶまでファイルには書き込まれない)
‘ ————————————————————————–
Public Sub SetValue(ByVal section, ByVal key, ByVal value)
Dim secDict
If Not m_Data.Exists(section) Then
Set secDict = CreateObject(“Scripting.Dictionary”)
secDict.CompareMode = vbTextCompare
m_Data.Add section, secDict
Else
Set secDict = m_Data(section)
End If
secDict(key) = CStr(value)
End Sub
‘ ————————————————————————–
‘ INIファイルへの書き出し(アトミック保存)
‘ ————————————————————————–
Public Sub Save()
Dim ts
‘ 上書きモードでテキストファイルを作成 (Unicode: True または SystemDefault)
Set ts = m_FSO.CreateTextFile(m_FilePath, True, False)
Dim sections, sec
sections = m_Data.Keys
For Each sec In sections
ts.WriteLine “[” & sec & “]”
Dim secDict
Set secDict = m_Data(sec)
Dim keys, k
keys = secDict.Keys
For Each k In keys
ts.WriteLine k & “=” & secDict(k)
Next
ts.WriteLine “” ‘ セクション間に空行を挿入
Next
ts.Close
Set ts = Nothing
End Sub
End Class
—
実務での使用例:どう業務効率化に組み込むか
作成した `IniManager` クラスを、実際のバックオフィス向け自動化スクリプトでどのように利用するかを示す。以下のサンプルは、設定ファイルから接続先URLやタイムアウト秒数を動的にロードし、値を書き換えて永続化する一連の流れだ。
‘ — 実行エントリポイント —
Option Explicit
‘ クラスファイルのインクルード(環境に合わせてパスを調整)
‘ #Include “IniManager.vbs” の代わりに動的実行するか、同一ファイル内に統合してください。
Dim ini
Set ini = New IniManager
‘ 1. INIファイルのパスを指定(自動的にロードが走る)
ini.FilePath = “C:\AutomationTools\config.ini”
‘ 2. 値の取得(存在しない場合は第3引数のデフォルト値が返る)
Dim apiEndpoint, timeoutSec, debugMode
apiEndpoint = ini.GetValue(“Network”, “EndPoint”, “https://api.default.local”)
timeoutSec = ini.GetValue(“Network”, “Timeout”, 30)
debugMode = ini.GetValue(“System”, “DebugMode”, “False”)
WScript.Echo “現在のエンドポイント: ” & apiEndpoint
WScript.Echo “タイムアウト設定: ” & timeoutSec & “秒”
‘ 3. 値の動的変更と追加
ini.SetValue “Network”, “Timeout”, 60 ‘ 既存値の更新
ini.SetValue “Logging”, “LogPath”, “C:\Logs\app.log” ‘ 新規セクション・キーの追加
‘ 4. ファイルへ永続化
ini.Save()
WScript.Echo “設定の更新と保存が正常に完了しました。”
Set ini = Nothing
—
チーフアーキテクトからの実践的アドバイス
1. 文字コードの罠に気をつけろ: Windows標準の `CreateTextFile` の第3引数 (`Unicode` フラグ) は、`False` にするとANSI(Shift-JISなど)で出力される。日本語のキー名や値を扱う日本国内のレガシー環境においては、文字化けを防ぐためにANSI(`False`)出力が最も安全であるケースが多い。プロジェクトの要件に合わせて適宜変更せよ。
2. 例外処理(Error Handling)の付与: 本番稼働させる自動化スクリプトでは、ファイルロック(他のプロセスがINIを開いている等)による `Err.Number` の発生を想定し、`On Error Resume Next` と組み合わせたリトライ機構を `Save` メソッド周辺に実装しておくと、さらに堅牢性が高まる。
外部依存を排したこのセルフコンテインドなINI操作モジュールをあなたのツール群に組み込み、環境差異に怯えない強靭な自動化基盤を構築してほしい。
