Public/Get-GkSecureScore.ps1

function Get-GkSecureScore {
    <#
    .SYNOPSIS
        Report the tenant's latest Microsoft Secure Score, or the per-control breakdown.

    .DESCRIPTION
        Reads GET /security/secureScores and returns the most recent score with the current/max
        values and a computed percentage. With -IncludeControls it instead returns one row per
        control in that score (name, category, score, status) so you can see where points are lost.

        Requires the SecurityEvents.Read.All scope. Secure Score is fed by Microsoft 365 / Defender
        products; no extra Entra license is needed to read it.

    .PARAMETER IncludeControls
        Return the per-control breakdown of the latest score instead of the summary.

    .PARAMETER AsReport
        Add a ReportGeneratedUtc column.

    .EXAMPLE
        Get-GkSecureScore

        The latest overall Secure Score and percentage.

    .EXAMPLE
        Get-GkSecureScore -IncludeControls | Sort-Object Score | Select-Object -First 15

        The 15 controls contributing the fewest points (improvement opportunities).

    .EXAMPLE
        Get-GkSecureScore -AsReport | Export-Csv .\secure-score.csv -NoTypeInformation

    .OUTPUTS
        PSGraphKit.SecureScore or PSGraphKit.SecureScoreControl
    #>

    [CmdletBinding()]
    [OutputType('PSGraphKit.SecureScore', 'PSGraphKit.SecureScoreControl')]
    param(
        [switch] $IncludeControls,
        [switch] $AsReport
    )

    begin {
        Test-GkConnection -FunctionName 'Get-GkSecureScore' | Out-Null
        $now = [datetime]::UtcNow
    }

    process {
        $latest = @(Invoke-GkGraphRequest -Uri '/security/secureScores?$top=1' -CallerFunction 'Get-GkSecureScore') | Select-Object -First 1
        if (-not $latest) { Write-Warning 'No Secure Score data was returned for this tenant.'; return }

        if ($IncludeControls) {
            foreach ($c in @(Get-GkDictValue $latest 'controlScores')) {
                $obj = [ordered]@{
                    PSTypeName  = 'PSGraphKit.SecureScoreControl'
                    ControlName = [string](Get-GkDictValue $c 'controlName')
                    Category    = [string](Get-GkDictValue $c 'controlCategory')
                    Score       = [double](Get-GkDictValue $c 'score')
                    Description = [string](Get-GkDictValue $c 'description')
                }
                if ($AsReport) { $obj['ReportGeneratedUtc'] = $now }
                [pscustomobject]$obj
            }
            return
        }

        $current = [double](Get-GkDictValue $latest 'currentScore')
        $max     = [double](Get-GkDictValue $latest 'maxScore')
        $obj = [ordered]@{
            PSTypeName        = 'PSGraphKit.SecureScore'
            CurrentScore      = $current
            MaxScore          = $max
            Percentage        = if ($max -gt 0) { [math]::Round(($current / $max) * 100, 1) } else { $null }
            ActiveUserCount   = [int](Get-GkDictValue $latest 'activeUserCount')
            LicensedUserCount = [int](Get-GkDictValue $latest 'licensedUserCount')
            ControlCount      = @(Get-GkDictValue $latest 'controlScores').Count
            ScoreDate         = ConvertTo-GkDateTime (Get-GkDictValue $latest 'createdDateTime')
        }
        if ($AsReport) { $obj['ReportGeneratedUtc'] = $now }
        [pscustomobject]$obj
    }
}