Public/Get-MsecEntraConditionalAccessStats.ps1
|
function Get-MsecEntraConditionalAccessStats { <# .SYNOPSIS Aggregated Conditional Access insights over the last N days - the data behind the "Conditional Access insights and reporting" workbook in the Entra portal, in a single summary row. .DESCRIPTION Internally calls Get-MsecEntraConditionalAccessSignInLog -Days $Days and aggregates the sign-in events. Returns one PSCustomObject covering: - Volume: TotalSignIns, UniqueUsers - CA mix: CaSuccess / CaFailure / CaNotApplied (mutually exclusive, sum to TotalSignIns) + their percentages - Risk: HighRiskSignIns, MediumRiskSignIns (from Identity Protection) - Report-only impact: ReportOnlyWouldBlock - sign-ins that a report-only policy WOULD HAVE blocked if it were enforced. The single most useful metric for "is it safe to flip this report-only policy to enabled?" - TopFailingPolicies: top-5 policies by failure count (nested array of {Name, Count} objects). This function is a thin aggregation over the raw sign-in log. It pulls every sign-in event for the window once, so the API cost is the same as a single Get-MsecEntraConditionalAccessSignInLog call - heavy in busy tenants. The default -Days 7 matches the portal workbook's default view and keeps the cost bounded. Permission requirement is inherited from Get-MsecEntraConditionalAccessSignInLog (AuditLog.Read.All). .PARAMETER Days Window size in days. Default 7, max 30 (Graph signIn retention cap). .EXAMPLE Get-MsecEntraConditionalAccessStats # last 7 days .EXAMPLE Get-MsecEntraConditionalAccessStats -Days 30 | Format-List .EXAMPLE # Slot it into the bi-weekly archive snapshot: $snapshot = [pscustomobject]@{ CapturedAtUtc = (Get-Date).ToUniversalTime().ToString('u') SecureScore = Get-MsecSecureScore -Top 1 EmailStats = Get-MsecDefenderEmailStats -Days 30 IncidentStats = Get-MsecDefenderIncidentStats -Days 30 CaStats = Get-MsecEntraConditionalAccessStats -Days 7 } .OUTPUTS PSCustomObject with StartDate, EndDate, volume + CA-outcome + risk + report-only + TopFailingPolicies columns. #> [CmdletBinding()] param( [Parameter()] [ValidateRange(1, 30)] [int] $Days = 7 ) # No Assert-MsecSession here - the inner call asserts, and double-checking # would just give a misleading stack trace. $log = @(Get-MsecEntraConditionalAccessSignInLog -Days $Days) $total = $log.Count # Closure for percent-of-total math. Returns 0.0 (not null) when total is 0 # so the row schema stays consistent (no mixed-type columns in the archive). $pct = { param($n) if ($total -gt 0) { [math]::Round($n / $total * 100, 2) } else { 0.0 } } # CA outcomes are mutually exclusive: every sign-in has exactly one # ConditionalAccessStatus value. Sum should equal $total (modulo 'unknown' # for very rare cases - we count what's there). $caSuccess = ($log | Where-Object ConditionalAccessStatus -eq 'success').Count $caFailure = ($log | Where-Object ConditionalAccessStatus -eq 'failure').Count $caNotApplied = ($log | Where-Object ConditionalAccessStatus -eq 'notApplied').Count # Identity Protection risk levels (only populated when IP is licensed and # actually evaluating sign-ins). $highRisk = ($log | Where-Object RiskLevelDuringSignIn -eq 'high').Count $mediumRisk = ($log | Where-Object RiskLevelDuringSignIn -eq 'medium').Count # Per-applied-policy aggregations: flatten the nested AppliedPolicies array # so we can group across all events. $applied = $log | ForEach-Object { $_.AppliedPolicies } # Report-only WOULD-block: the headline "is this safe to turn on?" metric. $reportOnlyWouldBlock = ($applied | Where-Object result -in 'reportOnlyFailure', 'reportOnlyInterrupted').Count # Top-5 failing policies (by failed-application count). Returned as an # explicit array so the column shape is stable even when there are <5. $topFailing = @( $applied | Where-Object result -eq 'failure' | Group-Object displayName | Sort-Object Count -Descending | Select-Object -First 5 @{n = 'Name'; e = { $_.Name } }, @{n = 'Count'; e = { $_.Count } } ) $now = (Get-Date).ToUniversalTime() $startUtc = $now.AddDays(-$Days) [PSCustomObject]@{ StartDate = $startUtc.Date EndDate = $now.Date TotalSignIns = $total UniqueUsers = ($log.UserPrincipalName | Sort-Object -Unique).Count CaSuccess = $caSuccess CaFailure = $caFailure CaNotApplied = $caNotApplied CaSuccessPercent = & $pct $caSuccess CaFailurePercent = & $pct $caFailure HighRiskSignIns = $highRisk MediumRiskSignIns = $mediumRisk ReportOnlyWouldBlock = $reportOnlyWouldBlock TopFailingPolicies = $topFailing } } |