かつて山市良と呼ばれたおじさんのブログ

セイテクエンジニアのブログ  かつて山市良と呼ばれたおじさんのブログ  vol.245 Hyper-Vホスト情報の棚卸スクリプト|Hyper-V 健康診断

 

 

vol.245 Hyper-Vホスト情報の棚卸スクリプト|Hyper-V 健康診断

2026年10月26日配信
執筆者:山内 和朗

 前回までの「Hyper-V小技・裏技集」シリーズでは、Hyper-VやHyper-Vホストクラスター上の個々のVMに焦点を当て、PowerShellを使用した自動化テクニックを紹介してきました。今回から「Hyper-V健康診断」シリーズと名前を変えて、PowerShellによるHyper-Vのプラットフォーム側の管理の効率化を目指していきます。

 

 Hyper-Vの仮想化プラットフォームを長期間運用していると、導入当初からさまざまな設定変更が加えられていきます。仮想マシン(VM)の追加や削除、CPUやメモリの増設、VMへのリソースの割り当て変更、仮想スイッチやネットワーク設定、ストレージの変更など、その内容は多岐にわたります。フェールオーバークラスターを構成している場合は、クラスターやクラスター共有ボリューム(CSV)の構成も把握しておく必要があります。

 こうした構成情報は、障害発生時の状況確認や、メンテナンス前後の設定比較にも役立ちます。しかし、Hyper-Vマネージャーやフェールオーバークラスターマネージャー、その他のWindowsの管理ツールを開いて一つずつ確認し、記録していくのは大変です。

 そこで今回は、PowerShellを使ってHyper-Vホストの構成情報をまとめて取得する「棚卸スクリプト」を作成しようと思います。棚卸スクリプトで保存されたデータは、Hyper-Vプラットフォームの構成や状態を把握し、変更点や異常を見つけ、その健全性を維持するための、いわば「カルテ」になります。

 

 棚卸スクリプトで現在の構成をファイルに保存しておけば、後から読み込んで過去の設定を確認したり、最新の構成と比較したり、取得時期の異なるファイル同士を比較して構成変更を検出したりできるようになります。Hyper-Vホストやホストクラスターの「現在の構成を記録する」だけでなく、「以前と比べて何が変わったのか」をPowerShellで確認できるようにすることも、今回作成する棚卸スクリプトの目的です。差分比較の方法については、次回以降で取り上げる予定です。

 

さまざまなコマンドレットの実行結果を1ファイルに

 

 Windowsサーバーの基本構成情報やHyper-Vなどの特定の役割の設定情報の多くは、Windows PowerShell 5.1やPowerShell 6/7標準のコマンドレットや、役割に対応したPowerShellモジュールのコマンドレットで取得することができます。対応するコマンドレットがない場合でも、コマンドラインツールの実行結果やレジストリを参照することで不足をカバーできます。

 

 例えば、コンピューターの基本情報(Get-ComputerInfo、Get-WindowsFeatureなど)、Hyper-Vの設定(Get-VMHost、Get-Clusterなど)、ネットワーク(Get-NetAdapter、Get-VMSwitch、Get-NetIPConfigurationなど)、ストレージ(Get-PhysicalDisk、Get-Volume、Get-ClusterSharedVolumeなど)が返すオブジェクトやコレクションの各オブジェクトには、設定値や現在の状態を含む多数のプロパティが含まれているため、これらのコマンドレットで取得したオブジェクトのコレクションを一括で保存しておけば、後から読み込んで調査するのに役立てることができます。

 

 PowerShellはXML(ConvertTo-Xml/ConvertFrom-Xml)、CSV(Export-Csv/Import-Csv、ConvertTo-Csv/ConvertFrom-Csv)、JSON(ConvertTo-Json/ConvertFrom-Json)といった主要な構造化ファイル形式の扱いを得意としています。今回は、Export-Clixml/Import-Clixmlコマンドレットを使用して、取得した構成情報をCLIXML(Common Language Infrastructure XML)形式で保存することにします。

 

 PowerShell以外の他のツールと組み合わせる場合はXMLやJSON、CSVが適していますが、PowerShellでの利用を前提とした場合、CLIXMLが適しています。*1  CLIXMLは、PowerShellオブジェクトをシリアライズして保存するための形式であり、配列や入れ子になったオブジェクトなどの構造に加えて、プロパティ値の型に関する情報もある程度保持できます。そのため、Get-ComputerInfoやGet-VM、Get-VMHostなど、構造の異なるさまざまなオブジェクトをまとめて1つのファイルに保存する今回の用途に適しています。Export-Clixmlで保存した構成情報は、Import-Clixmlを使用して再びPowerShellオブジェクトとして読み込み、参照や比較などに利用できます。

*1 Export-Clixml/Import-Clixmlの利用例については、「スクリプトから平文パスワードを排除する簡単な方法」の記事で資格情報の安全なエクスポート/インポートについて紹介しました。

 ただし、Import-Clixmlで完全に元のオブジェクトを復元できるわけではない点には注意が必要です。読み込んだオブジェクトは、多くの場合Deserialized.*オブジェクト(元のオブジェクトの状態を保存したコピー、スナップショットのようなもの)となり、メソッドやライブオブジェクトとしての動作は失われます。


 次のPowerShellスクリプト「get-hypervhostinventory.ps1」は、Hyper-VホストまたはHyper-Vホストクラスターのノードで実行することを想定し、Windowsのシステム情報とHyper-Vに関連するホスト、VM、ネットワーク、ストレージ、および過去24時間に記録された重要な(重大/エラー/警告)イベントを1つのCLIXMLファイル(.xml)に保存する棚卸スクリプトです。ファイルは、カレントディレクトリのファイル名「HyperVHost-Inventory-コンピューター名-yyyyMMdd-HHmmss.xml」に保存されます。

 

 「get-hypervhostinventory.ps1」ではファイル出力後にもう1つのPowerShellスクリプト「get-hypervhostsummary.ps1」を実行して保存された情報の概要をコンソールに出力します(画面1)。「get-hypervhostsummary.ps1」は単体で実行することもできます。それには、引数にCLIXMLファイル(.xml)のパスを指定します。

 

[get-hypervhostinventory.ps1](プレーンテキストで表示/ダウンロード)




$ComputerInfo = Get-ComputerInfo

$cv = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'

# クラスター構成を確認
$cluster = $false

try {
    $cluster = Get-Cluster -ErrorAction Stop
}
catch {
    $cluster = $false
}

# HA VM の一覧
$haVMNames = @()

if ($cluster) {
    $haVMNames = @(
        Get-ClusterGroup |
        Where-Object GroupType -eq 'VirtualMachine' |
        Select-Object -ExpandProperty Name
    )
    $replicaBroker = Get-ClusterGroup |
        Where-Object GroupType -eq 'VMReplicaBroker'
}

# VM一覧
$VirtualMachines = @(
    Get-VM | ForEach-Object {

        $vm = $_

        $vm |
            Select-Object *,
                @{
                    Name       = 'HA'
                    Expression = {
                        $_.Name -in $haVMNames
                    }
                },
                @{
                    Name       = 'VCPU'
                    Expression = {
                        Get-VMProcessor -VM $_
                    }
                },
                @{
                    Name       = 'VRAM'
                    Expression = {
                        Get-VMMemory -VM $_
                    }
                },
                @{
                    Name       = 'VMFirmware'
                    Expression = {
                        if ($_.Generation -eq 2) {
                            Get-VMFirmware -VM $_
                        }
                    }
                },
                @{
                    Name       = 'VMSecurity'
                    Expression = {
                        Get-VMSecurity -VM $_
                    }
                },
                @{
                    Name       = 'VNIC'
                    Expression = {
                        @(Get-VMNetworkAdapter -VM $_)
                    }
                },
                @{
                    Name       = 'VHD'
                    Expression = {
                        @(Get-VMHardDiskDrive -VM $_)
                    }
                },
                @{
                    Name       = 'VMSnapshot'
                    Expression = { 
                        @(Get-VMSnapshot -VM $_ -ErrorAction SilentlyContinue)
                    }
                }
    }
)

# Hyper-V / Cluster 関連サービス
$services = @(
    'vmms'
    'vmcompute'
    'hvhost'
    'clussvc'
)

$HyperVServices = @(
    Get-Service -Name $services -ErrorAction SilentlyContinue 
)

# Hyper-V関連のイベントログ(重大/エラー/警告)
$start = (Get-Date).AddHours(-24)

$HyperVLogs = @(
    'System'
    'Application'
    'Security'

    Get-WinEvent -ListLog 'Microsoft-Windows-Hyper-V-*' |
        Where-Object {
            $_.IsEnabled -and $_.RecordCount -gt 0
        } |
        Select-Object -ExpandProperty LogName

    if ($cluster) {
        Get-WinEvent -ListLog 'Microsoft-Windows-FailoverClustering-*' |
            Where-Object {
                $_.IsEnabled -and $_.RecordCount -gt 0
            } |
            Select-Object -ExpandProperty LogName
    }
)

$HyperVEvents = foreach ($log in $HyperVLogs) {

    Get-WinEvent -FilterHashtable @{
        LogName   = $log
        StartTime = $start
        Level     = 1, 2, 3   # Critical, Error, Warning
    } -ErrorAction SilentlyContinue
}

$inventory = [pscustomobject]@{
    CollectedAt = Get-Date

    OSInfo = [pscustomobject]@{
        ComputerName    = $env:COMPUTERNAME
        OperatingSystem = $ComputerInfo.OSName
        OSBuild         = "$($cv.CurrentBuild).$($cv.UBR)"
        isCluster       = if ($cluster) { $true } else { $false }
        ClusterName     = if ($cluster) { $cluster.Name } else { $null }
        HotFix          = @(Get-HotFix)
        WindowsFeatures = @(Get-WindowsFeature)
    }

    ComputerInfo = $ComputerInfo

    HyperV = [pscustomobject]@{
        Services        = $HyperVServices
        NumaNodes = @(Get-VMHostNumaNode)
        VMHost    = Get-VMHost
        VMReplicationServer = if($cluster) {
            if ($replicaBroker) {
                Get-VMReplicationServer -ComputerName $replicaBroker.Name
            } else {
                $null
            }
        } else {
            Get-VMReplicationServer
        }
        VMReplication = @(Get-VMReplication)
    }

    Network = [pscustomobject]@{
        NetAdapters = @(
            Get-NetAdapter
        )
        VMSwitches = @(
            Get-VMSwitch
        )
        NetNats = @(
            Get-NetNat
        )
        NetNatStaticMappings = @(
            Get-NetNatStaticMapping 
        )
        ManagementOSAdapters = @(
            Get-VMNetworkAdapter -ManagementOS
        )
        IPAddresses = @(
            Get-NetIPConfiguration
        )
        FirewallRules = @(
            Get-NetFirewallRule -PolicyStore ActiveStore
        )
    }

    VirtualMachines = @(
        $VirtualMachines
    )

    Storage = [pscustomobject]@{
        PhysicalDisks = @(
            Get-PhysicalDisk
        )
        Volumes = @(
            Get-Volume
        )
        ClusterSharedVolumes = @(
            if ($cluster) {
                Get-ClusterSharedVolume
            }
        )
    S2D = if ($cluster) {
        $s2dPool = @(Get-StoragePool -FriendlyName 'S2D*' -ErrorAction SilentlyContinue)
            if ($s2dPool) {
                [pscustomobject]@{
                    StoragePools = $s2dPool
                    PhysicalDisks = @(
                        Get-StorageSubsystem -FriendlyName 'Cluster*' |
                            Get-PhysicalDisk
                    )
                    VirtualDisks = @(
                        $s2dPool | Get-VirtualDisk
                    )
                }
            } else {
                $null
            }
        } else {
            $null
        }
    }

    HyperVEvents = @(
        $HyperVEvents |
            Sort-Object TimeCreated |
            Select-Object TimeCreated, LogName, Id, Level, LevelDisplayName, Message
    )
}

$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'

$inventory | Export-Clixml ".\HyperVHost-Inventory-$env:COMPUTERNAME-$timestamp.xml"
& .\get-hypervhostsummary.ps1 ".\HyperVHost-Inventory-$env:COMPUTERNAME-$timestamp.xml"
Write-Host "`nOutput to: .\HyperVHost-Inventory-$env:COMPUTERNAME-$timestamp.xml"

 

[get-hypervhostsummary.ps1](プレーンテキストで表示/ダウンロード、Hyper-Vホストクラスターでの出力サンプル)

param(
    [Parameter(Mandatory)]
    [string]$Path
)
$inventory = Import-Clixml $Path

$computer = $inventory.ComputerInfo
$roles    = $inventory.OSInfo.WindowsFeatures
$services = @($inventory.HyperV.Services)
$vms      = @($inventory.VirtualMachines)
$nics     = @($inventory.Network.NetAdapters)
$mgmtNics = @($inventory.Network.ManagementOSAdapters)
$switches = @($inventory.Network.VMSwitches)
$volumes  = @($inventory.Storage.Volumes)

Write-Host "`n### Hyper-V Host Summary ###"

[pscustomobject]@{
    # OS / Host
    ComputerName        = $inventory.OSInfo.ComputerName
    OperatingSystem     = $inventory.OSInfo.OperatingSystem
    OSBuild             = $inventory.OSInfo.OSBuild
    CollectedAt         = $inventory.CollectedAt.DateTime

    # ComputerInfo
    Manufacturer        = $computer.CsManufacturer
    Model               = $computer.CsModel
    Processor           = $computer.CsProcessors.Name
    PhysicalProcessors  = @($computer.CsProcessors).Count
    LogicalProcessors   = $computer.CsNumberOfLogicalProcessors
    NUMANodes = @($inventory.NumaNodes).Count
    MemoryGB            = [math]::Round(
                              $computer.CsTotalPhysicalMemory / 1GB, 1
                          )
    BIOSVersion         = $computer.BiosSMBIOSBIOSVersion

    # Cluster
    ClusterName         = $inventory.OSInfo.ClusterName

    # Role and Features
    InstalledRoles      = ($roles | 
        Where-Object {$_.Installed -and $_.FeatureType -like 'Role*'}).Count
    InstalledFeatures   = ($roles |
        Where-Object {$_.Installed -and $_.FeatureType -like 'Feature*'}).Count
    # Services
    HyperVServices      = $services.Count
    RunningServices     = @($services |
                              Where-Object Status -like 'Running'
                          ).Count

    # VM
    VMCount             = $vms.Count
    RunningVMs          = @($vms | Where-Object State -like 'Running').Count
    StoppedVMs          = @($vms | Where-Object State -like 'Off').Count
    HAVMs               = @($vms | Where-Object HA -eq $true).Count

    # Network
    NetAdapters         = $nics.Count
    UpNetAdapters       = @($nics | Where-Object Status -like 'Up').Count
    ManagementOSNICs    = $mgmtNics.Count
    VMSwitches          = $switches.Count

    # Storage
    Volumes             = $volumes.Count
}

Write-Host "`n### WindowsFeatures (Get-WindowsFeature) ###"
$roles | 
    Where-Object {$_.Installed} |
    Format-Table -AutoSize

Write-Host "`n### Services (Get-Service) ###"
$services |
    Format-Table Name, Status, StartType

Write-Host "`n=== VMHost (Get-VMHost) ==="
$inventory.HyperV.VMHost |
    Select LogicalProcessorCount, NumaSpanningEnabled, MemoryCapacity, VirtualMachineMigrationEnabled,VirtualHardDiskPath, VirtualMachinePath |
    Format-List

Write-Host "`n### Virtual Machines (Get-VM) ###"
$vms |
    Select-Object Name, State, Generation, ProcessorCount, HA |
    Format-Table -AutoSize

Write-Host "`n=== VMReplicationServer (Get-VMReplicationServer) ==="
$inventory.HyperV.VMReplicationServer |
    Format-Table -AutoSize

Write-Host "`n=== VMReplication (Get-VMReplication) ==="
$inventory.HyperV.VMReplication  |
    Format-Table -AutoSize

Write-Host "`n### Network ###"
Write-Host "`n=== NetAdapters (Get-NetAdapter) ==="
$nics |
    Select-Object Name, Status, LinkSpeed |
    Format-Table -AutoSize

Write-Host "`n=== VMSwitches (Get-VMSwitch) ==="
$switches |
   Select Name,SwitchType,AllowManagementOS,EmbeddedTeamingEnabled,AllowNetLbfoTeams |
   Format-Table -AutoSize

Write-Host "`n=== IPAddresses (Get-NetIPConfiguration) ==="
$inventory.Network.IPAddresses |
        ForEach-Object {
            [pscustomobject]@{
                IfAlias = $_.InterfaceAlias
                IfIndex = $_.InterfaceIndex
                MacAddress     = $_.NetAdapter.MacAddress

                IPv4Address = @(
                    $_.IPv4Address | ForEach-Object {
                        [pscustomobject]@{
                            IPAddress    = $_.IPAddress
                            PrefixLength = $_.PrefixLength
                        }
                    }
                )

                IPv6Address = @(
                    $_.IPv6Address | ForEach-Object {
                        [pscustomobject]@{
                            IPAddress    = $_.IPAddress
                            PrefixLength = $_.PrefixLength
                        }
                    }
                )

                IPv4Gateway = @(
                    $_.IPv4DefaultGateway.NextHop
                )

                IPv6Gateway = @(
                    $_.IPv6DefaultGateway.NextHop
                )

                DNSServer = @(
                    $_.DNSServer.ServerAddresses
                )
            }
        } |
    Select-Object IfAlias,IfIndex, MacAddress,
        @{N='IPv4Address';E={
            ($_.IPv4Address | ForEach-Object {
                "$($_.IPAddress)/$($_.PrefixLength)"
            }) -join ', '
        }} |Format-Table -AutoSize

Write-Host "`n### Storage ###"
Write-Host "`n=== Volumes (Get-Volume) ==="
$volumes |
    Select-Object DriveLetter, FileSystemLabel,
        @{N='SizeGB';E={[math]::Round($_.Size / 1GB,1)}},
        @{N='FreeGB';E={[math]::Round($_.SizeRemaining / 1GB,1)}} |
    Format-Table -AutoSize
Write-Host "`n=== PhysicalDisks (Get-PhysicalDisk) ==="
$inventory.Storage.PhysicalDisks | Format-Table -AutoSize
Write-Host "`n=== ClusterSharedVolumes (Get-ClusterSharedVolume) ==="
$inventory.Storage.ClusterSharedVolumes |Select Name, OwnerNode, State, @{Name='Path'; Expression={$_.SharedVolumeInfo.FriendlyVolumeName}} | Format-Table -AutoSize
Write-Host "`n=== S2D.StoragePools (Get-StoragePool) ==="
$inventory.Storage.S2D.StoragePools|Format-Table -AutoSize
Write-Host "`n=== S2D.PhysicalDisks (Get-PhysicalDisk) ==="
$inventory.Storage.S2D.PhysicalDisks|Format-Table -AutoSize
Write-Host "`n=== S2D.VirtualDisks (Get-VirtualDisk) ==="
$inventory.Storage.S2D.VirtualDisks|Format-Table -AutoSize

Write-Host "`n### EventLog (Get-WinEvent, Critical/Error in the past 24h) ###"
$Inventory.HyperVEvents |
    Where-Object LevelDisplayName -in '重大','エラー' |
    Group-Object LogName |
    ForEach-Object {
        Write-Host "`n=== $($_.Name) ==="
        $_.Group |
            Sort-Object TimeCreated -Descending |
            Format-Table TimeCreated, Id, LevelDisplayName, Message -AutoSize -Wrap
    }

 

vol245_scr01

画面1 棚卸スクリプトは、スタンドアロンのHyper-VホストとHyper-Vホストクラスターの両方に対応(クラスターでの出力サンプルはこちら)

 

 次回はこの棚卸スクリプトで保存したCLIXMLファイル(.xml)を読み込んで、Hyper-Vホストの構成データを参照する方法を紹介します。今回のPowerShellスクリプト「Get-hypervhostsummary.ps1」はその一例です。なお、棚卸スクリプトは次回以降に説明する内容に対応させるために項目を追加していった結果、かなり複雑で大きなデータになってしまいました。棚卸データで保存されるデータの構造については、次回説明します。

 

サーバー設定仕様書の自動生成サービス、SSD-assistanceのご紹介

 弊社の主力製品の1つ設定仕様書自動生成サービス「SSD-assistance」は、設定仕様書作成のためのデータ収集やドキュメント作成の手間と時間を省き、高品質で標準化された設定仕様書を自動生成するサービスです。SSD-assistanceは、「Hyper-V ホスト 設定仕様書」の作成に対応している上、「差分比較リスト」で構成の変更差分を視覚的に強調表示させることができます。今回、そして次回以降の記事でやろうとしていることと同等のことは、実はSSD-assistanceを利用すれば誰でも、簡単に、すぐに実現することができます。ただこの連載記事では、SSD-assistanceでは手の届かない、Hyper-Vの深くて、細かいところまで手を伸ばせればと考えています。

Hyper-Vホスト設定仕様書のサンプル

Hyper-Vホスト設定仕様書のサンプル(クリックで拡大)

差分比較リストのサンプル

差分比較リストのサンプル(クリックで拡大)

 

 

リファレンス(Microsoft Learn):

Get-ComputerInfo(PowerShell Management)|Get-Service(PowerShell Management)|Get-HotFix(PowerShell Management)|Get-VMHost(Hyper-V)|Get-VMHostNumaNode(Hyper-V)|Get-VM(Hyper-V)|Get-VMProcessor(Hyper-V)|Get-VMMemory(Hyper-V)|Get-VMFirmware(Hyper-V)|Get-VMSecurity(Hyper-V)|Get-VMNetworkAdapter(Hyper-V)|Get-VMHardDiskDrive(Hyper-V)|Get-VMSnapshot(Hyper-V)|Get-VMReplicationServer(Hyper-V)|Get-VMReplication(Hyper-V)|Get-VMSwitch(Hyper-V)|Get-Cluster(FailoverClusters)|Get-ClusterGroup(FailoverClusters)|Get-ClusterSharedVolume(FailoverClusters)|Get-NetAdapter(NetAdapter)|Get-PhysicalDisk(Storage)|Get-Volume(Storage)|Get-WinEvent(PowerShell Dignostics)|Export-Clixml(PowerShell Utility)|Import-Clixml(PowerShell Utility)|ConvertTo-JSON(PowerShell Utility)|ConvertFrom-JSON(PowerShell Utility)|Compare-Object(PowerShell Utility) 

Hyper-V 小技・裏技集 目次|(1) | (2)

blog_yamanxworld_subscribe

blog_yamanxworld_comment

blog_yamanxworld_WP_ws2025

最新記事