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 { # secureScores is returned newest-first and carries a nextLink, so letting Invoke-GkGraphRequest # auto-paginate would walk ~90 days of history just to use the latest day. Read only the first # page (-Raw returns the untouched first-page body) and take the latest item from it. # NB: build $values with a plain if-STATEMENT, not an if-EXPRESSION: an if-expression unrolls a # single-element 'value' array back to a scalar, so $values[0] would then index a hashtable and # return $null. A direct @() assignment keeps it a real array. $body = Invoke-GkGraphRequest -Uri '/security/secureScores?$top=1' -Raw -CallerFunction 'Get-GkSecureScore' $values = @() if ($body -is [System.Collections.IDictionary]) { $values = @($body['value']) } if ($values.Count -eq 0) { Write-Warning 'No Secure Score data was returned for this tenant.'; return } $latest = $values[0] 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 } } |