Public/Get-EntraUserRiskReport.ps1
|
function Get-EntraUserRiskReport { <# .SYNOPSIS Reports on user risk levels, MFA registration status, and authentication methods. .DESCRIPTION Pulls all Entra ID users and evaluates their security posture: risk level from Identity Protection, MFA registration status, and registered authentication methods. Flags users with no MFA, high risk levels, or only legacy auth methods. .PARAMETER RiskLevelFilter Filter to a specific risk level. Default returns all users. .PARAMETER IncludeGuests Include guest/external users in the report. .EXAMPLE Get-EntraUserRiskReport -IncludeGuests .EXAMPLE Get-EntraUserRiskReport -RiskLevelFilter High #> [CmdletBinding()] param( [Parameter()] [ValidateSet('High', 'Medium', 'Low', 'None', 'All')] [string]$RiskLevelFilter = 'All', [Parameter()] [switch]$IncludeGuests ) begin { Test-GraphConnection $results = [System.Collections.Generic.List[PSObject]]::new() } process { # Get all users with relevant properties $graphParams = @{ Select = @( 'id', 'displayName', 'userPrincipalName', 'userType', 'accountEnabled', 'signInActivity', 'riskLevel', 'riskState' ) All = $true } $users = Get-MgUser @graphParams if (-not $IncludeGuests) { $users = $users | Where-Object { $_.UserType -eq 'Member' } } foreach ($user in $users) { # Get authentication methods for this user try { $authMethods = Get-MgUserAuthenticationMethod -UserId $user.Id -ErrorAction Stop $methodTypes = $authMethods | ForEach-Object { ($_.AdditionalProperties.'@odata.type' -replace '#microsoft.graph.', '') } $hasMfa = ($methodTypes | Where-Object { $_ -in @('microsoftAuthenticatorAuthenticationMethod', 'fido2AuthenticationMethod', 'windowsHelloForBusinessAuthenticationMethod', 'phoneAuthenticationMethod') }).Count -gt 0 } catch { $methodTypes = @('Unable to retrieve') $hasMfa = $false } $riskLevel = if ($user.RiskLevel) { $user.RiskLevel } else { 'none' } # Apply risk level filter if ($RiskLevelFilter -ne 'All' -and $riskLevel -ne $RiskLevelFilter.ToLower()) { continue } $lastSignIn = if ($user.SignInActivity.LastSignInDateTime) { $user.SignInActivity.LastSignInDateTime } else { 'Never' } $daysSinceSignIn = if ($lastSignIn -ne 'Never') { [math]::Round(((Get-Date) - [datetime]$lastSignIn).TotalDays) } else { -1 } $results.Add([PSCustomObject]@{ DisplayName = $user.DisplayName UPN = $user.UserPrincipalName UserType = $user.UserType Enabled = $user.AccountEnabled RiskLevel = $riskLevel RiskState = if ($user.RiskState) { $user.RiskState } else { 'none' } MFARegistered = $hasMfa AuthMethods = ($methodTypes -join ', ') LastSignIn = $lastSignIn DaysSinceLogin = $daysSinceSignIn Finding = $( $flags = @() if (-not $hasMfa) { $flags += 'NO MFA' } if ($riskLevel -eq 'high') { $flags += 'HIGH RISK' } if ($riskLevel -eq 'medium') { $flags += 'MEDIUM RISK' } if ($daysSinceSignIn -gt 90) { $flags += 'STALE (90+ days)' } if (-not $user.AccountEnabled) { $flags += 'DISABLED' } if ($flags.Count -gt 0) { $flags -join ' | ' } else { 'OK' } ) }) } } end { $atRisk = $results | Where-Object { $_.Finding -ne 'OK' } Write-Host " Users scanned: $($results.Count) | Findings: $($atRisk.Count)" -ForegroundColor Gray $results | Sort-Object Finding, DisplayName } } |