Src/Private/Get-AbrSPTenantOverview.ps1

function Get-AbrSPTenantOverview {
    <#
    .SYNOPSIS
    Documents the SharePoint Online tenant overview, licensing, and top-level settings.
    .NOTES
        Version: 0.1.4
        Author: Pai Wei Sing

    Uses $script:CachedOrg and $script:CachedSkus which are pre-fetched in
    Connect-SharePointSession BEFORE PnP.PowerShell connects.
    This avoids the Microsoft.Graph.Core assembly conflict caused by PnP v3.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting SharePoint Tenant Overview for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Tenant Overview'
    }

    process {
        Section -Style Heading1 'Tenant Overview' {
            Paragraph "The following section provides a summary of the SharePoint Online and OneDrive for Business configuration for tenant $TenantId."
            BlankLine

            #region Tenant Identity (uses pre-fetched cache)
            try {
                $Org = $script:CachedOrg
                if (-not $Org) { throw "Organisation data not available (Graph pre-fetch may have failed)." }

                $TenantObj     = [System.Collections.ArrayList]::new()
                $DefaultDomain = ($Org.verifiedDomains | Where-Object { $_.isDefault }).name
                $Domains       = ($Org.verifiedDomains | ForEach-Object { $_.name }) -join ', '

                $tenantInObj = [ordered] @{
                    'Tenant Name'        = $Org.displayName
                    'Tenant ID'          = $Org.id
                    'Default Domain'     = $DefaultDomain
                    'Verified Domains'   = $Domains
                    'Country / Region'   = if ($Org.countryLetterCode) { $Org.countryLetterCode } else { '--' }
                    'Preferred Language' = if ($Org.preferredLanguage)  { $Org.preferredLanguage  } else { '--' }
                    'Created Date'       = if ($Org.createdDateTime)    { ([datetime]$Org.createdDateTime).ToString('yyyy-MM-dd') } else { '--' }
                }
                $TenantObj.Add([pscustomobject](ConvertTo-HashToYN $tenantInObj)) | Out-Null

                $TableParams = @{ Name = "Tenant Details - $TenantId"; List = $true; ColumnWidths = 40, 60 }
                if ($Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" }
                $TenantObj | Table @TableParams
            } catch {
                Write-AbrSectionError -Section 'Tenant Overview' -Message "$($_.Exception.Message)"
            }
            #endregion

            #region SharePoint Tenant Settings (via PnP)
            try {
                $SPTenantObj = [System.Collections.ArrayList]::new()
                $spInObj     = $null

                if ($script:PnPAvailable) {
                    $SPTenant = Get-PnPTenant -ErrorAction Stop

                    # Guard all values that may be null from PnP (especially boolean fields
                    # that return $null instead of $false when not explicitly configured)
                    $StorageQuotaGB     = if ($SPTenant.StorageQuota -gt 0)     { [math]::Round($SPTenant.StorageQuota / 1024, 0)     } else { 'Auto-managed' }
                    $StorageQuotaUsedGB = if ($SPTenant.StorageQuotaUsed -gt 0) { [math]::Round($SPTenant.StorageQuotaUsed / 1024, 2) } else { 'See individual sites' }
                    $RecycleBin         = if ($null -ne $SPTenant.RecycleBinEnabled)             { $SPTenant.RecycleBinEnabled             } else { $true }   # enabled by default in M365
                    $PublicCDN          = if ($null -ne $SPTenant.PublicCdnEnabled)              { $SPTenant.PublicCdnEnabled              } else { $false }
                    $SelfSvcSite        = if ($null -ne $SPTenant.SelfServiceSiteCreationEnabled){ $SPTenant.SelfServiceSiteCreationEnabled } else { 'Not configured' }
                    $LegacyAuth         = if ($null -ne $SPTenant.LegacyAuthProtocolsEnabled)    { $SPTenant.LegacyAuthProtocolsEnabled    } else { $false }

                    $spInObj  = [ordered] @{
                        'SharePoint Admin Center URL'         = $script:TenantAdminUrl
                        'Root SharePoint URL'                 = $script:TenantRootUrl
                        'Storage Quota (GB)'                  = $StorageQuotaGB
                        'Storage Quota Used (GB)'             = $StorageQuotaUsedGB
                        'Recycle Bin Enabled'                 = $RecycleBin
                        'Public CDN Enabled'                  = $PublicCDN
                        'Self-Service Site Creation Enabled'  = $SelfSvcSite
                        'Legacy Auth Protocols Enabled'       = $LegacyAuth
                    }
                } else {
                    $spInObj = [ordered] @{
                        'SharePoint Admin Center URL' = $script:TenantAdminUrl
                        'Root SharePoint URL'         = $script:TenantRootUrl
                        'Note'                        = 'PnP.PowerShell not available -- detailed settings require a PnP connection'
                    }
                }

                if ($spInObj) {
                    $SPTenantObj.Add([pscustomobject](ConvertTo-HashToYN $spInObj)) | Out-Null
                    $TableParams2 = @{ Name = "SharePoint Tenant Settings - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                    if ($Report.ShowTableCaptions) { $TableParams2['Caption'] = "- $($TableParams2.Name)" }
                    $SPTenantObj | Table @TableParams2
                }
            } catch {
                Write-AbrSectionError -Section 'SharePoint Tenant Settings' -Message "$($_.Exception.Message)"
            }
            #endregion

            #region Licence Summary (uses pre-fetched cache)
            try {
                $SkuList = $script:CachedSkus
                if (-not $SkuList) { throw "Licence data not available (Graph pre-fetch may have failed)." }

                $SPSkuKeywords = @('SPE_E','M365','SHAREPOINTENTERPRISE','SHAREPOINTSTANDARD',
                                   'ONEDRIVE','ENTERPRISEPREMIUM','BUSINESSPREMIUM','BUSINESS_ESSENTIALS',
                                   'ENTERPRISEPACK','FRONTLINE','EDU')

                $RelevantSkus = $SkuList | Where-Object {
                    $sku = $_.skuPartNumber
                    $SPSkuKeywords | Where-Object { $sku -like "*$_*" }
                } | Sort-Object skuPartNumber

                if ($RelevantSkus) {
                    $LicObj = [System.Collections.ArrayList]::new()
                    foreach ($Sku in $RelevantSkus) {
                        $licInObj = [ordered] @{
                            'SKU / License'   = $Sku.skuPartNumber
                            'Status'          = $Sku.capabilityStatus
                            'Total Units'     = $Sku.prepaidUnits.enabled
                            'Assigned Units'  = $Sku.consumedUnits
                            'Available Units' = ($Sku.prepaidUnits.enabled - $Sku.consumedUnits)
                        }
                        $LicObj.Add([pscustomobject]$licInObj) | Out-Null
                    }
                    $LicTableParams = @{ Name = "Licence Summary (M365/SharePoint) - $TenantId"; ColumnWidths = 38, 14, 14, 17, 17 }
                    if ($Report.ShowTableCaptions) { $LicTableParams['Caption'] = "- $($LicTableParams.Name)" }
                    $LicObj | Table @LicTableParams
                    $script:ExcelSheets['Licences'] = $LicObj
                }
            } catch {
                Write-AbrSectionError -Section 'Licence Summary' -Message "$($_.Exception.Message)"
            }
            #endregion
        }
    }

    end {
        Show-AbrDebugExecutionTime -End -TitleMessage 'Tenant Overview'
    }
}