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
    }
}