SHELL/5.1.2.1.ps1

$CheckId = "5.1.2.1"
$Title = "Ensure 'Per-user MFA' is disabled"
$Level = "L1"
$BenchmarkType = "Automated"
$AuditCommands = @(
    "Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/users?\$filter=perUserMfaState eq ''enforced'' or perUserMfaState eq ''enabled'''",
    "Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/beta/users/{id}/authentication/requirements'"
)

try {
    $PerUserMfaUsers = @()
    $BulkWorked = $false
    $SampleMode = $false

    try {
        $PerUserUri = "https://graph.microsoft.com/beta/users?`$count=true&`$filter=perUserMfaState eq 'enforced' or perUserMfaState eq 'enabled'&`$select=id,displayName,userPrincipalName"
        $PerUserResponse = Invoke-MgGraphRequest -Uri $PerUserUri -Method GET -Headers @{ "ConsistencyLevel" = "eventual" } -ErrorAction Stop
        $PerUserMfaUsers = @($PerUserResponse.value)

        while ($PerUserResponse.'@odata.nextLink') {
            $PerUserResponse = Invoke-MgGraphRequest -Uri $PerUserResponse.'@odata.nextLink' -Method GET -Headers @{ "ConsistencyLevel" = "eventual" } -ErrorAction Stop
            $PerUserMfaUsers += @($PerUserResponse.value)
        }

        $BulkWorked = $true
    }
    catch {
        try {
            $SampleMode = $true
            $UsersUri = "https://graph.microsoft.com/v1.0/users?`$select=id,userPrincipalName&`$top=50"
            $UsersResponse = Invoke-MgGraphRequest -Uri $UsersUri -Method GET -ErrorAction Stop
            $SampleUsers = @($UsersResponse.value)

            foreach ($User in $SampleUsers) {
                try {
                    $Req = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/users/$($User.id)/authentication/requirements" -ErrorAction Stop
                    if ([string]$Req.perUserMfaState -in @("enforced", "enabled")) {
                        $PerUserMfaUsers += [pscustomobject]@{
                            id = [string]$User.id
                            userPrincipalName = [string]$User.userPrincipalName
                            perUserMfaState = [string]$Req.perUserMfaState
                        }
                    }
                }
                catch {
                    continue
                }
            }

            $BulkWorked = $true
        }
        catch {
            $BulkWorked = $false
        }
    }

    if ($BulkWorked) {
        $Pass = (@($PerUserMfaUsers).Count -eq 0)
        $Status = if ($Pass) { "PASS" } else { "FAIL" }
        $Sample = @($PerUserMfaUsers | Select-Object -First 5 | ForEach-Object { $_.userPrincipalName }) -join ", "

        [pscustomobject]@{
            CheckId = $CheckId
            Title = $Title
            Level = $Level
            BenchmarkType = $BenchmarkType
            Status = $Status
            Pass = $Pass
            Evidence = [pscustomobject]@{
                AuditCommands = $AuditCommands
                DetectionMode = if ($SampleMode) { "SampledPerUserRequirements" } else { "BulkFilter" }
                UserCountWithPerUserMfa = @($PerUserMfaUsers).Count
                UsersWithPerUserMfaPreview = @($PerUserMfaUsers | Select-Object -First 50)
                SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
            }
            Error = if ($Pass) { $null } else { "$(@($PerUserMfaUsers).Count) users have per-user MFA enabled (example: $Sample)." }
            Timestamp = Get-Date
        }
        return
    }

    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = "MANUAL_REVIEW"
        Pass = $null
        Evidence = [pscustomobject]@{
            AuditCommands = $AuditCommands
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = "Per-user MFA state could not be checked via Graph API. Verify manually in Entra admin center."
        Timestamp = Get-Date
    }
}
catch {
    [pscustomobject]@{
        CheckId = $CheckId
        Title = $Title
        Level = $Level
        BenchmarkType = $BenchmarkType
        Status = "ERROR"
        Pass = $null
        Evidence = [pscustomobject]@{
            AuditCommands = $AuditCommands
            SourceDocument = "CIS_Microsoft_365_Foundations_Benchmark_v6.0.1"
        }
        Error = $_.Exception.Message
        Timestamp = Get-Date
    }
}