WMI/CIMと並列処理で実現する、マルチターゲット・リモートPC状態監視・イベントログ一括収集自動化スクリプト

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

WMI/CIMと並列処理で実現する、マルチターゲット・リモートPC状態監視・イベントログ一括収集自動化スクリプト

【導入:解決する課題】

複数リモートPCに対する手作業での状態確認やログ収集に伴う、多大な運用コストと監視漏れリスクを解消します。

【設計方針と処理フロー】

本スクリプトは、レガシーなWMI(Get-WmiObject)に代わり、WS-ManagementプロトコルをベースとするモダンなCIM(Common Information Model)コマンドレットを採用します。これにより、セキュリティ(暗号化通信)と信頼性を担保します。

また、多数のターゲットPCから効率的に情報を収集するため、PowerShell 7の ForEach-Object -Parallel を利用してマルチスレッドで非同期にCIMセッションを確立。各PCのシステム基本情報(OS・ディスク残量)と、直近の致命的(Critical/Error)なイベントログを一括取得し、構造化データとして出力します。

graph TD
    A["処理開始"] --> B["ターゲットPCリストの読み込み"]
    B --> C["CimSessionの並列確立"]
    C --> D{"接続検証"}
    D -- 接続成功 --> E["システム状態の取得 Win32_OperatingSystem / Win32_LogicalDisk"]
    D -- 接続失敗 --> F["エラーハンドリング: ログ出力とスキップ"]
    E --> G["重大なイベントログのフィルタ抽出 Win32_NTLogEvent"]
    G --> H["カスタムオブジェクトへのマージ"]
    H --> I["CSV/JSON出力 & セッション切断"]
    F --> J["処理終了"]
    I --> J

【実装:コアスクリプト】

以下は、本運用の要件を満たすために設計された堅牢なPowerShellスクリプトです。Active Directoryドメイン環境、または適切なWinRM信頼関係が構成された環境で実行可能です。

function Get-RemoteComputerStatus {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]$ComputerNames,

        [Parameter(Mandatory = $false)]
        [int]$MaxLogAgeHours = 24,

        [Parameter(Mandatory = $false)]
        [int]$ThrottleLimit = 5
    )

    process {

        # 並列処理の実行(PowerShell 7.x 以上を推奨)

        $ComputerNames | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel {
            $computer = $_
            $logAgeHours = $using:MaxLogAgeHours

            Write-Verbose "Processing computer: $computer"

            # CIMセッションオプションの作成(タイムアウトの設定など)

            $sessionOption = New-CimSessionOption -Protocol Dcom -ConnectTimeoutSec 10

            # デフォルトでWSManを試行し、失敗した場合はDCOMを試行する、または環境に合わせて最適化

            $session = $null

            try {

                # 接続確認を兼ねてCIMセッションを確立

                $session = New-CimSession -ComputerName $computer -OperationTimeoutSec 15 -ErrorAction Stop

                # 1. OS基本情報の取得

                $osInfo = Get-CimInstance -CimSession $session -ClassName Win32_OperatingSystem -ErrorAction Stop

                # 2. ディスク空き容量の取得(Cドライブを対象)

                $systemDrive = Get-CimInstance -CimSession $session -ClassName Win32_LogicalDisk -Filter "DeviceID='C:'" -ErrorAction Stop
                $freeSpaceGB = [math]::Round($systemDrive.FreeSpace / 1GB, 2)
                $sizeGB = [math]::Round($systemDrive.Size / 1GB, 2)
                $freePercent = [math]::Round(($systemDrive.FreeSpace / $systemDrive.Size) * 100, 1)

                # 3. 直近24時間の「Error」「Critical」イベントログの取得

                $filterDate = (Get-Date).AddHours(-$logAgeHours)

                # WQLクエリによる効率的なサーバーサイドフィルタリング

                $wqlQuery = "Select * From Win32_NTLogEvent Where (Type = 'Error' Or Type = 'Critical') And TimeGenerated >= '$($filterDate.ToString('yyyyMMddHHmmss.ffffff-000'))'"
                $errorLogs = Get-CimInstance -CimSession $session -Query $wqlQuery -ErrorAction SilentlyContinue

                # イベントログデータの集約

                $logSummaries = foreach ($log in $errorLogs) {
                    [PSCustomObject]@{
                        LogName   = $log.Logfile
                        Source    = $log.SourceName
                        EventID   = $log.EventCode
                        Time      = $log.TimeGenerated
                        Message   = $log.Message -replace "`r`n|`r|`n", " " # 改行の平坦化
                    }
                }

                # 総合的な結果オブジェクトの出力

                [PSCustomObject]@{
                    ComputerName       = $computer
                    Status             = "Online"
                    OSName             = $osInfo.Caption
                    LastBootTime       = $osInfo.LastBootUpTime
                    FreeSpaceC_GB      = $freeSpaceGB
                    FreeSpaceC_Percent = $freePercent
                    RecentErrorsCount  = ($logSummaries | Measure-Object).Count
                    ErrorLogs          = $logSummaries
                    Timestamp          = (Get-Date)
                }

            } catch {

                # エラーログハンドリング

                [PSCustomObject]@{
                    ComputerName       = $computer
                    Status             = "Offline / Error"
                    OSName             = $null
                    LastBootTime       = $null
                    FreeSpaceC_GB      = $null
                    FreeSpaceC_Percent = $null
                    RecentErrorsCount  = $null
                    ErrorLogs          = $null
                    Timestamp          = (Get-Date)
                    ErrorMessage       = $_.Exception.Message
                }
            } finally {

                # セッションのクリーンアップ

                if ($null -ne $session) {
                    Remove-CimSession $session
                }
            }
        }
    }
}

# --- 実行およびエクスポート例 ---


# $targets = @("Server01", "Server02", "ClientPC01")


# $results = Get-RemoteComputerStatus -ComputerNames $targets -MaxLogAgeHours 12 -Verbose


# $results | ConvertTo-Json -Depth 4 | Out-File -FilePath ".\SystemReport.json" -Encoding utf8

【検証とパフォーマンス評価】

パフォーマンス計測の実施

従来の Get-WmiObject(DCOM接続・直列実行)と、本スクリプトで採用している Get-CimInstance(WSMan接続・ForEach-Object -Parallel)のパフォーマンスを、10台のリモートPCを対象に Measure-Command で比較評価します。

# CIM + 並列処理(PowerShell 7)

$cimTime = Measure-Command {
    $results = Get-RemoteComputerStatus -ComputerNames $targetList -ThrottleLimit 5
}

# レガシーWMI + 直列処理(PowerShell 5.1相当の疑似比較)

$wmiTime = Measure-Command {
    foreach ($computer in $targetList) {
        try {
            $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer -ErrorAction Stop
            $disk = Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computer -Filter "DeviceID='C:'" -ErrorAction Stop
            $logs = Get-WmiObject -Class Win32_NTLogEvent -ComputerName $computer -Filter "Type='Error'" -ErrorAction Stop
        } catch {}
    }
}

パフォーマンス比較期待値

  • 直列処理(レガシーWMI): 1台あたり約5〜15秒。オフライン端末が存在する場合、コネクションタイムアウト(デフォルト30秒以上)により全体処理が著しく遅延(10台中3台オフラインの場合、2分以上を要する)。

  • 並列処理(CIMセッション): ThrottleLimit 5 設定により、複数台へ同時アプローチ。かつCIMのタイムアウト値を明示的(10〜15秒)に絞ることで、オフライン端末があっても全体で15〜25秒以内に処理が完了。約500%以上のパフォーマンス改善が見込めます。

【運用上の落とし穴と対策】

1. PowerShell 5.1 と 7.x の互換性

  • 落とし穴: Windows Serverの標準環境であるPowerShell 5.1(Desktop Edition)では、ForEach-Object -Parallel パラメーターが利用できません。

  • 対策: PS 5.1環境で並列実行を行う場合は、標準コマンドレットの代わりに Start-JobRunspaces などのスレッドプール制御機構を利用するか、管理端末側にのみPowerShell 7.x(Core)を導入し、そこからリモート監視を実行します。

2. CIM/WMIのダブルホップ(二重ホップ)問題

  • 落とし穴: 収集したデータをさらに別サーバー(ファイル共有など)に保存する際、接続に使用している資格情報(トークン)をリモートサーバーが再委託できず、アクセス拒否(Access Denied)が発生します。

  • 対策: New-CimSession 確立時に CredSSP(Credential Security Support Provider)認証を有効化するか、Kerberosの委任を構成してセキュリティコンテキストを正しく引き継ぎます。

3. WMIのクエリフィルタ(サーバーサイドとクライアントサイド)

  • 落とし穴: Get-CimInstance -ClassName Win32_NTLogEvent でフィルタ(WqlQuery)を指定せずに全件取得すると、リモートPCの全イベントログがメモリ上にロードされ、帯域を圧迫し接続がタイムアウトします。

  • 対策: 必ず Filter パラメーターや Query(WQL)を利用し、リモート(サーバー)側で必要なログ(例:直近24時間のErrorのみ)を絞り込んでからデータを転送させてください。

【まとめ】

  1. CIM(Common Information Model)への完全移行 非推奨の Get-WmiObject を排除し、業界標準のWSMan/CIMベースである Get-CimInstance を使用することで、将来的な互換性と高いセキュリティ性能を担保する。

  2. マルチスレッド(Parallel)の活用によるタイムアウト抑止 監視対象のスケールアウト(数十〜数百台)に対応するため、並列実行とセッションの個別タイムアウト管理を導入し、1台のハングアップが全体のボトルネックにならないよう設計する。

  3. 適切なフィルタリングと例外処理の実装 ネットワーク負荷を抑えるための「サーバーサイドフィルタリング」の徹底と、オフラインや認証エラーを検知して適切にスキップし、処理を継続させる try-catch-finally の運用を徹底する。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

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