CIMセッションと並列処理による複数リモートPCのシステム状態監視・ログ一括収集

Tech

Windows/Linuxインフラ管理者、PowerShell自動化を担当するシステムエンジニア PowerShell 7.0以降(推奨) / Windows Server 2016/2019/2022 / Windows 10/11 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

CIMセッションと並列処理による複数リモートPCのシステム状態監視・ログ一括収集

【導入:解決する課題】

複数台のWindows機器へ個別接続し、リソース状況やイベントログを手動確認する膨大な運用負荷をCIM並列処理で自動化・軽減します。

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

本スクリプトでは、非推奨となった Get-WmiObject(DCOM依存)の代わりに、WinRMベースで高速かつセキュアな Get-CimInstance を採用します。PowerShell 7の ForEach-Object -Parallel を組み合わせて複数ノードへの同時問い合わせを行い、タイムアウトや接続エラーが発生した場合でも処理全体が停止しないように例外処理を設計します。

graph TD
    A["開始: 対象ホストリスト読み込み"] --> B["CIMセッションオプション設定"]
    B --> C["ForEach-Object -Parallel による並列処理開始"]
    C --> D{"CIM接続テスト"}
    D -- 成功 --> E["OS/CPU/メモリ/ディスク情報の取得"]
    D -- 失敗 --> H["エラーログ出力 & スキップ"]
    E --> F["直近のシステムイベントログ取得"]
    F --> G["カスタムオブジェクト構造化 & 配列格納"]
    G --> I["結果をPSCustomObjectとして集約"]
    H --> I
    I --> J["CSV/JSON等への出力処理"]
    J --> K["終了"]

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

以下は、複数のリモートホストからシステム基本リソース情報(CPU、メモリ、ディスク)と直近の特定エラーログを並列に一括取得する関数です。サードパーティ製モジュールは使用せず、標準の CimCmdlets と .NET クラス構造のみを利用しています。

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

        [Parameter()]
        [int]$HoursHistory = 24,

        [Parameter()]
        [int]$ThrottleLimit = 10,

        [Parameter()]
        [pscredential]$Credential
    )

    begin {
        Write-Verbose "監視ジョブを開始します。対象台数: $($ComputerName.Count)"
        $sessionOption = New-CimSessionOption -Protocol Wsman -Culture en-US
    }

    process {
        $results = $ComputerName | ForEach-Object -Parallel {
            $computer = $_
            $hours = $using:HoursHistory
            $cred = $using:Credential
            $opt = $using:sessionOption

            $status = [PSCustomObject]@{
                ComputerName  = $computer
                Timestamp     = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                Status        = "Unknown"
                OSName        = $null
                CPUUsagePercent = $null
                FreeMemoryGB  = $null
                C_DriveFreeGB = $null
                ErrorLogCount = $null
                ErrorMessage  = $null
            }

            try {

                # CIMセッションの作成パラメータ準備

                $sessionParams = @{
                    ComputerName  = $computer
                    SessionOption = $opt
                    ErrorAction   = 'Stop'
                }
                if ($cred) { $sessionParams.Credential = $cred }

                $session = New-CimSession @sessionParams

                # 1. OSおよびメモリ情報取得

                $os = Get-CimInstance -CimSession $session -ClassName Win32_OperatingSystem -ErrorAction Stop
                $status.OSName = $os.Caption
                $status.FreeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)

                # 2. CPU使用率取得

                $cpu = Get-CimInstance -CimSession $session -ClassName Win32_Processor -ErrorAction Stop | 
                       Measure-Object -Property LoadPercentage -Average
                $status.CPUUsagePercent = [math]::Round($cpu.Average, 1)

                # 3. Cドライブ空き容量取得

                $disk = Get-CimInstance -CimSession $session -ClassName Win32_LogicalDisk -Filter "DeviceID='C:'" -ErrorAction Stop
                $status.C_DriveFreeGB = [math]::Round($disk.FreeSpace / 1GB, 2)

                # 4. システムイベントログ(過去X時間のエラー)取得

                $startTime = (Get-Date).AddHours(-$hours)
                $logFilter = "Logfile='System' AND Type='Error' AND TimeGenerated >= '$([System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($startTime))'"
                $logs = Get-CimInstance -CimSession $session -ClassName Win32_NTLogEvent -Filter $logFilter -ErrorAction SilentlyContinue
                $status.ErrorLogCount = ($logs | Measure-Object).Count

                $status.Status = "Success"
            }
            catch {
                $status.Status = "Failed"
                $status.ErrorMessage = $_.Exception.Message
            }
            finally {
                if ($null -ne $session) {
                    Remove-CimSession -CimSession $session -ErrorAction SilentlyContinue
                }
            }

            return $status
        } -ThrottleLimit $ThrottleLimit

        return $results
    }

    end {
        Write-Verbose "全ノードからのデータ収集が完了しました。"
    }
}

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

上記スクリプトの処理性能を検証するため、Measure-Command を用いて同期処理(従来方式)と並列処理(ForEach-Object -Parallel)の実行速度を比較測定します。

計測スクリプト例

# テスト対象ノードリスト

$targetNodes = @("Server01", "Server02", "Server03", "Server04", "Server05")

# 実行時間の計測

$executionTime = Measure-Command {
    $report = Get-RemoteSystemStatus -ComputerName $targetNodes -HoursHistory 12 -ThrottleLimit 5
}

Write-Host "総実行時間: $($executionTime.TotalSeconds) 秒"
$report | Format-Table -AutoSize

期待されるパフォーマンス(100台規模の環境想定)

  • 従来の逐次実行(WinRM): 1台あたり約3秒 × 100台 = 約300秒(5分)

  • 本スクリプト(Parallel / スロットル数20): 約15〜20秒(ネットワークレイテンシ依存)

並列実行を適用することで、監視スケジューラーのタイムアウト(通常5分以内)内に収まる高効率なデータ収集が可能となります。

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

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

PowerShell 5.1 には ForEach-Object -Parallel が存在しません。PS 5.1 環境で運用する場合は、Get-CimInstance 自体の -ComputerName パラメータ(マルチスレッド処理内包)を利用するか、PoshRSJob などのワークスルー設計を検討してください。

2. DCOM(WMI)対 WSMan(CIM)のファイアウォール・トランスポート問題

Get-WmiObject は RPC/DCOM(TCP 135 および動的ポート)を使用するため、境界ファイアウォールでブロックされやすい傾向があります。Get-CimInstance(WSManプロトコル)を使用し、TCP 5985 (HTTP) / 5986 (HTTPS) のみを解放する運用を強く推奨します。

3. 文字コードと WinRM 認証の制約

リモート取得したイベントログメッセージに日本語が含まれる場合、出力先のエンコーディング(UTF-8 Bom付き等)に注意が必要です。また、ドメイン非参加マシンに対する実行時は Set-Item WSMan:\localhost\Client\TrustedHosts の設定および RunAs 昇格権限での実行が必須となります。

【まとめ】

本スクリプトを安全に本番運用するための3つのポイントは以下の通りです。

  1. WMIからCIMへの完全移行: DCOM依存を排除し、Port 5985/5986のWinRMベースに統合してセキュリティと通信の安定性を確保する。

  2. エラー制御とセッション破棄の徹底: 応答なしノードによる全体の遅延を防ぐため、try-catch-finally で確実に Remove-CimSession を呼び出しリソース漏れを抑止する。

  3. 並列処理(ThrottleLimit)の最適化: 実行端末のCPU/ネットワーク帯域に合わせて -ThrottleLimit(推奨: 10〜20)を調整し、監視元負荷を適切に制御する。

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

コメント

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