Src/Private/Get-AbrEntraIDSSPR.ps1
|
function Get-AbrEntraIDSSPR { [CmdletBinding()] param ([Parameter(Position=0,Mandatory)][string]$TenantId) begin { Write-PScriboMessage "Collecting SSPR configuration for tenant $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'SSPR' } process { Section -Style Heading2 'Self-Service Password Reset (SSPR)' { Paragraph "The following section documents the Self-Service Password Reset configuration and registration status for tenant $TenantId. SSPR reduces helpdesk burden and is required for user risk remediation workflows in Identity Protection." BlankLine try { Write-Host " - Retrieving SSPR configuration..." # SSPR policy is part of Authentication Methods Policy $SSPRPolicy = Invoke-MgGraphRequest -Method GET ` -Uri 'https://graph.microsoft.com/v1.0/policies/authenticationMethodsPolicy' ` -ErrorAction SilentlyContinue # Get SSPR registration from userRegistrationDetails (replaces deprecated credentialUserRegistrationDetails) # Requires Reports.Read.All -- falls back gracefully if not available $AllSSPRReg = @() try { $SSPRRegDetails = Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/v1.0/reports/authenticationMethods/userRegistrationDetails?`$top=999" ` -ErrorAction Stop $AllSSPRReg = if ($SSPRRegDetails -and $SSPRRegDetails.value) { $SSPRRegDetails.value } else { @() } while ($SSPRRegDetails.'@odata.nextLink') { $SSPRRegDetails = Invoke-MgGraphRequest -Method GET -Uri $SSPRRegDetails.'@odata.nextLink' -ErrorAction Stop $AllSSPRReg += $SSPRRegDetails.value } } catch { # Reports.Read.All not granted or endpoint unavailable -- SSPR registration counts will show as 0 Write-PScriboMessage -IsWarning "SSPR registration data unavailable (requires Reports.Read.All): $($_.Exception.Message)" $AllSSPRReg = @() } $TotalUsers = @($AllSSPRReg).Count # New endpoint uses isSsprRegistered/isSsprCapable/isSsprEnabled # Fallback: check both old and new field names $SSPREnabled = @($AllSSPRReg | Where-Object { $_.isSsprEnabled -or $_.isSsprRegistered }).Count $SSPRRegistered = @($AllSSPRReg | Where-Object { $_.isSsprRegistered }).Count $SSPRCapable = @($AllSSPRReg | Where-Object { $_.isSsprCapable -or $_.isSsprRegistered }).Count $SSPRPct = if ($TotalUsers -gt 0) { [math]::Round(($SSPRRegistered / $TotalUsers) * 100, 0) } else { 0 } $SSPRSumObj = [System.Collections.ArrayList]::new() $ssprInObj = [ordered] @{ 'Total Users Assessed' = $TotalUsers 'SSPR Enabled (Licensed)' = $SSPREnabled 'SSPR Registered' = $SSPRRegistered 'SSPR Capable (Enabled+Reg)' = $SSPRCapable 'SSPR Registration %' = "$SSPRPct%" 'Not SSPR Registered' = ($TotalUsers - $SSPRRegistered) } $SSPRSumObj.Add([pscustomobject]$ssprInObj) | Out-Null $null = (& { if ($HealthCheck.EntraID.MFA) { if ($SSPRPct -lt 50) { $null = ($SSPRSumObj | Set-Style -Style Critical | Out-Null) } elseif ($SSPRPct -lt 80) { $null = ($SSPRSumObj | Set-Style -Style Warning | Out-Null) } } }) $SSPRSumTableParams = @{ Name = "SSPR Registration Summary - $TenantId"; List = $true; ColumnWidths = 55, 45 } if ($Report.ShowTableCaptions) { $SSPRSumTableParams['Caption'] = "- $($SSPRSumTableParams.Name)" } $SSPRSumObj | Table @SSPRSumTableParams $null = ($script:ExcelSheets['SSPR Summary'] = $SSPRSumObj) # SSPR method configuration from Authentication Methods Policy if ($SSPRPolicy -and $SSPRPolicy.authenticationMethodConfigurations) { BlankLine $SSPRMethodObj = [System.Collections.ArrayList]::new() $SSPRMethods = @('email','mobilePhone','officePhone','securityQuestion','microsoftAuthenticatorPush','softwareOath') foreach ($Config in ($SSPRPolicy.authenticationMethodConfigurations | Where-Object { $_.id -in $SSPRMethods })) { $smInObj = [ordered] @{ 'Method' = $Config.id 'State' = $Config.state } $SSPRMethodObj.Add([pscustomobject]$smInObj) | Out-Null } if ($SSPRMethodObj.Count -gt 0) { $SSPRMethTableParams = @{ Name = "SSPR Available Methods - $TenantId"; List = $false; ColumnWidths = 60, 40 } if ($Report.ShowTableCaptions) { $SSPRMethTableParams['Caption'] = "- $($SSPRMethTableParams.Name)" } $SSPRMethodObj | Table @SSPRMethTableParams } } #region ACSC E8 + CIS SSPR Assessment BlankLine if ($script:IncludeACSCe8) { Paragraph "ACSC Essential Eight Maturity Level Assessment -- SSPR:" BlankLine #region ACSC E8 Assessment (definitions from Src/Compliance/ACSC.E8.json) $_ComplianceVars = @{ 'SSPREnabled' = $SSPREnabled 'SSPRPct' = $SSPRPct 'SSPRRegistered' = $SSPRRegistered 'TotalUsers' = $TotalUsers } $E8SSPRChecks = Build-AbrComplianceChecks ` -Definitions (Get-AbrE8Checks -Section 'SSPR') ` -Framework E8 ` -CallerVariables $_ComplianceVars New-AbrE8AssessmentTable -Checks $E8SSPRChecks -Name 'SSPR' -TenantId $TenantId # Consolidated into ACSC E8 Assessment sheet if ($E8SSPRChecks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8SSPRChecks | Select-Object @{N='Section';E={'SSPR'}}, ML, Control, Status, Detail ))) } #endregion } if ($script:IncludeCISBaseline) { BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- SSPR:" BlankLine #region CIS Assessment (definitions from Src/Compliance/CIS.M365.json) $CISSSPRChecks = Build-AbrComplianceChecks ` -Definitions (Get-AbrCISChecks -Section 'SSPR') ` -Framework CIS ` -CallerVariables $_ComplianceVars New-AbrCISAssessmentTable -Checks $CISSSPRChecks -Name 'SSPR' -TenantId $TenantId # Consolidated into CIS Assessment sheet if ($CISSSPRChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISSSPRChecks | Select-Object @{N='Section';E={'SSPR'}}, CISControl, Level, Status, Detail ))) } #endregion } #endregion } catch { Write-AbrSectionError -Section 'SSPR' -Message "$($_.Exception.Message)" } } # end Section SSPR } end { Show-AbrDebugExecutionTime -End -TitleMessage 'SSPR' } } |