Private/CBS/New-SilkTCOCBSArrayData.ps1

<#
    .SYNOPSIS
    Pulls provisioned capacity, data reduction and volume detail from a CBS array.

    .DESCRIPTION
    The Purity API is the only place the reduction ratio lives, and on CBS that ratio is
    directly upstream of the azure blob bill - 100 TiB provisioned at 4:1 is 25 TiB of blob
    actually being paid for. That's the number the whole costing comparison turns on.
#>


function New-SilkTCOCBSArrayData {
    param(
        [Parameter(Mandatory)]
        $arrayConnection,
        [Parameter()]
        [switch] $includeHosts,
        [Parameter()]
        [switch] $includeProtectionGroups
    )

    $conn = $arrayConnection

    $arrayInfo = Get-Pfa2Array -Array $conn -ErrorAction Stop
    $arraySpace = $null
    try {
        $arraySpace = Get-Pfa2ArraySpace -Array $conn -ErrorAction Stop
    } catch {
        Write-Warning "Could not read array space: $($_.Exception.Message)"
    }

    # Get-Pfa2Array already carries space on most purity builds, fall back to it
    $space = if ($arraySpace) { $arraySpace.Space } else { $arrayInfo.Space }
    $capacity = if ($arraySpace -and $arraySpace.Capacity) { $arraySpace.Capacity } else { $arrayInfo.Capacity }

    $result = New-Object psobject

    $a = New-Object psobject
    $a | Add-Member -MemberType NoteProperty -Name ArrayName -Value $arrayInfo.Name
    $a | Add-Member -MemberType NoteProperty -Name ArrayId -Value $arrayInfo.Id
    $a | Add-Member -MemberType NoteProperty -Name PurityVersion -Value $arrayInfo.Version
    $a | Add-Member -MemberType NoteProperty -Name Model -Value $arrayInfo.Model
    $a | Add-Member -MemberType NoteProperty -Name UsableCapacityTiB -Value $(if ($capacity) { [Math]::Round($capacity / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name TotalProvisionedTiB -Value $(if ($space.TotalProvisioned) { [Math]::Round($space.TotalProvisioned / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name TotalPhysicalTiB -Value $(if ($space.TotalPhysical) { [Math]::Round($space.TotalPhysical / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name UniqueTiB -Value $(if ($space.Unique) { [Math]::Round($space.Unique / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name SnapshotsTiB -Value $(if ($space.Snapshots) { [Math]::Round($space.Snapshots / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name SharedTiB -Value $(if ($space.Shared) { [Math]::Round($space.Shared / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name SystemTiB -Value $(if ($space.System) { [Math]::Round($space.System / 1TB, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name DataReduction -Value $(if ($space.DataReduction) { [Math]::Round($space.DataReduction, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name TotalReduction -Value $(if ($space.TotalReduction) { [Math]::Round($space.TotalReduction, 2) } else { $null })
    $a | Add-Member -MemberType NoteProperty -Name ThinProvisioning -Value $(if ($space.ThinProvisioning) { [Math]::Round($space.ThinProvisioning, 2) } else { $null })

    $result | Add-Member -MemberType NoteProperty -Name Array -Value $a

    # --- volumes + per volume space ---
    $volDetail = @()
    try {
        $volumes = @(Get-Pfa2Volume -Array $conn -ErrorAction Stop)
        $volSpaces = @{}
        try {
            foreach ($vs in @(Get-Pfa2VolumeSpace -Array $conn -ErrorAction Stop)) {
                $volSpaces[$vs.Name] = $vs
            }
        } catch {
            Write-Verbose "-> per volume space not available" -Verbose
        }

        foreach ($v in $volumes) {
            $vs = $volSpaces[$v.Name]
            $vspace = if ($vs) { $vs.Space } else { $v.Space }

            $o = New-Object psobject
            $o | Add-Member -MemberType NoteProperty -Name VolumeName -Value $v.Name
            $o | Add-Member -MemberType NoteProperty -Name Serial -Value $v.Serial
            $o | Add-Member -MemberType NoteProperty -Name Destroyed -Value $v.Destroyed
            $o | Add-Member -MemberType NoteProperty -Name ProvisionedGiB -Value $(if ($v.Provisioned) { [Math]::Round($v.Provisioned / 1GB, 2) } else { $null })
            $o | Add-Member -MemberType NoteProperty -Name UniqueGiB -Value $(if ($vspace.Unique) { [Math]::Round($vspace.Unique / 1GB, 2) } else { $null })
            $o | Add-Member -MemberType NoteProperty -Name SnapshotsGiB -Value $(if ($vspace.Snapshots) { [Math]::Round($vspace.Snapshots / 1GB, 2) } else { $null })
            $o | Add-Member -MemberType NoteProperty -Name TotalPhysicalGiB -Value $(if ($vspace.TotalPhysical) { [Math]::Round($vspace.TotalPhysical / 1GB, 2) } else { $null })
            $o | Add-Member -MemberType NoteProperty -Name DataReduction -Value $(if ($vspace.DataReduction) { [Math]::Round($vspace.DataReduction, 2) } else { $null })
            $volDetail += $o
        }
    } catch {
        Write-Warning "Could not list volumes: $($_.Exception.Message)"
    }
    $result | Add-Member -MemberType NoteProperty -Name Volumes -Value @($volDetail)

    # --- host mappings. lets you tie cbs volumes back to vms in the ec2/azure reports ---
    $hostDetail = @()
    if ($includeHosts) {
        try {
            $conns = @(Get-Pfa2Connection -Array $conn -ErrorAction SilentlyContinue)
            foreach ($h in @(Get-Pfa2Host -Array $conn -ErrorAction Stop)) {
                $mapped = @($conns | Where-Object { $_.Host.Name -eq $h.Name })
                $o = New-Object psobject
                $o | Add-Member -MemberType NoteProperty -Name HostName -Value $h.Name
                $o | Add-Member -MemberType NoteProperty -Name HostGroup -Value $h.HostGroup.Name
                $o | Add-Member -MemberType NoteProperty -Name ConnectionCount -Value $mapped.Count
                $o | Add-Member -MemberType NoteProperty -Name MappedVolumes -Value (($mapped.Volume.Name) -join '; ')
                $hostDetail += $o
            }
        } catch {
            Write-Verbose "-> host detail not available" -Verbose
        }
    }
    $result | Add-Member -MemberType NoteProperty -Name Hosts -Value @($hostDetail)

    # --- protection groups / replication footprint ---
    $pgDetail = @()
    if ($includeProtectionGroups) {
        try {
            foreach ($pg in @(Get-Pfa2ProtectionGroup -Array $conn -ErrorAction Stop)) {
                $o = New-Object psobject
                $o | Add-Member -MemberType NoteProperty -Name GroupName -Value $pg.Name
                $o | Add-Member -MemberType NoteProperty -Name SnapshotsGiB -Value $(if ($pg.Space.Snapshots) { [Math]::Round($pg.Space.Snapshots / 1GB, 2) } else { $null })
                $o | Add-Member -MemberType NoteProperty -Name TotalPhysicalGiB -Value $(if ($pg.Space.TotalPhysical) { [Math]::Round($pg.Space.TotalPhysical / 1GB, 2) } else { $null })
                $o | Add-Member -MemberType NoteProperty -Name VolumeCount -Value $pg.VolumeCount
                $o | Add-Member -MemberType NoteProperty -Name TargetCount -Value $pg.TargetCount
                $pgDetail += $o
            }
        } catch {
            Write-Verbose "-> protection group detail not available" -Verbose
        }
    }
    $result | Add-Member -MemberType NoteProperty -Name ProtectionGroups -Value @($pgDetail)

    return $result
}