Src/Private/Get-AbrEntraIDConditionalAccess.ps1

function Get-AbrEntraIDConditionalAccess {
    <#
    .SYNOPSIS
    Documents the Conditional Access policies configured in Entra ID.
    .DESCRIPTION
        Collects and reports on:
          - All Conditional Access policies (name, state, users, apps, grant controls)
          - Named Locations (IP ranges, countries)
          - Policy summary statistics
    .NOTES
        Version: 0.1.21
        Author: Pai Wei Sing
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting Entra ID Conditional Access policies for tenant $TenantId." 
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Conditional Access'
    }

    process {
        #region Conditional Access
        # Section{} created unconditionally so catch{} always writes inside it
        Section -Style Heading2 'Conditional Access' {
            Paragraph "The following section documents the Conditional Access policies configured in tenant $TenantId."
            BlankLine
            if ($script:Charts['CA']) {
                Image -Text 'CA Policy State' -Base64 $script:Charts['CA'] -Percent 65 -Align Center
                Paragraph "Figure: Conditional Access Policy State -- $($script:ChartData['EnabledPolicies']) enabled, $($script:ChartData['ReportOnlyPolicies']) report-only, $($script:ChartData['DisabledPolicies']) disabled"
                BlankLine
            }

            try {
                Write-Host " - Retrieving Conditional Access policies..."
                $CAPolicies = Get-MgIdentityConditionalAccessPolicy -All -ErrorAction Stop

                if ($CAPolicies) {

                    #region CA Policy Summary
                    $EnabledPolicies   = @($CAPolicies | Where-Object { $_.State -eq 'enabled' }).Count
                    $ReportOnlyPolicies= @($CAPolicies | Where-Object { $_.State -eq 'enabledForReportingButNotEnforced' }).Count
                    $DisabledPolicies  = @($CAPolicies | Where-Object { $_.State -eq 'disabled' }).Count
                    # Store for chart caption at section top
                    $script:ChartData['EnabledPolicies']    = $EnabledPolicies
                    $script:ChartData['ReportOnlyPolicies'] = $ReportOnlyPolicies
                    $script:ChartData['DisabledPolicies']   = $DisabledPolicies
                    # Generate chart (outside PScribo pipeline)
                    try {
                        if (Get-Command New-AbrCAStateDonut -ErrorAction SilentlyContinue) {
                            $script:Charts['CA'] = New-AbrCAStateDonut -Enabled ([int]$EnabledPolicies) -ReportOnly ([int]$ReportOnlyPolicies) -Disabled ([int]$DisabledPolicies) -TenantId $TenantId
                        }
                    } catch { Write-AbrDebugLog "CA chart failed: $($_.Exception.Message)" 'WARN' 'CHART' }

                    $CASumObj = [System.Collections.ArrayList]::new()
                    $caSumInObj = [ordered] @{
                        'Total Policies'          = @($CAPolicies).Count
                        'Enabled'                 = $EnabledPolicies
                        'Report-Only'             = $ReportOnlyPolicies
                        'Disabled'                = $DisabledPolicies
                    }
                    $CASumObj.Add([pscustomobject]$caSumInObj) | Out-Null

                    $null = (& {
                        if ($HealthCheck.EntraID.ConditionalAccess) {
                            $null = ($CASumObj | Where-Object { [int]$_.Enabled -eq 0 }          | Set-Style -Style Critical | Out-Null)
                            $null = ($CASumObj | Where-Object { [int]$_.'Report-Only' -gt 0 }     | Set-Style -Style Warning  | Out-Null)
                        }
                    })

                    $CASumTableParams = @{ Name = "Conditional Access Policy Summary - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                    if ($Report.ShowTableCaptions) { $CASumTableParams['Caption'] = "- $($CASumTableParams.Name)" }
                    $CASumObj | Table @CASumTableParams

                    #endregion # chart rendered at section top
                    #endregion

                    #region CA Policy Table
                    $CAObj = [System.Collections.ArrayList]::new()
                    foreach ($Policy in ($CAPolicies | Sort-Object DisplayName)) {
                        try {
                            # Resolve users/groups included
                            $IncludeUsers = $Policy.Conditions.Users.IncludeUsers
                            $IncludeGroups= $Policy.Conditions.Users.IncludeGroups
                            $ExcludeUsers = $Policy.Conditions.Users.ExcludeUsers

                            $UsersScope = if ($IncludeUsers -contains 'All') { 'All Users' }
                                          elseif ($IncludeUsers -contains 'GuestsOrExternalUsers') { 'Guests / External' }
                                          elseif ($IncludeGroups) { "$(@($IncludeGroups).Count) group(s)" }
                                          elseif ($IncludeUsers)  { "$(@($IncludeUsers).Count) user(s)" }
                                          else { '--' }

                            $ExclScope  = if ($ExcludeUsers -contains 'GuestsOrExternalUsers') { 'Guests; ' } else { '' }
                            $ExclGroups = $Policy.Conditions.Users.ExcludeGroups
                            $ExclScope += if ($ExclGroups) { "$(@($ExclGroups).Count) group(s)" } else { 'None' }

                            # Resolve apps
                            $IncludeApps = $Policy.Conditions.Applications.IncludeApplications
                            $AppsScope   = if ($IncludeApps -contains 'All') { 'All Cloud Apps' }
                                           elseif ($IncludeApps -contains 'Office365') { 'Office 365' }
                                           elseif ($IncludeApps) { "$(@($IncludeApps).Count) app(s)" }
                                           else { '--' }

                            # Grant controls
                            $GrantCtrl     = $Policy.GrantControls
                            $BuiltInCtrl   = if ($GrantCtrl.BuiltInControls) { $GrantCtrl.BuiltInControls -join ', ' } else { '--' }

                            # Session controls
                            $SessionCtrl   = $Policy.SessionControls
                            $HasSession    = ($SessionCtrl.SignInFrequency -or $SessionCtrl.PersistentBrowser -or $SessionCtrl.ApplicationEnforcedRestrictions)

                            # Auto-derive Gap/Risk notes
                            $GapRisk = [System.Collections.ArrayList]::new()
                            if ($Policy.State -eq 'enabledForReportingButNotEnforced') { $null = $GapRisk.Add('Not enforced -- report-only') }
                            if ($Policy.State -eq 'disabled')                           { $null = $GapRisk.Add('Policy disabled -- no protection') }
                            if ($Policy.Conditions.Users.ExcludeGroups.Count -gt 3)     { $null = $GapRisk.Add("Large exclusion ($($Policy.Conditions.Users.ExcludeGroups.Count) groups) -- review") }
                            if ($Policy.SessionControls.SignInFrequency -and
                                $Policy.SessionControls.SignInFrequency.Value -ge 7 -and
                                $Policy.Conditions.Users.IncludeRoles)                  { $null = $GapRisk.Add("Admin session >=$($Policy.SessionControls.SignInFrequency.Value)d -- reduce to 1-4h") }
                            if ($Policy.Conditions.Users.IncludeUsers -contains 'All' -and
                                ($Policy.Conditions.Users.ExcludeGroups.Count -gt 0 -or
                                 $Policy.Conditions.Users.ExcludeUsers.Count -gt 2))    { $null = $GapRisk.Add('Exclusions may leave users unprotected') }
                            if ($BuiltInCtrl -notcontains 'block' -and
                                $BuiltInCtrl -notcontains 'mfa' -and
                                $BuiltInCtrl -notcontains 'compliantDevice')             { $null = $GapRisk.Add('Weak grant control -- no MFA/compliance/block') }
                            $GapRiskStr = if ($GapRisk.Count -gt 0) { $GapRisk -join '; ' } else { '--' }

                            $caInObj = [ordered] @{
                                'Policy Name'     = $Policy.DisplayName
                                'State'           = switch ($Policy.State) {
                                    'enabled'                             { 'Enabled' }
                                    'disabled'                            { 'Disabled' }
                                    'enabledForReportingButNotEnforced'   { 'Report-Only' }
                                    default                               { $Policy.State }
                                }
                                'Users / Groups'  = $UsersScope
                                'Exclusions'      = $ExclScope
                                'Applications'    = $AppsScope
                                'Grant Controls'  = $BuiltInCtrl
                                'Session'         = if ($HasSession) { 'Yes' } else { 'No' }
                                'Gap / Risk'      = $GapRiskStr
                                'Modified'        = if ($Policy.ModifiedDateTime) { ($Policy.ModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }
                            $CAObj.Add([pscustomobject]$caInObj) | Out-Null
                        } catch {
                            Write-PScriboMessage -IsWarning -Message "CA Policy '$($Policy.DisplayName)': $($_.Exception.Message)" 
                        }
                    }

                    $null = (& {
                        if ($HealthCheck.EntraID.ConditionalAccess) {
                            $null = ($CAObj | Where-Object { $_.State -eq 'Disabled' }     | Set-Style -Style Critical | Out-Null)
                            $null = ($CAObj | Where-Object { $_.State -eq 'Report-Only' }  | Set-Style -Style Warning  | Out-Null)
                        }
                    })

                    $CATableParams = @{ Name = "Conditional Access Policies - $TenantId"; List = $false; ColumnWidths = 18, 10, 10, 10, 10, 12, 6, 18, 6 }
                    if ($Report.ShowTableCaptions) { $CATableParams['Caption'] = "- $($CATableParams.Name)" }
                    $CAObj | Table @CATableParams

                    # Store for Excel export
                    $null = ($script:ExcelSheets['Conditional Access'] = $CAObj)

                    #region Per-Policy Detail (Level 2)
                    if ($InfoLevel.ConditionalAccess -ge 2) {
                        foreach ($Policy in ($CAPolicies | Sort-Object DisplayName)) {
                            try {
                                Section -Style Heading3 $Policy.DisplayName {
                                    Paragraph "Logicalis implemented the Conditional Access policy '$($Policy.DisplayName)' with the following configuration."
                                    BlankLine

                                    $PolicyDetailObj = [System.Collections.ArrayList]::new()

                                    # Platforms
                                    $Platforms = $Policy.Conditions.Platforms
                                    $PlatformInclude = if ($Platforms.IncludePlatforms) { $Platforms.IncludePlatforms -join ', ' } else { 'Any' }
                                    $PlatformExclude = if ($Platforms.ExcludePlatforms) { $Platforms.ExcludePlatforms -join ', ' } else { 'None' }

                                    # Locations
                                    $Locations = $Policy.Conditions.Locations
                                    $LocInclude = if ($Locations.IncludeLocations) { $Locations.IncludeLocations -join ', ' } else { 'Any' }
                                    $LocExclude = if ($Locations.ExcludeLocations) { $Locations.ExcludeLocations -join ', ' } else { 'None' }

                                    # Client apps
                                    $ClientApps = $Policy.Conditions.ClientAppTypes
                                    $ClientAppsStr = if ($ClientApps) { $ClientApps -join ', ' } else { 'All' }

                                    # Sign-in risk
                                    $SignInRisk = if ($Policy.Conditions.SignInRiskLevels) { $Policy.Conditions.SignInRiskLevels -join ', ' } else { 'Not configured' }
                                    $UserRisk   = if ($Policy.Conditions.UserRiskLevels)   { $Policy.Conditions.UserRiskLevels   -join ', ' } else { 'Not configured' }

                                    $policyDetailInObj = [ordered] @{
                                        'Policy ID'                  = $Policy.Id
                                        'State'                      = $Policy.State
                                        'Platforms (Include)'        = $PlatformInclude
                                        'Platforms (Exclude)'        = $PlatformExclude
                                        'Locations (Include)'        = $LocInclude
                                        'Locations (Exclude)'        = $LocExclude
                                        'Client App Types'           = $ClientAppsStr
                                        'Sign-In Risk Levels'        = $SignInRisk
                                        'User Risk Levels'           = $UserRisk
                                        'Grant Controls'             = if ($Policy.GrantControls.BuiltInControls) { $Policy.GrantControls.BuiltInControls -join ', ' } else { '--' }
                                        'Custom Auth Factors'        = if ($Policy.GrantControls.CustomAuthenticationFactors) { $Policy.GrantControls.CustomAuthenticationFactors -join ', ' } else { '--' }
                                        'Terms of Use Required'      = if ($Policy.GrantControls.TermsOfUse) { $Policy.GrantControls.TermsOfUse -join ', ' } else { 'No' }
                                        'Sign-In Frequency'          = if ($Policy.SessionControls.SignInFrequency -and $Policy.SessionControls.SignInFrequency.Value) { "$($Policy.SessionControls.SignInFrequency.Value) $($Policy.SessionControls.SignInFrequency.Type)" } else { 'Not configured' }
                                        'Persistent Browser Session' = if ($Policy.SessionControls.PersistentBrowser -and $Policy.SessionControls.PersistentBrowser.Mode) { $Policy.SessionControls.PersistentBrowser.Mode } else { 'Not configured' }
                                        'Created'                    = if ($Policy.CreatedDateTime)  { ($Policy.CreatedDateTime).ToString('yyyy-MM-dd HH:mm')  } else { '--' }
                                        'Last Modified'              = if ($Policy.ModifiedDateTime) { ($Policy.ModifiedDateTime).ToString('yyyy-MM-dd HH:mm') } else { '--' }
                                    }
                                    $PolicyDetailObj.Add([pscustomobject]$policyDetailInObj) | Out-Null

                                    $PolicyDetailTableParams = @{ Name = "CA Policy Detail - $($Policy.DisplayName)"; List = $true; ColumnWidths = 40, 60 }
                    if ($Report.ShowTableCaptions) { $PolicyDetailTableParams['Caption'] = "- $($PolicyDetailTableParams.Name)" }
                                    $PolicyDetailObj | Table @PolicyDetailTableParams
                                }
                            } catch {
                                Write-PScriboMessage -IsWarning -Message "CA Policy detail '$($Policy.DisplayName)': $($_.Exception.Message)" 
                            }
                        }
                    }
                    #endregion

                } else {
                    Paragraph "No Conditional Access policies are configured in tenant $TenantId. This is a critical security gap -- consider enabling Security Defaults or deploying CA policies."
                    $null = (& {
                        if ($HealthCheck.EntraID.ConditionalAccess) {
                            # No CA policies -- flag at document level
                        }
                    })
                }

                #region Named Locations
                try {
                    Write-Host " - Retrieving Named Locations..."
                    $NamedLocations = Get-MgIdentityConditionalAccessNamedLocation -All -ErrorAction SilentlyContinue

                    if ($NamedLocations) {
                        Section -Style Heading3 'Named Locations' {
                            Paragraph "The following Named Locations are configured for use in Conditional Access policies in tenant $TenantId."
                            BlankLine

                            $LocObj = [System.Collections.ArrayList]::new()
                            foreach ($Loc in ($NamedLocations | Sort-Object DisplayName)) {
                                $OdataType = $Loc.AdditionalProperties['@odata.type']
                                $LocType   = switch ($OdataType) {
                                    '#microsoft.graph.ipNamedLocation'      { 'IP Ranges' }
                                    '#microsoft.graph.countryNamedLocation' { 'Countries'  }
                                    default                                 { $OdataType   }
                                }

                                # Expand IP ranges at InfoLevel 2, but cap at 10 to prevent table overflow
                                $Detail = if ($OdataType -eq '#microsoft.graph.ipNamedLocation') {
                                    $Ranges = $Loc.AdditionalProperties.ipRanges
                                    if ($Ranges) {
                                        [int]$RangeCount = @($Ranges).Count
                                        if ($InfoLevel.ConditionalAccess -ge 2) {
                                            $top10 = ($Ranges | Select-Object -First 10 | ForEach-Object { if ($_.cidrAddress) { $_.cidrAddress } else { $_ } }) -join ', '
                                            if ($RangeCount -gt 10) { "$top10 ... and $($RangeCount - 10) more (total: $RangeCount ranges)" } else { $top10 }
                                        } else {
                                            "$RangeCount range(s)"
                                        }
                                    } else { 'No ranges' }
                                } elseif ($OdataType -eq '#microsoft.graph.countryNamedLocation') {
                                    $Countries = $Loc.AdditionalProperties.countriesAndRegions
                                    if ($Countries) { $Countries -join ', ' } else { '--' }
                                } else { '--' }

                                $locInObj = [ordered] @{
                                    'Location Name'     = $Loc.DisplayName
                                    'Type'              = $LocType
                                    'Trusted'           = if ($Loc.AdditionalProperties.isTrusted -ne $null) { $Loc.AdditionalProperties.isTrusted } else { 'N/A' }
                                    'Detail'            = $Detail
                                    'Created'           = if ($Loc.CreatedDateTime)  { ($Loc.CreatedDateTime).ToString('yyyy-MM-dd')  } else { '--' }
                                    'Modified'          = if ($Loc.ModifiedDateTime) { ($Loc.ModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                                }
                                $LocObj.Add([pscustomobject](ConvertTo-HashToYN $locInObj)) | Out-Null
                            }

                            $LocTableParams = @{ Name = "Named Locations - $TenantId"; List = $false; ColumnWidths = 22, 14, 10, 30, 12, 12 }
                    if ($Report.ShowTableCaptions) { $LocTableParams['Caption'] = "- $($LocTableParams.Name)" }
                            $LocObj | Table @LocTableParams

                            # Store for Excel export
                            $null = ($script:ExcelSheets['Named Locations'] = $LocObj)
                        }
                    }
                } catch {
                    Write-AbrSectionError -Section 'Named Locations' -Message "$($_.Exception.Message)" 
                }
                #endregion

                #region ACSC E8 Conditional Access Assessment
                BlankLine
                Paragraph "ACSC Essential Eight Maturity Level Assessment -- Conditional Access:"
                BlankLine
                try {
                    $Enabled     = @($CAPolicies | Where-Object { $_.State -eq 'enabled' })

                    # --- New CIS v5 / E8 CA checks ---
                    # Sign-in frequency + no persistent browser for admin-targeted policies
                    $AdminSessionPolicy = $false
                    $AdminSessionPolicyDetail = 'No CA policy found targeting admin roles with sign-in frequency <= 4 hours and non-persistent browser session.'
                    foreach ($P in $Enabled) {
                        if ($P.Conditions.Users.IncludeRoles.Count -gt 0 -and $P.SessionControls.SignInFrequency -ne $null) {
                            $FreqHours = if ($P.SessionControls.SignInFrequency.Type -eq 'hours') { $P.SessionControls.SignInFrequency.Value } else { $P.SessionControls.SignInFrequency.Value * 24 }
                            $NoPersist = ($P.SessionControls.PersistentBrowser.IsEnabled -eq $false -or $P.SessionControls.PersistentBrowser.Mode -eq 'never')
                            if ($FreqHours -le 4 -and $NoPersist) {
                                $AdminSessionPolicy = $true
                                $AdminSessionPolicyDetail = "Policy '$($P.DisplayName)' enforces sign-in frequency ($FreqHours h) and non-persistent browser sessions for admin roles. [OK]"
                                break
                            }
                        }
                    }

                    # Device code flow blocked
                    $DeviceCodeBlockPolicy = $false
                    $DeviceCodeBlockDetail = 'No CA policy found blocking the device code authentication flow.'
                    foreach ($P in $Enabled) {
                        if ($P.Conditions.AdditionalProperties.authenticationFlows.transferMethods -contains 'deviceCodeFlow' -and
                            $P.GrantControls.BuiltInControls -contains 'block') {
                            $DeviceCodeBlockPolicy = $true
                            $DeviceCodeBlockDetail = "Policy '$($P.DisplayName)' blocks device code flow. [OK]"
                            break
                        }
                    }

                    # MFA registration requires managed device (User Action: registerOrJoinDevices)
                    $MfaRegistrationDevicePolicy = $false
                    $MfaRegistrationDevicePolicyDetail = 'No CA policy found requiring managed device for MFA/security info registration.'
                    foreach ($P in $Enabled) {
                        $Actions = $P.Conditions.Applications.IncludeUserActions
                        if ($Actions -contains 'urn:user:registerdevice' -or $Actions -contains 'urn:user:registersecurityinfo') {
                            if ($P.GrantControls.BuiltInControls -contains 'compliantDevice' -or $P.GrantControls.BuiltInControls -contains 'domainJoinedDevice') {
                                $MfaRegistrationDevicePolicy = $true
                                $MfaRegistrationDevicePolicyDetail = "Policy '$($P.DisplayName)' requires managed device for security info registration. [OK]"
                                break
                            }
                        }
                    }

                    # MFA for all users (enforced)
                    $MFAAllEnforced = @($Enabled | Where-Object {
                        $_.Conditions.Users.IncludeUsers -contains 'All' -and
                        $_.GrantControls.BuiltInControls -contains 'mfa'
                    })
                    # Block legacy auth (enforced)
                    $LegacyBlocked = @($Enabled | Where-Object {
                        ($_.Conditions.ClientAppTypes -contains 'exchangeActiveSync' -or $_.Conditions.ClientAppTypes -contains 'other') -and
                        $_.GrantControls.BuiltInControls -contains 'block'
                    })
                    # MFA for admins (enforced)
                    $MFAAdmins = @($Enabled | Where-Object {
                        $_.Conditions.Users.IncludeRoles.Count -gt 0 -and
                        $_.GrantControls.BuiltInControls -contains 'mfa'
                    })
                    # Device compliance (enforced)
                    $DevCompliance = @($Enabled | Where-Object {
                        $_.GrantControls.BuiltInControls -contains 'compliantDevice'
                    })
                    # Auth strength (phishing-resistant) - any user
                    $PhishResistantCA = @($Enabled | Where-Object {
                        $_.GrantControls.AuthenticationStrength -ne $null
                    })
                    # Auth strength specifically targeting admin roles
                    $PhishResistantAdminCA = @($Enabled | Where-Object {
                        $_.Conditions.Users.IncludeRoles.Count -gt 0 -and
                        $_.GrantControls.AuthenticationStrength -ne $null
                    })
                    $PhishResistantAdminPolicy = $PhishResistantAdminCA.Count -gt 0
                    $PhishResistantAdminDetail = if ($PhishResistantAdminPolicy) {
                        "$($PhishResistantAdminCA.Count) policy/policies enforce phishing-resistant MFA Auth Strength for admin roles. [OK]"
                    } else { 'No CA policy enforces phishing-resistant MFA Authentication Strength for admin roles. [WARN]' }

                    # --- Fix: derive correctly-named compliance vars from computed values ---
                    $EnabledPolicyCount  = $EnabledPolicies
                    $ReportOnlyCount     = $ReportOnlyPolicies
                    $MfaAllUsersPolicy   = $MFAAllEnforced.Count -gt 0
                    $MfaAllUsersPolicyDetail = if ($MFAAllEnforced.Count -gt 0) {
                        "$($MFAAllEnforced.Count) enforced CA policy/policies require MFA for All Users. [OK]"
                    } else { 'No enforced CA policy found requiring MFA for All Users targeting All Cloud Apps. [FAIL]' }
                    $LegacyBlockPolicy   = $LegacyBlocked.Count -gt 0
                    $LegacyBlockDetail   = if ($LegacyBlocked.Count -gt 0) {
                        "$($LegacyBlocked.Count) enforced CA policy/policies block legacy authentication clients. [OK]"
                    } else { 'No enforced CA policy found blocking legacy authentication (Exchange ActiveSync / Other clients). [FAIL]' }
                    $MfaAdminPolicy      = $MFAAdmins.Count -gt 0
                    $MfaAdminPolicyDetail = if ($MFAAdmins.Count -gt 0) {
                        "$($MFAAdmins.Count) enforced CA policy/policies require MFA for admin roles. [OK]"
                    } else { 'No enforced CA policy found requiring MFA for admin directory roles. [FAIL]' }
                    $DeviceCompliancePolicy = $DevCompliance.Count -gt 0
                    $DeviceCompliancePolicyDetail = if ($DevCompliance.Count -gt 0) {
                        "$($DevCompliance.Count) enforced CA policy/policies require device compliance. [OK]"
                    } else { 'No enforced CA policy found requiring device compliance. [WARN]' }
                    $PhishResistantStrengthPolicy = $PhishResistantCA.Count -gt 0
                    $PhishResistantStrengthDetail = if ($PhishResistantCA.Count -gt 0) {
                        "$($PhishResistantCA.Count) enforced CA policy/policies use Authentication Strength (phishing-resistant MFA). [OK]"
                    } else { 'No enforced CA policy uses Authentication Strength with phishing-resistant MFA. [WARN]' }

                    # CIS 5.2.2.8: Sign-in risk blocks medium AND high
                    $SignInRiskMedHighPolicy = ($Enabled | Where-Object {
                        $_.Conditions.SignInRiskLevels -contains 'medium' -and
                        $_.Conditions.SignInRiskLevels -contains 'high' -and
                        ($_.GrantControls.BuiltInControls -contains 'block' -or $_.GrantControls.BuiltInControls -contains 'mfa')
                    }).Count -gt 0
                    $SignInRiskMedHighDetail = if ($SignInRiskMedHighPolicy) {
                        'CA policy blocks/challenges medium and high sign-in risk. [OK]'
                    } else { 'No CA policy found blocking medium+high sign-in risk. [WARN]' }

                    # CIS 5.2.2.11: Sign-in frequency for Intune enrollment
                    $IntuneEnrollFreqPolicy = ($Enabled | Where-Object {
                        $_.Conditions.Applications.IncludeUserActions -contains 'urn:user:registerdevice' -and
                        $_.SessionControls.SignInFrequency -ne $null
                    }).Count -gt 0
                    $IntuneEnrollFreqDetail = if ($IntuneEnrollFreqPolicy) {
                        'CA policy sets sign-in frequency for Intune device enrollment. [OK]'
                    } else { 'No CA policy sets sign-in frequency for Intune enrollment user action. [WARN]' }

                    #region ACSC E8 Assessment (definitions from Src/Compliance/ACSC.E8.json)
                    $_ComplianceVars = @{
                        'EnabledPolicyCount'             = $EnabledPolicyCount
                        'ReportOnlyCount'                = $ReportOnlyCount
                        'MfaAllUsersPolicy'              = $MfaAllUsersPolicy
                        'MfaAllUsersPolicyDetail'        = $MfaAllUsersPolicyDetail
                        'LegacyBlockPolicy'              = $LegacyBlockPolicy
                        'LegacyBlockDetail'              = $LegacyBlockDetail
                        'MfaAdminPolicy'                 = $MfaAdminPolicy
                        'MfaAdminPolicyDetail'           = $MfaAdminPolicyDetail
                        'DeviceCompliancePolicy'         = $DeviceCompliancePolicy
                        'DeviceCompliancePolicyDetail'   = $DeviceCompliancePolicyDetail
                        'PhishResistantStrengthPolicy'   = $PhishResistantStrengthPolicy
                        'PhishResistantStrengthDetail'   = $PhishResistantStrengthDetail
                        'AdminSessionPolicy'             = $AdminSessionPolicy
                        'AdminSessionPolicyDetail'       = $AdminSessionPolicyDetail
                        'DeviceCodeBlockPolicy'          = $DeviceCodeBlockPolicy
                        'DeviceCodeBlockDetail'          = $DeviceCodeBlockDetail
                        'MfaRegistrationDevicePolicy'    = $MfaRegistrationDevicePolicy
                        'MfaRegistrationDevicePolicyDetail' = $MfaRegistrationDevicePolicyDetail
                        'SignInRiskMedHighPolicy'        = $SignInRiskMedHighPolicy
                        'SignInRiskMedHighDetail'        = $SignInRiskMedHighDetail
                        'IntuneEnrollFreqPolicy'          = $IntuneEnrollFreqPolicy
                        'IntuneEnrollFreqDetail'          = $IntuneEnrollFreqDetail
                        'PhishResistantAdminPolicy'       = $PhishResistantAdminPolicy
                        'PhishResistantAdminDetail'       = $PhishResistantAdminDetail
                    }
                    $E8CAChecks = Build-AbrComplianceChecks `
                        -Definitions (Get-AbrE8Checks -Section 'ConditionalAccess') `
                        -Framework E8 `
                        -CallerVariables $_ComplianceVars
                    New-AbrE8AssessmentTable -Checks $E8CAChecks -Name 'Conditional Access' -TenantId $TenantId
                    # Consolidated into ACSC E8 Assessment sheet
                    if ($E8CAChecks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8CAChecks | Select-Object @{N='Section';E={'Conditional Access'}}, ML, Control, Status, Detail ))) }
                    #endregion
                } catch {
                    Write-AbrSectionError -Section 'E8 CA Assessment' -Message "$($_.Exception.Message)"
                }
                #endregion

                #region CIS Baseline Conditional Access Assessment
                if ($script:IncludeCISBaseline) {
                    BlankLine
                    Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- Conditional Access:"
                    BlankLine
                    try {
                        #region CIS Assessment (definitions from Src/Compliance/CIS.M365.json)
                        $CISCAChecks = Build-AbrComplianceChecks `
                            -Definitions (Get-AbrCISChecks -Section 'ConditionalAccess') `
                            -Framework CIS `
                            -CallerVariables $_ComplianceVars
                        New-AbrCISAssessmentTable -Checks $CISCAChecks -Name 'Conditional Access' -TenantId $TenantId
                        # Consolidated into CIS Assessment sheet
                    if ($CISCAChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISCAChecks | Select-Object @{N='Section';E={'Conditional Access'}}, CISControl, Level, Status, Detail ))) }
                        #endregion
                    } catch {
                        Write-AbrSectionError -Section 'CIS CA Assessment' -Message "$($_.Exception.Message)"
                    }
                }
                #endregion

            } catch {
                Write-AbrSectionError -Section 'Conditional Access section' -Message "$($_.Exception.Message)"
            }
        } # end Section Conditional Access
        #endregion
    }

    end {
        Show-AbrDebugExecutionTime -End -TitleMessage 'Conditional Access'
    }
}