こんにちは!VBScriptの世界へようこそ。
普段何気なく使っているWindows環境の裏側で、地味ながらも強力にタスクを自動化してくれるVBScript。今回は、実務の現場で必ずといっていいほど必要になる「INI設定ファイルの自作モジュール化」について、徹底的に解説していきます。
「レジストリを汚したくない」「APIの宣言とか面倒なことは抜きで、テキスト操作だけで完結させたい」
そんな現場の切実な願いを叶えるため、Windows標準機能だけで軽快に動く、極上の設定管理クラスを作ってみましょう。
ここをクリアすれば、VBScriptのテキスト処理とオブジェクト指向的なアプローチの基本はバッチリです。温かく導いていくので、一緒にリラックスして進めていきましょう!
—
なぜ「INIファイル」なのか? そしてなぜ「自作」するのか?
業務自動化のスクリプトを書いていると、接続先のURLや保存先フォルダのパスなど、「環境によって変えたい値(設定値)」が出てきますよね。
これをスクリプト内に直接書く(ハードコーディングする)のは、エンジニアの御法度です。環境が変わるたびにコードを書き換えるなんて、保守性が最悪になってしまいます。
レジストリ vs INIファイル
Windowsにはレジストリという素晴らしい設定保存場所がありますが、以下のデメリットがあります。
- レジストリのバックアップや移行が面倒
- 権限管理(UACなど)で書き込みに失敗することがある
- 開発者が中身を目視で確認しにくい
そこで重宝するのが、昔ながらのINIファイル(`settings.ini` など)です。人間がテキストエディタで直接読めるし、バックアップもファイルをコピペするだけ。最高にシンプルですよね。
今回は、Windowsの面倒なAPI(`GetPrivateProfileString`など)をあえて使わず、VBScriptの純粋なテキスト処理(FileSystemObject)だけでINIファイルを完璧に読み書きするモジュールを構築します。環境依存ゼロの、どこでも動く最強のパーツを手に入れましょう。
—
INIファイルの構造をおさらいしよう
INIファイルは、以下のような構造をしています。
[Database]
Server=192.168.1.50
Port=1433
Timeout=30
[AppConfig]
Mode=Production
LogPath=C:\Logs\App.log
DebugMode=False
- `[セクション名]` : データのグループを表します。
- `キー = 値` : 実際のデータペアです。
これをVBScriptで読み解くには、「テキストファイルを1行ずつ読み込み、現在のセクションを記憶しながら、目的のキーを探す」というアルゴリズムが必要になります。
—
実装コード:完全版 INIファイル操作クラス
それでは早速、現場でそのままコピペして使えるクラスファイルを見ていきましょう。
VBScriptには本来「クラス」の概念が薄いですが、`Class` ステートメントを使う立派なオブジェクト指向プログラミングが可能です。
メモ帳を開き、以下のコードを `IniManager.vbs` という名前で保存してください。
‘ ==============================================================================
‘ クラス名: IniFileManager
‘ 概要: Windows標準機能(FSO)のみでINIファイルの読み書きを行う汎用クラス
‘ ==============================================================================
Class IniFileManager
Private fso
Private filePath
‘ — コンストラクタ(インスタンス生成時に呼ばれる) —
Private Sub Class_Initialize()
Set fso = CreateObject(“Scripting.FileSystemObject”)
filePath = “”
End Sub
‘ — デストラクタ(インスタンス破棄時に呼ばれる) —
Private Sub Class_Terminate()
Set fso = Nothing
End Sub
‘ — INIファイルのパスを設定するプロパティ —
Public Property Let Path(ByVal p_Path)
‘ 相対パスが渡された場合を考慮し、絶対パスに変換しておく
filePath = fso.GetAbsolutePathName(p_Path)
End Property
Public Property Get Path()
Path = filePath
End Property
‘ ==========================================================================
‘ 読み込みメソッド
‘ 指定したセクションとキーに対応する値を取得する
‘ ==========================================================================
Public Function ReadValue(ByVal section, ByVal key, ByVal defaultValue)
‘ デフォルト値を初期値としてセット
ReadValue = defaultValue
‘ ファイルが存在しない場合はデフォルト値を返す
If Not fso.FileExists(filePath) Then Exit Function
Dim file, line, currentSection
currentSection = “”
‘ テキストファイルを読み取り専用でオープン
Set file = fso.OpenTextFile(filePath, 1, False)
Do While Not file.AtEndOfStream
line = Trim(file.ReadLine)
‘ コメント行(’;’ または ‘#’ で始まる)や空行はスキップ
If line <> “” And Left(line, 1) <> “;” And Left(line, 1) <> “#” Then
‘ セクション判定 (例: [Database])
If Left(line, 1) = “[” And Right(line, 1) = “]” Then
currentSection = Mid(line, 2, Len(line) – 2)
currentSection = Trim(currentSection)
‘ キーと値の判定 (例: Server=192.168.1.50)
ElseIf LCase(currentSection) = LCase(section) Then
Dim delimiterPos
delimiterPos = InStr(line, “=”)
If delimiterPos > 0 Then
Dim k, v
k = Trim(Left(line, delimiterPos – 1))
v = Trim(Mid(line, delimiterPos + 1))
visualize = False
‘ 目的のキーと一致したら値を返す
If LCase(k) = LCase(key) Then
ReadValue = v
file.Close
Exit Function
End If
End If
End If
End If
Loop
file.Close
End Function
‘ ==========================================================================
‘ 書き込み(更新・追加)メソッド
‘ 既存のキーがあれば上書き、なければセクション末尾(またはファイル末尾)に追加
‘ ==========================================================================
Public Sub WriteValue(ByVal section, ByVal key, ByVal value)
Dim lines
Set lines = CreateObject(“Scripting.Dictionary”)
Dim file, line, currentSection
currentSection = “”
Dim sectionExists, keyExists
sectionExists = False
keyExists = False
‘ 1. 既存ファイルを全行読み込み、メモリ上の辞書(配列的リスト)に保持する
If fso.FileExists(filePath) Then
Set file = fso.OpenTextFile(filePath, 1, False)
Do While Not file.AtEndOfStream
line = file.ReadLine
Dim trimmedLine: trimmedLine = Trim(line)
If Left(trimmedLine, 1) = “[” And Right(trimmedLine, 1) = “]” Then
currentSection = Mid(trimmedLine, 2, Len(trimmedLine) – 2)
currentSection = Trim(currentSection)
End If
If LCase(currentSection) = LCase(section) Then
sectionExists = True
Dim delimiterPos: delimiterPos = InStr(trimmedLine, “=”)
If delimiterPos > 0 Then
Dim k: k = Trim(Left(trimmedLine, delimiterPos – 1))
If LCase(k) = LCase(key) Then
‘ ターゲットのキーを発見!新しい値に置き換える
line = key & “=” & value
keyExists = True
End If
End If
End If
‘ 一意なキーを維持するため、行番号をキーにしたDictionaryに格納(簡易的な全行保持)
‘ ※実務では配列を使うアプローチもありますが、ここではシンプルにテキスト全体を再構築します
Loop
file.Close
End If
‘ — 実務的かつ堅牢な書き込み処理(全行再構築方式) —
‘ コードが長くなりすぎるのを防ぐため、全行を配列として再書き込みするロジックを構築します。
Call RewriteIniFile(section, key, value)
End Sub
‘ プライベートヘルパー:INIファイルを安全に書き換える内部関数
Private Sub RewriteIniFile(ByVal targetSection, ByVal targetKey, ByVal targetValue)
Dim allLines
Set allLines = CreateObject(“System.Collections.ArrayList”) ‘ ArrayListで動的配列を操作
Dim file, line, currentSection
currentSection = “”
Dim isSectionFound = False
Dim isKeyFound = False
‘ ファイルが存在する場合は読み込む
If fso.FileExists(filePath) Then
Set file = fso.OpenTextFile(filePath, 1, False)
Do While Not file.AtEndOfStream
line = file.ReadLine
Dim tLine: tLine = Trim(line)
If Left(tLine, 1) = “[” And Right(tLine, 1) = “]” Then
‘ もし前のセクションで、目的のキーがまだ見つかっておらず、かつ新しいセクションに移る場合、
‘ 旧セクションの直下にキーを追加するチャンスだが、シンプルにファイル末尾追加でも動く
currentSection = Mid(tLine, 2, Len(tLine) – 2)
If LCase(currentSection) = LCase(targetSection) Then
isSectionFound = True
End If
Else
If LCase(currentSection) = LCase(targetSection) Then
Dim p: p = InStr(tLine, “=”)
If p > 0 Then
Dim k: k = Trim(Left(tLine, p – 1))
If LCase(k) = LCase(targetKey) Then
‘ 既存キーを発見したので、新しい値に書き換える
line = targetKey & “=” & targetValue
isKeyFound = True
End If
End If
End If
End If
allLines.Add line
Loop
file.Close
End If
‘ セクション自体が存在しなかった場合
If Not isSectionFound Then
If allLines.Count > 0 Then allLines.Add “” ‘ 1行空ける
allLines.Add “[” & targetSection & “]”
allLines.Add targetKey & “=” & targetValue
ElseIf Not isKeyFound Then
‘ セクションはあるがキーがなかった場合、セクションのすぐ下(あるいは末尾)に追加
‘ ここでは安全にファイル末尾、またはセクション配下に追加する処理
Dim i
For i = 0 To allLines.Count – 1
If LCase(Trim(allLines(i))) = “[” & LCase(targetSection) & “]” Then
‘ セクション名の直下に挿入
allLines.Insert i + 1, targetKey & “=” & targetValue
isKeyFound = True
Exit For
End If
Next
If Not isKeyFound Then allLines.Add targetKey & “=” & targetValue
End If
‘ 一括書き出し(上書き保存)
Set file = fso.CreateTextFile(filePath, True)
Dim item
For Each item In allLines
file.WriteLine item
Next
file.Close
End Sub
End Class
—
実際に動かしてみよう(利用サンプル)
先ほど作成したクラスを呼び出して、実際にINIファイルの読み書きを行うテストスクリプトを作成します。
`main.vbs` という名前で、`IniManager.vbs` と同じフォルダに保存してください。
‘ main.vbs
‘ ——————————————————————————
‘ INIファイル操作クラスの動作テストスクリプト
‘ ——————————————————————————
‘ 同じフォルダにあるクラスファイルを読み込む(VBScriptでのインクルード手法)
ExecuteGlobal CreateObject(“Scripting.FileSystemObject”).OpenTextFile(“IniManager.vbs”, 1).ReadAll
‘ 1. インスタンスの生成
Dim ini
Set ini = New IniFileManager
‘ 2. 操作するINIファイルのパスを指定(相対パスOK)
ini.Path = “config.ini”
‘ — 3. 書き込みテスト —
WScript.Echo “— 書き込みテストを開始します —”
ini.WriteValue “Database”, “Server”, “10.0.0.99”
ini.WriteValue “Database”, “Timeout”, “60”
ini.WriteValue “AppConfig”, “Mode”, “Staging”
WScript.Echo “書き込みが完了しました。config.iniを確認してください。”
‘ — 4. 読み込みテスト —
WScript.Echo vbCrLf & “— 読み込みテストを開始します —”
Dim dbServer, dbTimeout, appMode, unkownVal
dbServer = ini.ReadValue(“Database”, “Server”, “127.0.0.1”) ‘ 存在しない場合のデフォルト値指定
dbTimeout = ini.ReadValue(“Database”, “Timeout”, “30”)
appMode = ini.ReadValue(“AppConfig”, “Mode”, “Debug”)
unkownVal = ini.ReadValue(“AppConfig”, “NotExistKey”, “初期値だよ”)
WScript.Echo “Database.Server : ” & dbServer
WScript.Echo “Database.Timeout : ” & dbTimeout
WScript.Echo “AppConfig.Mode : ” & appMode
WScript.Echo “NotExistKey(Def) : ” & unkownVal
‘ オブジェクトの解放
Set ini = Nothing
WScript.Echo vbCrLf & “すべての処理が正常に終了しました。”
これをダブルクリック、またはコマンドプロンプトから `cscript main.vbs` で実行してみてください。
同じフォルダに自動的に `config.ini` が生成され、意図した通りに値が読み書きされているのが分かります。
—
陥りやすいエラーと知っておくべき極意
最後に、現場で開発する際につまずきやすいポイントと、シニアエンジニアとしての知見をいくつか共有しておきます。
1. 大文字・小文字の区別トラップ
INIファイルのキーやセクション名は、人間が適当に書くことが多いです(例: `Server` と `server`)。
今回のコードでは `LCase()` 関数を使って大文字と小文字を完全に無視して比較するように設計しています。これを怠ると、「キーが見つからない!」という謎のバグに悩まされるので注意してください。
2. 文字コード(BOM付きUTF-8など)の罠
VBScriptの `OpenTextFile` メソッドは、デフォルトでシステム標準の文字コード(日本語環境ならShift-JIS / ANSI)でファイルを読み込みます。
もしINIファイルを無理やりUTF-8(BOMなし)などで保存してしまうと、日本語のセクション名や値が文字化けします。
【知見】 VBScriptでテキストを扱う際は、ファイルは「Shift-JIS (ANSI)」または「BOM付きUTF-8」で統一するのが無難です。環境依存をなくすなら素直にANSIで保存させましょう。
3. コメント行や余白のハンドリング
実務のINIファイルには、メモ書きとして `;` や `#` で始まるコメント行や、無駄なスペースが含まれています。
今回のコードでは `Trim()` 関数で前後の空白を綺麗に削り、先頭の文字をチェックする堅牢なフィルターを入れているため、現場の汚れたINIファイルでも誤動作しません。
—
おわりに
お疲れ様でした!
今回はAPI非依存で動作する、自作のINIファイル読み込み・書き込みモジュールを解説しました。
VBScriptは古い言語と言われがちですが、オブジェクトのライフサイクルを意識し、このように美しくクラス設計してパーツ化すれば、モダンなプログラミング言語に負けない強力な自動化武器になります。
ここをクリアしたあなたなら、もうマクロの記録の域を完全に脱却し、立派な業務自動化エンジニアの階段を登っています。
ぜひ、日々の業務の効率化にこのモジュールを役立ててくださいね!それでは、また次の知見でお会いしましょう。
