【実務・中級編】【実務中級者向け】FSOとWScript.Shell環境変数の組み合わせによる「%APPDATA%」配下のログ自動一括収集 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

【設計の本質】VBScriptにおける「動的パス解決」と「堅牢なログ収集」の極意

現場で動く「ツール」を作る際、多くの開発者が陥る致命的な罠がある。それは、「パスのハードコーディング」だ。

`C:\Users\Tanaka\AppData\Roaming\…`
このようなコードを一行でも書いた瞬間、そのスクリプトの寿命は決まった。ユーザー名が変われば動かず、環境が変われば死ぬ。これは自動化エンジニアとして、最も忌避すべき「脆弱な設計」である。

本稿では、実務中級者が一皮むけるために不可欠な、`WScript.Shell` による環境変数の動的解決と、`FileSystemObject (FSO)` による堅牢なファイル操作の組み合わせについて、プロフェッショナルな設計思想と共に解説する。

1. なぜ「環境変数」を介在させるのか

Windows環境において、ユーザーのプロファイルパスは動的である。特にアプリケーションの構成ファイルやログが格納される `%APPDATA%` は、OSの設計上、ユーザーごとに異なる場所を指し示す。

我々が目指すべきは、「実行環境に依存しない、抽象化されたロジック」だ。

ここで登場するのが `WScript.Shell` オブジェクトの `ExpandEnvironmentStrings` メソッドである。これを用いることで、OSが持つ環境変数(`%APPDATA%`, `%TEMP%`, `%USERPROFILE%` 等)を、実行時のリアルタイムな絶対パスへと展開できる。

この「展開(Expansion)」のプロセスを挟むことこそが、ポータビリティ(移植性)を確保するための第一歩となる。

2. 堅牢な設計のための「3つの鉄則」

単にファイルをコピーするだけのコードは、初心者レベルだ。実務で「止まらない、かつ原因特定が容易な」ツールを作るには、以下の設計思想が必要となる。

① 存在確認の徹底(Pre-flight Check)

「ファイルがある前提」で動くコードは、エラーで即死する。操作対象のフォルダ、および出力先のフォルダが「現時点で存在するか」を、FSOを用いて必ず事前に検証せよ。

② エラーハンドリングの局所化

VBScriptにおける `On Error Resume Next` の乱用は、バグを隠蔽する毒薬だ。エラーハンドリングは「エラーが予想される最小単位のブロック」に限定し、発生した場合は `Err.Number` を適切に評価してログに残す設計にせよ。

③ リソースのライフサイクル管理

`FileSystemObject` や `WScript.Shell` などのCOMオブジェクトは、生成したら確実に解放(`Set obj = Nothing`)する。メモリリークやファイルロックの残留を防ぐのは、エンジニアとしての最低限のマナーである。

3. プロダクション・コード例:ログ一括収集エージェント

以下に、`%APPDATA%` 配下の特定アプリケーションのログを、動的に解決して一括集約する、実戦投入レベルのコードを示す。

' ==============================================================================
' Script Name: LogCollector_Pro.vbs
' Description: Dynamically resolves %APPDATA% and collects logs from multiple apps.
' Author: Chief Architect
' ==============================================================================
Option Explicit

' --- 設定エリア:収集対象のサブディレクトリを定義 ---
' 形式: Array("AppName_SubDir", "AnotherApp_SubDir")
Dim TARGET_SUB_DIRS
TARGET_SUB_DIRS = Array("MyApp1\Logs", "MyApp2\Debug", "CustomTool\LogData")

' --- メイン処理 ---
Main

Sub Main()
    Dim shell, fso, appDataPath, destinationPath
    Dim targetDir, logFolder, targetApp
    Dim i, statusCount

    ' オブジェクトの初期化
    Set shell = CreateObject("WScript.Shell")
    Set fso = CreateObject("Scripting.FileSystemObject")

    ' 1. 環境変数の動的展開 (%APPDATA% の解決)
    appDataPath = shell.ExpandEnvironmentStrings("%APPDATA%")
    
    ' 2. 集約先フォルダの設定 (例: %TEMP%\CollectedLogs)
    destinationPath = shell.ExpandEnvironmentStrings("%TEMP%\CollectedLogs")

    WScript.Echo "--- Log Collection Started ---"
    WScript.Echo "Base APPDATA: " & appDataPath
    WScript.Echo "Destination: " & destinationPath

    ' 3. 集約先フォルダの準備(なければ作成)
    If Not fso.FolderExists(destinationPath) Then
        On Error Resume Next
        fso.CreateFolder(destinationPath)
        If Err.Number <> 0 Then
            WScript.Echo "CRITICAL ERROR: Could not create destination folder. " & Err.Description
            WScript.Quit 1
        End If
        On Error GoTo 0
    End If

    statusCount = 0

    ' 4. 各ターゲットの巡回
    For i = 0 To UBound(TARGET_SUB_DIRS)
        targetApp = TARGET_SUB_DIRS(i)
        logFolder = appDataPath & "\" & targetApp

        WScript.Echo "Processing: " & targetApp

        ' 存在確認(Pre-flight Check)
        If fso.FolderExists(logFolder) Then
            If CollectFiles(fso, logFolder, destinationPath) Then
                statusCount = statusCount + 1
            Else
                WScript.Echo "  [!] Warning: Failed to collect files from " & targetApp
            End If
        Else
            WScript.Echo "  [!] Skip: Folder not found: " & logFolder
        End If
    Next

    WScript.Echo "------------------------------"
    WScript.Echo "Collection Complete. Success: " & statusCount & "/" & (UBound(TARGET_SUB_DIRS) + 1)
    WScript.Echo "--- End of Process ---"

    ' オブジェクトの解放
    Set shell = Nothing
    Set fso = Nothing
End Sub

' ------------------------------------------------------------------------------
' Function: CollectFiles
' Purpose: Copy all files from source to destination with error handling
' ------------------------------------------------------------------------------
Function CollectFiles(fso, sourcePath, destPath)
    Dim folder, file, fileName
    Dim success
    success = True

    On Error Resume Next ' 局所的なエラーハンドリング
    Set folder = fso.GetFolder(sourcePath)
    
    For Each file In folder.Files
        ' ファイル名の重複を避けるため、タイムスタンプを付与する等の設計も検討すべきだが、
        ' ここではシンプルにコピーを試行
        fileName = file.Name
        
        ' コピー実行
        fso.CopyFile file.Path, destPath & "\" & fileName, True
        
        If Err.Number <> 0 Then
            WScript.Echo "    [Error] Failed to copy " & fileName & ": " & Err.Description
            success = False
            Err.Clear
        End If
    Next
    On Error GoTo 0 ' エラーハンドリングを解除

    CollectFiles = success
End Function

4. 実務における高度なアドバイス

このコードをベースに、さらに現場の要件に応えるための「次の一手」を伝授する。

ファイル名の衝突回避

上記のサンプルでは、同じファイル名のログが異なるアプリから出力されている場合、上書きのリスクがある。実務では、`fso.CopyFile` を行う前に、ファイル名に `yyyyMMdd_HHmmss_` のようなタイムスタンプを付与するか、あるいは `destinationPath & “\” & targetApp & “_” & fileName` のように、アプリ名をプレフィックスとして結合する設計にすべきだ。

権限(Permissions)の壁

`%APPDATA%` 自体はユーザー権限でアクセス可能だが、一部のシステムレベルのアプリケーションは、ログフォルダに対して特殊なACL(アクセス制御リスト)を設定している場合がある。`fso.FolderExists` が `True` を返しても、`CopyFile` で拒否されるケースは多々ある。この際、スクリプトを「管理者として実行」させる運用ルールを策定するか、エラーログを詳細に出力してユーザーに報告するフローを組み込んでおくこと。

ログのローテーション

収集したログを溜め込みすぎると、ストレージを圧迫する。収集スクリプトの冒頭に、「集約先フォルダ内の〇日以上前のファイルを削除する」という Cleanupロジック を追加するのが、真に「運用を考慮したエンジニア」の仕事である。

結論

VBScriptは古い技術だと言われることもある。しかし、Windows環境において、追加のランタイムを必要とせず、OSの標準機能だけでこれほどまでに強力な自動化ができる言語は他にない。

`WScript.Shell` で環境を理解し、`FSO` で構造を支配する。この二つの武器を正しく使いこなせば、君の作るツールは「単なるスクリプト」から「信頼されるインフラ」へと昇華するだろう。

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