Functions/Private/Get-ClusterInventory.ps1

function Get-ClusterInventory {
    <#
    .SYNOPSIS
        Performs lightweight discovery of nodes, VMs, and Cluster Shared Volumes in a
        Failover Cluster.

    .DESCRIPTION
        Unlike Get-ClusterSnapshot / Get-StorageSnapshot, this collects no performance
        counters (CPU, memory, network, I/O latency) — it only enumerates what exists,
        so it returns quickly and is safe to call at any time.

    .OUTPUTS
        PSCustomObject:
          ClusterName — string
          Timestamp — DateTime
          Nodes — array of PSCustomObject (NodeName, State)
          VMs — array of PSCustomObject (VMName, VMId, HostNode, State,
                        ProcessorCount, MemoryAssignedMB)
          CSVs — array of PSCustomObject (Name, Path, OwnerNode, TotalGB, FreeGB)
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $ClusterName
    )

    Write-Verbose "[ClusterInventory] Enumerating nodes on '$ClusterName'..."
    $clusterNodes = Get-ClusterNode -Cluster $ClusterName -ErrorAction Stop

    $nodeList = foreach ($node in $clusterNodes) {
        [PSCustomObject]@{
            NodeName = $node.Name
            State    = $node.State.ToString()
        }
    }

    $upNodes = @($clusterNodes | Where-Object { $_.State -eq 'Up' } | Select-Object -ExpandProperty Name)

    Write-Verbose "[ClusterInventory] Enumerating VMs across $($upNodes.Count) up node(s)..."
    $vmList = [System.Collections.Generic.List[PSCustomObject]]::new()
    foreach ($node in $upNodes) {
        try {
            Get-VM -ComputerName $node -ErrorAction Stop | ForEach-Object {
                $vmList.Add([PSCustomObject]@{
                    VMName           = $_.Name
                    VMId             = $_.Id.ToString()
                    HostNode         = $node
                    State            = $_.State.ToString()
                    ProcessorCount   = $_.ProcessorCount
                    MemoryAssignedMB = [Math]::Round($_.MemoryAssigned / 1MB, 0)
                })
            }
        } catch {
            Write-Warning "[ClusterInventory] Could not enumerate VMs on '$node': $_"
        }
    }

    Write-Verbose "[ClusterInventory] Enumerating Cluster Shared Volumes..."
    $csvList = [System.Collections.Generic.List[PSCustomObject]]::new()
    try {
        Get-ClusterSharedVolume -Cluster $ClusterName -ErrorAction Stop | ForEach-Object {
            $part    = $_.SharedVolumeInfo.Partition
            $totalGB = [Math]::Round($part.Size      / 1GB, 2)
            $freeGB  = [Math]::Round($part.FreeSpace / 1GB, 2)
            $csvList.Add([PSCustomObject]@{
                Name      = $_.Name
                Path      = $_.SharedVolumeInfo.FriendlyVolumeName
                OwnerNode = if ($_.OwnerNode) { $_.OwnerNode.Name } else { $null }
                TotalGB   = $totalGB
                FreeGB    = $freeGB
            })
        }
    } catch {
        Write-Warning "[ClusterInventory] Could not enumerate Cluster Shared Volumes: $_"
    }

    [PSCustomObject]@{
        ClusterName = $ClusterName
        Timestamp   = Get-Date
        Nodes       = @($nodeList)
        VMs         = @($vmList.ToArray())
        CSVs        = @($csvList.ToArray())
    }
}