Public/System/Get-VergeInventory.ps1

function Get-VergeInventory {
    <#
    .SYNOPSIS
        Generates a comprehensive inventory report of VergeOS infrastructure.

    .DESCRIPTION
        Get-VergeInventory aggregates inventory data across all resource types in a VergeOS
        system, similar to RVtools for VMware environments. Returns detailed information
        about VMs, networks, storage, nodes, clusters, tenants, and snapshots.

        The inventory can be exported to CSV, JSON, or used with Export-Excel for
        comprehensive Excel reports.

    .PARAMETER ResourceType
        Filter inventory by resource type(s). Valid values:
        - VMs: Virtual machines with CPU, RAM, OS, power state
        - Networks: Virtual networks with DHCP, DNS, IP configuration
        - Storage: Storage tiers with capacity and usage
        - Nodes: Physical nodes with hardware details
        - Clusters: Cluster configuration and resource utilization
        - Tenants: Multi-tenant environments
        - VMSnapshots: Individual VM and tenant point-in-time snapshots
        - CloudSnapshots: System-wide cloud snapshots (with immutability status)
        - NAS: NAS services, volumes, and shares
        - All: All resource types (default)

    .PARAMETER IncludeSnapshots
        Include snapshot VMs in the VM list. By default, VMs that are point-in-time snapshots
        of other VMs are excluded. Snapshot metadata is available via VMSnapshots and
        CloudSnapshots resource types regardless of this setting.

    .PARAMETER IncludePoweredOff
        Include powered-off VMs. By default, all VMs are included regardless of power state.
        Set to $false to exclude powered-off VMs.

    .PARAMETER Summary
        Return summary counts only instead of detailed inventory data.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Get-VergeInventory

        Returns complete inventory of all resources.

    .EXAMPLE
        Get-VergeInventory -ResourceType VMs, Networks

        Returns inventory of VMs and networks only.

    .EXAMPLE
        Get-VergeInventory -Summary

        Returns summary counts for all resource types.

    .EXAMPLE
        Get-VergeInventory -ResourceType VMs | Export-Csv -Path "VM_Inventory.csv"

        Exports VM inventory to CSV format.

    .EXAMPLE
        Get-VergeInventory | Export-Excel -Path "VergeOS_Inventory.xlsx" -WorksheetName "Inventory"

        Exports full inventory to Excel (requires ImportExcel module).

    .EXAMPLE
        Get-VergeInventory -ResourceType VMs -IncludePoweredOff:$false

        Returns inventory of running VMs only.

    .EXAMPLE
        $inventory = Get-VergeInventory
        $inventory.VMs | Format-Table Name, PowerState, CPUCores, RAM
        $inventory.Networks | Format-Table Name, Type, NetworkAddress
        $inventory.Storage | Format-Table Tier, CapacityGB, UsedGB, UsedPercent

        Access specific resource types from the inventory object.

    .OUTPUTS
        PSCustomObject with PSTypeName 'Verge.Inventory' containing:
        - VMs: Array of VM objects
        - Networks: Array of network objects
        - Storage: Array of storage tier objects
        - Nodes: Array of node objects
        - Clusters: Array of cluster objects
        - Tenants: Array of tenant objects
        - VMSnapshots: Array of VM/tenant snapshot objects
        - CloudSnapshots: Array of cloud snapshot objects (with immutability info)
        - NAS: Array of NAS service/volume objects
        - Summary: Object with counts per resource type
        - GeneratedAt: Timestamp of inventory generation
        - Server: VergeOS server name

        When -Summary is specified, returns only the Summary object.

    .NOTES
        For large environments, consider filtering by ResourceType to reduce query time.
        Use with Export-Excel module for RVtools-style Excel reports with multiple worksheets.

        When using -ResourceType to filter, Summary fields for non-requested resource types
        will be $null rather than 0. This allows callers to distinguish between "zero resources
        exist" and "this resource type was not queried."
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Position = 0)]
        [ValidateSet('All', 'VMs', 'Networks', 'Storage', 'Nodes', 'Clusters', 'Tenants', 'VMSnapshots', 'CloudSnapshots', 'NAS')]
        [string[]]$ResourceType = @('All'),

        [Parameter()]
        [switch]$IncludeSnapshots,

        [Parameter()]
        [bool]$IncludePoweredOff = $true,

        [Parameter()]
        [switch]$Summary,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Determine which resources to collect
        $collectAll = $ResourceType -contains 'All'
        $collectVMs = $collectAll -or $ResourceType -contains 'VMs'
        $collectNetworks = $collectAll -or $ResourceType -contains 'Networks'
        $collectStorage = $collectAll -or $ResourceType -contains 'Storage'
        $collectNodes = $collectAll -or $ResourceType -contains 'Nodes'
        $collectClusters = $collectAll -or $ResourceType -contains 'Clusters'
        $collectTenants = $collectAll -or $ResourceType -contains 'Tenants'
        $collectVMSnapshots = $collectAll -or $ResourceType -contains 'VMSnapshots'
        $collectCloudSnapshots = $collectAll -or $ResourceType -contains 'CloudSnapshots'
        $collectNAS = $collectAll -or $ResourceType -contains 'NAS'
    }

    process {
        try {
            Write-Verbose "Generating inventory from $($Server.Server)"

            # Initialize inventory object
            $inventory = [PSCustomObject]@{
                PSTypeName     = 'Verge.Inventory'
                Server         = $Server.Server
                GeneratedAt    = Get-Date
                VMs            = @()
                Networks       = @()
                Storage        = @()
                Nodes          = @()
                Clusters       = @()
                Tenants        = @()
                VMSnapshots    = @()
                CloudSnapshots = @()
                NAS            = @()
                Summary        = $null
            }

            # Collect VMs
            if ($collectVMs) {
                Write-Verbose "Collecting VM inventory..."
                $vmParams = @{ Server = $Server }
                if ($IncludeSnapshots) {
                    $vmParams['IncludeSnapshots'] = $true
                }

                $vms = Get-VergeVM @vmParams

                # Filter powered-off if requested
                if (-not $IncludePoweredOff) {
                    $vms = $vms | Where-Object { $_.PowerState -eq 'Running' }
                }

                # Bulk fetch all drives and NICs in 2 API calls instead of 2N calls
                # This is much more efficient for large environments
                Write-Verbose "Bulk fetching drives and NICs for all VMs..."

                # Fetch all virtual drives (physical_status eq null excludes physical host drives)
                $driveQuery = @{
                    filter = 'physical_status eq null'
                    fields = '$key,name,machine,disksize,used_bytes,media_source#allocated_bytes as allocated_bytes'
                }
                $driveResponse = Invoke-VergeAPI -Method GET -Endpoint 'machine_drives' -Query $driveQuery -Connection $Server -ErrorAction SilentlyContinue
                $allDrives = if ($driveResponse -is [array]) { $driveResponse } elseif ($driveResponse) { @($driveResponse) } else { @() }

                # Fetch all NICs
                $nicQuery = @{
                    fields = '$key,name,machine'
                }
                $nicResponse = Invoke-VergeAPI -Method GET -Endpoint 'machine_nics' -Query $nicQuery -Connection $Server -ErrorAction SilentlyContinue
                $allNICs = if ($nicResponse -is [array]) { $nicResponse } elseif ($nicResponse) { @($nicResponse) } else { @() }

                # Build lookup tables by machine key for O(1) access
                # API returns 'machine' field, not 'MachineKey'
                $drivesByMachine = @{}
                foreach ($drive in $allDrives) {
                    $machineKey = $drive.machine
                    if ($machineKey -and -not $drivesByMachine.ContainsKey($machineKey)) {
                        $drivesByMachine[$machineKey] = [System.Collections.Generic.List[object]]::new()
                    }
                    if ($machineKey) {
                        $drivesByMachine[$machineKey].Add($drive)
                    }
                }

                $nicsByMachine = @{}
                foreach ($nic in $allNICs) {
                    $machineKey = $nic.machine
                    if ($machineKey -and -not $nicsByMachine.ContainsKey($machineKey)) {
                        $nicsByMachine[$machineKey] = [System.Collections.Generic.List[object]]::new()
                    }
                    if ($machineKey) {
                        $nicsByMachine[$machineKey].Add($nic)
                    }
                }

                # Enrich VMs with drive and NIC counts using lookup tables
                $inventory.VMs = foreach ($vm in $vms) {
                    $drives = if ($drivesByMachine.ContainsKey($vm.MachineKey)) {
                        $drivesByMachine[$vm.MachineKey]
                    } else { @() }

                    $nics = if ($nicsByMachine.ContainsKey($vm.MachineKey)) {
                        $nicsByMachine[$vm.MachineKey]
                    } else { @() }

                    # Calculate total disk size from raw API response
                    # disksize or allocated_bytes, convert from bytes to GB
                    $totalDiskBytes = ($drives | ForEach-Object {
                        if ($_.disksize) { $_.disksize }
                        elseif ($_.allocated_bytes) { $_.allocated_bytes }
                        else { 0 }
                    } | Measure-Object -Sum).Sum
                    $totalDiskGB = $totalDiskBytes / 1GB

                    [PSCustomObject]@{
                        PSTypeName       = 'Verge.Inventory.VM'
                        Key              = $vm.Key
                        Name             = $vm.Name
                        Description      = $vm.Description
                        PowerState       = $vm.PowerState
                        CPUCores         = $vm.CPUCores
                        RAMGB            = [math]::Round($vm.RAM / 1024, 1)
                        RAMMb            = $vm.RAM
                        OSFamily         = $vm.OSFamily
                        GuestAgent       = $vm.GuestAgent
                        UEFI             = $vm.UEFI
                        SecureBoot       = $vm.SecureBoot
                        MachineType      = $vm.MachineType
                        Cluster          = $vm.Cluster
                        Node             = $vm.Node
                        HAGroup          = $vm.HAGroup
                        SnapshotProfile  = $vm.SnapshotProfile
                        DiskCount        = @($drives).Count
                        TotalDiskGB      = [math]::Round($totalDiskGB, 1)
                        NICCount         = @($nics).Count
                        Created          = $vm.Created
                        Modified         = $vm.Modified
                    }
                }
            }

            # Collect Networks
            if ($collectNetworks) {
                Write-Verbose "Collecting network inventory..."
                $networks = Get-VergeNetwork -Server $Server

                $inventory.Networks = foreach ($net in $networks) {
                    [PSCustomObject]@{
                        PSTypeName     = 'Verge.Inventory.Network'
                        Key            = $net.Key
                        Name           = $net.Name
                        Description    = $net.Description
                        Type           = $net.Type
                        PowerState     = $net.PowerState
                        NetworkAddress = $net.NetworkAddress
                        IPAddress      = $net.IPAddress
                        Gateway        = $net.Gateway
                        MTU            = $net.MTU
                        DHCPEnabled    = $net.DHCPEnabled
                        DHCPStart      = $net.DHCPStart
                        DHCPStop       = $net.DHCPStop
                        DNS            = $net.DNS
                        Domain         = $net.Domain
                        Cluster        = $net.Cluster
                        Node           = $net.Node
                    }
                }
            }

            # Collect Storage
            if ($collectStorage) {
                Write-Verbose "Collecting storage inventory..."
                $tiers = Get-VergeStorageTier -Server $Server

                $inventory.Storage = foreach ($tier in $tiers) {
                    [PSCustomObject]@{
                        PSTypeName   = 'Verge.Inventory.Storage'
                        Tier         = $tier.Tier
                        Description  = $tier.Description
                        CapacityGB   = $tier.CapacityGB
                        UsedGB       = $tier.UsedGB
                        FreeGB       = $tier.FreeGB
                        AllocatedGB  = $tier.AllocatedGB
                        UsedPercent  = $tier.UsedPercent
                        DedupeRatio  = $tier.DedupeRatio
                        ReadOps      = $tier.ReadOps
                        WriteOps     = $tier.WriteOps
                    }
                }
            }

            # Collect Nodes
            if ($collectNodes) {
                Write-Verbose "Collecting node inventory..."
                $nodes = Get-VergeNode -Server $Server

                $inventory.Nodes = foreach ($node in $nodes) {
                    [PSCustomObject]@{
                        PSTypeName      = 'Verge.Inventory.Node'
                        Key             = $node.Key
                        Name            = $node.Name
                        Status          = $node.Status
                        Cluster         = $node.Cluster
                        Cores           = $node.Cores
                        RAMGB           = [math]::Round($node.RAM / 1024, 1)
                        RAMMb           = $node.RAM
                        MaintenanceMode = $node.MaintenanceMode
                        NeedsRestart    = $node.NeedsRestart
                        RestartReason   = $node.RestartReason
                        IOMMU           = $node.IOMMU
                        VergeOSVersion  = $node.VergeOSVersion
                        KernelVersion   = $node.KernelVersion
                    }
                }
            }

            # Collect Clusters
            if ($collectClusters) {
                Write-Verbose "Collecting cluster inventory..."
                $clusters = Get-VergeCluster -Server $Server

                $inventory.Clusters = foreach ($cluster in $clusters) {
                    [PSCustomObject]@{
                        PSTypeName         = 'Verge.Inventory.Cluster'
                        Key                = $cluster.Key
                        Name               = $cluster.Name
                        Description        = $cluster.Description
                        Status             = $cluster.Status
                        TotalNodes         = $cluster.TotalNodes
                        OnlineNodes        = $cluster.OnlineNodes
                        OnlineCores        = $cluster.OnlineCores
                        UsedCores          = $cluster.UsedCores
                        OnlineRAMGB        = [math]::Round($cluster.OnlineRAM / 1024, 1)
                        UsedRAMGB          = [math]::Round($cluster.UsedRAM / 1024, 1)
                        RunningMachines    = $cluster.RunningMachines
                        DefaultCPUType     = $cluster.DefaultCPUType
                        NestedVirtualization = $cluster.NestedVirtualization
                    }
                }
            }

            # Collect Tenants
            if ($collectTenants) {
                Write-Verbose "Collecting tenant inventory..."
                $tenantParams = @{ Server = $Server }
                if ($IncludeSnapshots) {
                    $tenantParams['IncludeSnapshots'] = $true
                }
                $tenants = Get-VergeTenant @tenantParams

                $inventory.Tenants = foreach ($tenant in $tenants) {
                    [PSCustomObject]@{
                        PSTypeName  = 'Verge.Inventory.Tenant'
                        Key         = $tenant.Key
                        Name        = $tenant.Name
                        Description = $tenant.Description
                        Status      = $tenant.Status
                        State       = $tenant.State
                        IsRunning   = $tenant.IsRunning
                        Isolated    = $tenant.Isolated
                        URL         = $tenant.URL
                        UIAddress   = $tenant.UIAddress
                        NetworkName = $tenant.NetworkName
                        Created     = $tenant.Created
                        Started     = $tenant.Started
                    }
                }
            }

            # Collect VM Snapshots (individual VM point-in-time snapshots - manually created only)
            # Uses bulk API calls instead of per-VM queries to avoid N+1 performance issue
            if ($collectVMSnapshots) {
                Write-Verbose "Collecting VM snapshot inventory..."
                $vmSnapshotList = [System.Collections.Generic.List[object]]::new()

                # Bulk fetch all VM snapshots in a single API call
                # Filter out cloud snapshot entries (snapshot_period is set for cloud snapshots)
                $snapQuery = @{
                    fields = '$key,name,description,created,expires,expires_type,quiesced,created_manually,machine,snap_machine,snapshot_period'
                    filter = 'snapshot_period eq null'
                    sort   = '-created'
                }
                $snapResponse = Invoke-VergeAPI -Method GET -Endpoint 'machine_snapshots' -Query $snapQuery -Connection $Server -ErrorAction SilentlyContinue
                $allMachineSnapshots = if ($snapResponse -is [array]) { $snapResponse } elseif ($snapResponse) { @($snapResponse) } else { @() }

                # Build VM name lookup for display (reuse allVMs if already fetched for VM collection)
                $vmNameLookup = @{}
                $lookupVMs = if ($collectVMs -and $vms) { $vms } else { Get-VergeVM -Server $Server -IncludeSnapshots:$false }
                foreach ($vm in $lookupVMs) {
                    if ($vm.MachineKey) { $vmNameLookup[$vm.MachineKey] = $vm }
                }

                foreach ($snapshot in $allMachineSnapshots) {
                    if (-not $snapshot -or -not $snapshot.name) { continue }

                    $machineKey = $snapshot.machine
                    $vmInfo = if ($vmNameLookup.ContainsKey($machineKey)) { $vmNameLookup[$machineKey] } else { $null }

                    # Convert timestamps
                    $createdDate = if ($snapshot.created) {
                        [DateTimeOffset]::FromUnixTimeSeconds($snapshot.created).LocalDateTime
                    } else { $null }
                    $expiresDate = if ($snapshot.expires -and $snapshot.expires -gt 0) {
                        [DateTimeOffset]::FromUnixTimeSeconds($snapshot.expires).LocalDateTime
                    } else { $null }

                    $vmSnapshotList.Add([PSCustomObject]@{
                        PSTypeName      = 'Verge.Inventory.VMSnapshot'
                        Key             = [int]$snapshot.'$key'
                        Name            = $snapshot.name
                        Description     = $snapshot.description
                        VMName          = if ($vmInfo) { $vmInfo.Name } else { "VM:$machineKey" }
                        VMKey           = if ($vmInfo) { $vmInfo.Key } else { $null }
                        Created         = $createdDate
                        Expires         = $expiresDate
                        NeverExpires    = ($snapshot.expires_type -eq 'never' -or $snapshot.expires -eq 0)
                        Quiesced        = [bool]$snapshot.quiesced
                        CreatedManually = [bool]$snapshot.created_manually
                    })
                }

                # Bulk fetch all tenant snapshots in a single API call
                $tenantSnapQuery = @{
                    fields = '$key,tenant,name,description,created,expires'
                    sort   = '-created'
                }
                $tenantSnapResponse = Invoke-VergeAPI -Method GET -Endpoint 'tenant_snapshots' -Query $tenantSnapQuery -Connection $Server -ErrorAction SilentlyContinue
                $allTenantSnapshots = if ($tenantSnapResponse -is [array]) { $tenantSnapResponse } elseif ($tenantSnapResponse) { @($tenantSnapResponse) } else { @() }

                # Build tenant name lookup
                $tenantNameLookup = @{}
                $lookupTenants = if ($collectTenants -and $tenants) { $tenants } else { Get-VergeTenant -Server $Server -ErrorAction SilentlyContinue }
                foreach ($t in $lookupTenants) {
                    if ($t.Key) { $tenantNameLookup[$t.Key] = $t.Name }
                }

                foreach ($snapshot in $allTenantSnapshots) {
                    if (-not $snapshot -or -not $snapshot.name) { continue }

                    $tenantKey = [int]$snapshot.tenant
                    $tenantName = if ($tenantNameLookup.ContainsKey($tenantKey)) { $tenantNameLookup[$tenantKey] } else { "Tenant:$tenantKey" }

                    $createdDate = if ($snapshot.created) {
                        [DateTimeOffset]::FromUnixTimeSeconds($snapshot.created).LocalDateTime
                    } else { $null }
                    $expiresDate = if ($snapshot.expires -and $snapshot.expires -gt 0) {
                        [DateTimeOffset]::FromUnixTimeSeconds($snapshot.expires).LocalDateTime
                    } else { $null }

                    $vmSnapshotList.Add([PSCustomObject]@{
                        PSTypeName      = 'Verge.Inventory.VMSnapshot'
                        Key             = [int]$snapshot.'$key'
                        Name            = $snapshot.name
                        Description     = $snapshot.description
                        VMName          = "[Tenant] $tenantName"
                        VMKey           = $tenantKey
                        Created         = $createdDate
                        Expires         = $expiresDate
                        NeverExpires    = ($snapshot.expires -eq 0 -or -not $snapshot.expires)
                        Quiesced        = $null
                        CreatedManually = $null
                    })
                }

                $inventory.VMSnapshots = $vmSnapshotList
            }

            # Collect Cloud Snapshots (system-wide snapshots)
            if ($collectCloudSnapshots) {
                Write-Verbose "Collecting cloud snapshot inventory..."
                $cloudSnapshotList = @()

                $cloudSnapshots = Get-VergeCloudSnapshot -Server $Server -IncludeExpired -ErrorAction SilentlyContinue
                foreach ($snap in $cloudSnapshots) {
                    # Determine if snapshot is currently expired
                    $isExpired = if ($snap.Expires) {
                        $snap.Expires -lt (Get-Date)
                    } else {
                        $false
                    }

                    $cloudSnapshotList += [PSCustomObject]@{
                        PSTypeName           = 'Verge.Inventory.CloudSnapshot'
                        Key                  = $snap.Key
                        Name                 = $snap.Name
                        Description          = $snap.Description
                        Created              = $snap.Created
                        Expires              = $snap.Expires
                        NeverExpires         = $snap.NeverExpires
                        IsExpired            = $isExpired
                        Profile              = $snap.SnapshotProfileName
                        Status               = $snap.Status
                        Immutable            = $snap.Immutable
                        ImmutableStatus      = $snap.ImmutableStatus
                        ImmutableLockExpires = $snap.ImmutableLockExpires
                        RemoteSync           = $snap.RemoteSync
                    }
                }

                $inventory.CloudSnapshots = $cloudSnapshotList
            }

            # Collect NAS
            if ($collectNAS) {
                Write-Verbose "Collecting NAS inventory..."
                $nasItems = @()

                # NAS Services
                $nasServices = Get-VergeNASService -Server $Server -ErrorAction SilentlyContinue
                foreach ($svc in $nasServices) {
                    $nasItems += [PSCustomObject]@{
                        PSTypeName  = 'Verge.Inventory.NAS'
                        ItemType    = 'Service'
                        Key         = $svc.Key
                        Name        = $svc.Name
                        Description = $svc.Description
                        Status      = $svc.Status
                        IPAddress   = $svc.IPAddress
                        Cluster     = $svc.Cluster
                        Node        = $svc.Node
                    }
                }

                # NAS Volumes
                $nasVolumes = Get-VergeNASVolume -Server $Server -ErrorAction SilentlyContinue
                foreach ($vol in $nasVolumes) {
                    $nasItems += [PSCustomObject]@{
                        PSTypeName  = 'Verge.Inventory.NAS'
                        ItemType    = 'Volume'
                        Key         = $vol.Key
                        Name        = $vol.Name
                        Description = $vol.Description
                        Status      = $vol.MountStatus
                        Tier        = $vol.PreferredTier
                        SizeGB      = $vol.MaxSizeGB
                        UsedGB      = $vol.UsedGB
                        NASService  = $vol.NASService
                    }
                }

                $inventory.NAS = $nasItems
            }

            # Generate summary - use $null for resource types that were not collected
            # so users can distinguish "zero resources" from "not queried"
            $summaryData = [PSCustomObject]@{
                PSTypeName        = 'Verge.Inventory.Summary'
                Server            = $Server.Server
                GeneratedAt       = $inventory.GeneratedAt
                VMsTotal          = if ($collectVMs) { $inventory.VMs.Count } else { $null }
                VMsRunning        = if ($collectVMs) { ($inventory.VMs | Where-Object PowerState -eq 'Running').Count } else { $null }
                VMsStopped        = if ($collectVMs) { ($inventory.VMs | Where-Object PowerState -eq 'Stopped').Count } else { $null }
                TotalCPUCores     = if ($collectVMs) { ($inventory.VMs | Measure-Object -Property CPUCores -Sum).Sum } else { $null }
                TotalRAMGB        = if ($collectVMs) { [math]::Round(($inventory.VMs | Measure-Object -Property RAMGB -Sum).Sum, 1) } else { $null }
                TotalDiskGB       = if ($collectVMs) { [math]::Round(($inventory.VMs | Measure-Object -Property TotalDiskGB -Sum).Sum, 1) } else { $null }
                NetworksTotal     = if ($collectNetworks) { $inventory.Networks.Count } else { $null }
                NetworksRunning   = if ($collectNetworks) { ($inventory.Networks | Where-Object PowerState -eq 'Running').Count } else { $null }
                StorageTiers      = if ($collectStorage) { $inventory.Storage.Count } else { $null }
                StorageCapacityGB = if ($collectStorage) { [math]::Round(($inventory.Storage | Measure-Object -Property CapacityGB -Sum).Sum, 1) } else { $null }
                StorageUsedGB     = if ($collectStorage) { [math]::Round(($inventory.Storage | Measure-Object -Property UsedGB -Sum).Sum, 1) } else { $null }
                NodesTotal        = if ($collectNodes) { $inventory.Nodes.Count } else { $null }
                NodesOnline       = if ($collectNodes) { ($inventory.Nodes | Where-Object Status -eq 'Running').Count } else { $null }
                ClustersTotal     = if ($collectClusters) { $inventory.Clusters.Count } else { $null }
                TenantsTotal      = if ($collectTenants) { $inventory.Tenants.Count } else { $null }
                TenantsOnline     = if ($collectTenants) { ($inventory.Tenants | Where-Object IsRunning -eq $true).Count } else { $null }
                VMSnapshotsTotal  = if ($collectVMSnapshots) { $inventory.VMSnapshots.Count } else { $null }
                CloudSnapshotsTotal = if ($collectCloudSnapshots) { $inventory.CloudSnapshots.Count } else { $null }
                NASServices       = if ($collectNAS) { ($inventory.NAS | Where-Object ItemType -eq 'Service').Count } else { $null }
                NASVolumes        = if ($collectNAS) { ($inventory.NAS | Where-Object ItemType -eq 'Volume').Count } else { $null }
            }

            $inventory.Summary = $summaryData

            # Return summary only if requested
            if ($Summary) {
                Write-Output $summaryData
            }
            else {
                Write-Output $inventory
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
}