Src/Private/Get-AbrIntuneEnrollmentRestrictions.ps1

# Helper: map MDM/WIP policy appliesTo field to human-readable scope string
# The scope field is 'appliesTo' on policies/ but 'appliesToGroups'+'isDefaultPolicy' on mobilityManagementPolicies
function Get-MdmScopeString ($policy) {
    if (-not $policy) { return 'None' }
    if ($policy.PSObject.Properties['appliesTo']) {
        $val = $policy.appliesTo
        if ($val -eq 'all')      { return 'All' }
        if ($val -eq 'selected') { return 'Selected' }
        if ($val -eq 'none')     { return 'None' }
        if ($val)                { return $val }
        return 'None'
    }
    if ($policy.PSObject.Properties['appliesToGroups']) {
        if ($policy.appliesToGroups -and @($policy.appliesToGroups).Count -gt 0) { return 'Selected' }
        if ($policy.isDefaultPolicy -eq $true) { return 'All' }
    }
    return 'None'
}

function Get-AbrIntuneEnrollmentRestrictions {
    <#
    .SYNOPSIS
    Documents Intune Device Enrollment Restrictions and configuration.
    .DESCRIPTION
        Collects and reports on:
          - Enrollment Restrictions (device type and limit)
          - Device Enrollment (Windows Automatic MDM / WIP)
          - Deployment Profiles (Windows Autopilot)
          - Autopilot Device Preparation V2
          - Enrollment Status Pages (ESP)
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

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

    begin {
        Write-PScriboMessage -Message "Collecting Intune Enrollment Restrictions for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Enrollment Restrictions'
    }

    process {
        Section -Style Heading2 'Enrollment Restrictions' {
            Paragraph "The following section documents Device Enrollment configuration for tenant $TenantId."
            BlankLine

            $TotalEnrollmentRestrictions = 0
            $TotalAutopilotProfiles      = 0
            $TotalESPProfiles            = 0

            #region Device Type Restrictions
            try {
                Write-Host " - Retrieving device type restrictions..."
                $TypeRestResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/v1.0/deviceManagement/deviceEnrollmentConfigurations?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $AllRestrictions = $TypeRestResp.value

                $null = ($TotalEnrollmentRestrictions = @($AllRestrictions).Count)
                if ($AllRestrictions -and @($AllRestrictions).Count -gt 0) {

                    # Device Type Restrictions
                    $TypeRestrictions = $AllRestrictions | Where-Object { $_.'@odata.type' -like '*deviceEnrollmentPlatformRestrictionsConfiguration*' }
                    if ($TypeRestrictions) {
                        Section -Style Heading3 'Device Type Restrictions' {
                            BlankLine
                            foreach ($Restriction in ($TypeRestrictions | Sort-Object priority)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Restriction.assignments
                                $AssignedTo = if ($assignResolved.AssignmentSummary -ne 'Not assigned') { $assignResolved.AssignmentSummary } else { 'Default (All Users)' }

                                $RestObj = [System.Collections.ArrayList]::new()
                                $restInObj = [ordered] @{
                                    'Restriction Name'     = $Restriction.displayName
                                    'Priority'             = $Restriction.priority
                                    'Assignments'          = $AssignedTo
                                }

                                # Platform-level settings
                                $Platforms = @('androidRestriction', 'androidForWorkRestriction', 'iosRestriction',
                                    'macOSRestriction', 'windowsRestriction', 'windowsMobileRestriction')
                                foreach ($PlatKey in $Platforms) {
                                    if ($Restriction.$PlatKey) {
                                        $PlatLabel = $PlatKey -replace 'Restriction', '' -replace 'androidForWork', 'Android Work Profile' `
                                            -replace 'android', 'Android' -replace 'ios', 'iOS' -replace 'macOS', 'macOS' `
                                            -replace 'windows$', 'Windows' -replace 'windowsMobile', 'Windows Mobile'
                                        $PlatData = $Restriction.$PlatKey
                                        $restInObj["$PlatLabel - Platform Blocked"]   = $PlatData.platformBlocked
                                        $restInObj["$PlatLabel - Personal Blocked"]   = $PlatData.personalDeviceEnrollmentBlocked
                                        if ($PlatData.osMinimumVersion) { $restInObj["$PlatLabel - Min OS"] = $PlatData.osMinimumVersion }
                                        if ($PlatData.osMaximumVersion) { $restInObj["$PlatLabel - Max OS"] = $PlatData.osMaximumVersion }
                                    }
                                }

                                $RestObj.Add([pscustomobject](ConvertTo-HashToYN $restInObj)) | Out-Null
                                $RestTableParams = @{ Name = "Type Restriction - $($Restriction.displayName)"; List = $true; ColumnWidths = 45, 55 }
                                $RestObj | Table @RestTableParams
                                BlankLine
                            }
                        }
                    }

                    # Device Limit Restrictions
                    $LimitRestrictions = $AllRestrictions | Where-Object { $_.'@odata.type' -like '*deviceEnrollmentLimitConfiguration*' }
                    if ($LimitRestrictions) {
                        Section -Style Heading3 'Device Limit Restrictions' {
                            BlankLine
                            $LimObj = [System.Collections.ArrayList]::new()
                            foreach ($Lim in ($LimitRestrictions | Sort-Object priority)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Lim.assignments
                                $AssignedTo = if ($assignResolved.AssignmentSummary -ne 'Not assigned') { $assignResolved.AssignmentSummary } else { 'Default (All Users)' }

                                $limInObj = [ordered] @{
                                    'Restriction Name'  = $Lim.displayName
                                    'Priority'          = $Lim.priority
                                    'Device Limit'      = $Lim.limit
                                    'Assignments'       = $AssignedTo
                                }
                                $LimObj.Add([pscustomobject]$limInObj) | Out-Null
                            }
                            $LimTableParams = @{ Name = "Device Limit Restrictions - $TenantId"; ColumnWidths = 35, 15, 20, 30 }
                            if ($Report.ShowTableCaptions) { $LimTableParams['Caption'] = "- $($LimTableParams.Name)" }
                            $LimObj | Table @LimTableParams
                        }
                    }

                    if (Get-IntuneBackupSectionEnabled -SectionKey 'EnrollmentRestrictions') { $script:BackupData['EnrollmentRestrictions'] = $AllRestrictions }
                    if (Get-IntuneExcelSheetEnabled -SheetKey 'EnrollmentRestrictions') {
                        $AllRestrictionsExcel = $AllRestrictions | ForEach-Object {
                            [pscustomobject]@{
                                'Name'      = $_.displayName
                                'Type'      = $_.'@odata.type' -replace '#microsoft.graph.', ''
                                'Priority'  = $_.priority
                            }
                        }
                        $script:ExcelSheets['Enrollment Restrictions'] = $AllRestrictionsExcel
                    }

                    #region InfoLevel 2 -- per-restriction Heading4 detail
                    if ($InfoLevel.EnrollmentRestrictions -ge 2) {
                        $TypeRestrictions = $AllRestrictions | Where-Object { $_.'@odata.type' -like '*deviceEnrollmentPlatformRestrictions*' } | Sort-Object priority
                        foreach ($Restriction in $TypeRestrictions) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Restriction.assignments
                            Section -Style Heading4 $Restriction.displayName {
                                BlankLine
                                $ovObj = [System.Collections.ArrayList]::new()
                                $ovObj.Add([pscustomobject]@{ Setting = 'Restriction Name'; Value = $Restriction.displayName }) | Out-Null
                                $ovObj.Add([pscustomobject]@{ Setting = 'Priority';         Value = "$($Restriction.priority)" }) | Out-Null
                                $ovObj.Add([pscustomobject]@{ Setting = 'Included Groups';  Value = $assignResolved.IncludedGroups }) | Out-Null
                                $ovObj.Add([pscustomobject]@{ Setting = 'Excluded Groups';  Value = $assignResolved.ExcludedGroups }) | Out-Null
                                $OvParams = @{ Name = "Overview - $($Restriction.displayName)"; ColumnWidths = 30, 70 }
                                if ($Report.ShowTableCaptions) { $OvParams['Caption'] = "- $($OvParams.Name)" }
                                $ovObj | Table @OvParams

                                if ($Restriction.platformRestrictions) {
                                    BlankLine
                                    Paragraph 'Platform Settings:'
                                    BlankLine
                                    $platRows = [System.Collections.ArrayList]::new()
                                    $platMap = @{
                                        'windows'           = 'Windows'
                                        'windowsMobile'     = 'Windows Mobile'
                                        'ios'               = 'iOS / iPadOS'
                                        'android'           = 'Android'
                                        'androidForWork'    = 'Android Enterprise'
                                        'mac'               = 'macOS'
                                    }
                                    foreach ($key in $platMap.Keys) {
                                        $platData = $Restriction.platformRestrictions.$key
                                        if ($platData) {
                                            $platRows.Add([pscustomobject]([ordered]@{
                                                'Platform'         = $platMap[$key]
                                                'Platform Blocked' = if ($platData.platformBlocked)                { 'Yes' } else { 'No' }
                                                'Personal Blocked' = if ($platData.personalDeviceEnrollmentBlocked) { 'Yes' } else { 'No' }
                                                'Min OS'           = if ($platData.osMinimumVersion) { $platData.osMinimumVersion } else { '--' }
                                                'Max OS'           = if ($platData.osMaximumVersion) { $platData.osMaximumVersion } else { '--' }
                                            })) | Out-Null
                                        }
                                    }
                                    if ($platRows.Count -gt 0) {
                                        $PlatParams = @{ Name = "Platform Restrictions - $($Restriction.displayName)"; ColumnWidths = 22, 18, 18, 21, 21 }
                                        if ($Report.ShowTableCaptions) { $PlatParams['Caption'] = "- $($PlatParams.Name)" }
                                        $platRows | Table @PlatParams
                                    }
                                }
                            }
                        }
                    }
                    #endregion

                } else {
                    Paragraph "No Enrollment Restrictions found in tenant $TenantId."
                }

            } catch {
                    if (Test-AbrGraphForbidden -ErrorRecord $_) {
                        Write-AbrPermissionError -Section 'Enrollment Restrictions' -RequiredRole 'Intune Service Administrator or Global Administrator'
                    } else {
                        Write-AbrSectionError -Section 'Enrollment Restrictions' -Message "$($_.Exception.Message)"
                    }
                }
            #endregion

            #region Device Enrollment (Windows Automatic MDM Enrollment)
            try {
                Write-Host " - Retrieving Windows Automatic MDM Enrollment settings..."
                $MdmPolicy = $null
                $WipPolicy = $null

                try {
                    $MdmMobResp = Invoke-MgGraphRequest -Method GET `
                        -Uri "$($script:GraphEndpoint)/beta/deviceManagement/mobilityManagementPolicies?`$filter=managedDeviceType ne 'unknown'" `
                        -ErrorAction Stop
                    if ($MdmMobResp.value) {
                        $MdmPolicy = $MdmMobResp.value | Where-Object { $_.displayName -like '*Intune*' -or $_.isDefaultPolicy -eq $true } | Select-Object -First 1
                        if (-not $MdmPolicy) { $MdmPolicy = $MdmMobResp.value | Select-Object -First 1 }
                    }
                } catch {
                    $MdmPoliciesResp = Invoke-MgGraphRequest -Method GET `
                        -Uri "$($script:GraphEndpoint)/beta/policies/mobileDeviceManagementPolicies" `
                        -ErrorAction SilentlyContinue
                    $MdmPolicy = if ($MdmPoliciesResp.value) { $MdmPoliciesResp.value | Select-Object -First 1 } else { $null }
                }

                try {
                    $WipMobResp = Invoke-MgGraphRequest -Method GET `
                        -Uri "$($script:GraphEndpoint)/beta/deviceManagement/mobilityManagementPolicies?`$filter=managedDeviceType eq 'unknown'" `
                        -ErrorAction Stop
                    $WipPolicy = if ($WipMobResp.value) { $WipMobResp.value | Select-Object -First 1 } else { $null }
                } catch {
                    $WipPoliciesResp = Invoke-MgGraphRequest -Method GET `
                        -Uri "$($script:GraphEndpoint)/beta/policies/mobileAppManagementPolicies" `
                        -ErrorAction SilentlyContinue
                    $WipPolicy = if ($WipPoliciesResp.value) { $WipPoliciesResp.value | Select-Object -First 1 } else { $null }
                }

                $mdmScopeStr = Get-MdmScopeString $MdmPolicy
                $wipScopeStr = Get-MdmScopeString $WipPolicy

                Section -Style Heading2 'Device Enrollment' {
                    Paragraph "The following table documents the Windows automatic MDM and WIP enrollment configuration for tenant $TenantId."
                    BlankLine

                    $AutoEnrollRows = [System.Collections.ArrayList]::new()
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'MDM'; Value = '' }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'MDM User Scope';                                           Value = $mdmScopeStr }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'MDM Terms of Use URL';                                     Value = if ($MdmPolicy.termsOfUseUrl)  { $MdmPolicy.termsOfUseUrl }  else { 'https://portal.manage.microsoft.com/TermsofUse.aspx (Default)' } }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'MDM Discovery URL';                                        Value = if ($MdmPolicy.discoveryUrl)   { $MdmPolicy.discoveryUrl }   else { 'https://enrollment.manage.microsoft.com/enrollmentserver/discovery.svc (Default)' } }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'MDM Compliance URL';                                       Value = if ($MdmPolicy.complianceUrl)  { $MdmPolicy.complianceUrl }  else { 'https://portal.manage.microsoft.com/?portalAction=Compliance (Default)' } }) | Out-Null
                    $disableMdm = if ($mdmScopeStr -eq 'None') { 'Yes' } else { 'No' }
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'Disable MDM Enrollment When Adding Work or School Account'; Value = $disableMdm }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'Windows Information Protection (WIP)'; Value = '' }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'WIP User Scope';                                           Value = $wipScopeStr }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'WIP Terms of Use URL';                                     Value = if ($WipPolicy.termsOfUseUrl)  { $WipPolicy.termsOfUseUrl }  else { 'Default' } }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'WIP Discovery URL';                                        Value = if ($WipPolicy.discoveryUrl)   { $WipPolicy.discoveryUrl }   else { 'Default' } }) | Out-Null
                    $AutoEnrollRows.Add([pscustomobject]@{ Setting = 'WIP Compliance URL';                                       Value = if ($WipPolicy.complianceUrl)  { $WipPolicy.complianceUrl }  else { 'Default' } }) | Out-Null

                    $mdmWipHeaders = @('MDM', 'Windows Information Protection (WIP)')
                    $null = ($AutoEnrollRows | Where-Object { $_.Setting -in $mdmWipHeaders } | Set-Style -Style TableDefaultHeading)

                    $AutoEnrollParams = @{ Name = "Windows Automatic MDM Enrollment - $TenantId"; ColumnWidths = 50, 50 }
                    if ($Report.ShowTableCaptions) { $AutoEnrollParams['Caption'] = "- $($AutoEnrollParams.Name)" }
                    $AutoEnrollRows | Table @AutoEnrollParams
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) {
                    Write-AbrPermissionError -Section 'Device Enrollment' -RequiredRole 'DeviceManagementServiceConfig.Read.All or Policy.Read.All'
                } else { Write-AbrSectionError -Section 'Device Enrollment' -Message "$($_.Exception.Message)" }
            }
            #endregion

            #region Windows Autopilot Deployment Profiles
            try {
                Write-Host " - Retrieving Windows Autopilot profiles..."
                # /beta required -- windowsAutopilotDeploymentProfiles is not supported in v1.0
                $AutopilotResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceManagement/windowsAutopilotDeploymentProfiles?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $AutopilotProfiles = $AutopilotResp.value

                $TotalAutopilotProfiles = if ($AutopilotProfiles) { @($AutopilotProfiles).Count } else { 0 }
                if ($TotalAutopilotProfiles -gt 0) {
                    Section -Style Heading2 'Deployment Profiles' {
                        Paragraph "The following section documents the Windows Autopilot Deployment Profiles configured in tenant $TenantId."
                        BlankLine

                        # ── Summary table ──────────────────────────────────────────────────
                        $APObj = [System.Collections.ArrayList]::new()
                        foreach ($APProfile in ($AutopilotProfiles | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $APProfile.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            $AssignedTo     = $assignResolved.AssignmentSummary

                            # Determine Join Type from odata type and hybrid flag
                            $JoinType = if ($APProfile.'@odata.type' -like '*hybrid*' -or $null -ne $APProfile.hybridAzureADJoinSkipConnectivityCheck) {
                                'Microsoft Entra Hybrid Joined'
                            } else { 'Microsoft Entra Joined' }

                            # Deployment mode: check explicit property first, fall back to @odata.type encoding
                            # azureADWindowsAutopilotDeploymentProfile = User-Driven (Entra joined)
                            # activeDirectoryWindowsAutopilotDeploymentProfile = User-Driven (Hybrid)
                            # selfDeploying in either property = Self-Deploying
                            $odataType = $APProfile.'@odata.type'
                            $dmRaw = $APProfile.deploymentMode
                            if ($dmRaw -eq 'userDriven' -or $dmRaw -eq 'user_driven') {
                                $DeployMode = 'User-Driven'
                            } elseif ($dmRaw -eq 'selfDeploying' -or $dmRaw -eq 'self_deploying') {
                                $DeployMode = 'Self-Deploying'
                            } elseif ($odataType -like '*selfDeploying*') {
                                $DeployMode = 'Self-Deploying'
                            } elseif ($odataType -like '*azureAD*' -or $odataType -like '*activeDirectory*') {
                                $DeployMode = 'User-Driven'
                            } elseif ($dmRaw) {
                                $DeployMode = $dmRaw
                            } else {
                                $DeployMode = 'User-Driven'
                            }

                            # User account type — Graph returns 'outOfBoxExperienceSettings' (plural) for
                            # azureADWindowsAutopilotDeploymentProfile and 'outOfBoxExperienceSetting' (singular) for hybrid
                            $oobeObj = if ($APProfile.outOfBoxExperienceSettings) { $APProfile.outOfBoxExperienceSettings }
                                       else { $APProfile.outOfBoxExperienceSetting }
                            $utVal = if ($oobeObj) { $oobeObj.userType } else { $null }
                            if ($utVal -eq 'administrator') { $SumUserType = 'Administrator' }
                            elseif ($utVal -eq 'standard')  { $SumUserType = 'Standard' }
                            elseif ($utVal)                 { $SumUserType = $utVal }
                            else                            { $SumUserType = '--' }

                            $apInObj = [ordered] @{
                                'Profile Name'        = $APProfile.displayName
                                'Deployment Mode'     = $DeployMode
                                'Join Type'           = $JoinType
                                'User Account Type'   = $SumUserType
                                'Device Name Template'= if ($APProfile.deviceNameTemplate) { $APProfile.deviceNameTemplate } else { 'Not configured' }
                                'Assignments'         = $AssignedTo
                                'Last Modified'       = if ($APProfile.lastModifiedDateTime) { ([datetime]$APProfile.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }
                            $APObj.Add([pscustomobject]$apInObj) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.Intune.EnrollmentRestrictions) {
                                $null = ($APObj | Where-Object { $_.'Assignments' -eq 'Not assigned' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $APTableParams = @{ Name = "Autopilot Deployment Profiles - $TenantId"; ColumnWidths = 24, 14, 20, 12, 16, 7, 7 }
                        if ($Report.ShowTableCaptions) { $APTableParams['Caption'] = "- $($APTableParams.Name)" }
                        $APObj | Table @APTableParams

                        if (Get-IntuneExcelSheetEnabled -SheetKey 'AutopilotProfiles') {
                            $script:ExcelSheets['Autopilot Profiles'] = $APObj
                        }
                        if (Get-IntuneBackupSectionEnabled -SectionKey 'AutopilotProfiles') { $script:BackupData['AutopilotProfiles'] = $AutopilotProfiles }

                        # ── Per-profile detail sections (all InfoLevel) ────────────────────
                        foreach ($APProfile in ($AutopilotProfiles | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $APProfile.assignments -CheckMemberCount:$script:CheckEmptyGroups

                            # Derive values
                            $JoinType = if ($APProfile.'@odata.type' -like '*hybrid*' -or $null -ne $APProfile.hybridAzureADJoinSkipConnectivityCheck) {
                                'Microsoft Entra Hybrid Joined'
                            } else { 'Microsoft Entra Joined' }

                            $DeployMode = $null
                            $odataType2 = $APProfile.'@odata.type'
                            $dmRaw2 = $APProfile.deploymentMode
                            if ($dmRaw2 -eq 'userDriven' -or $dmRaw2 -eq 'user_driven') {
                                $DeployMode = 'User-Driven'
                            } elseif ($dmRaw2 -eq 'selfDeploying' -or $dmRaw2 -eq 'self_deploying') {
                                $DeployMode = 'Self-Deploying'
                            } elseif ($odataType2 -like '*selfDeploying*') {
                                $DeployMode = 'Self-Deploying'
                            } elseif ($odataType2 -like '*azureAD*' -or $odataType2 -like '*activeDirectory*') {
                                $DeployMode = 'User-Driven'
                            } elseif ($dmRaw2) {
                                $DeployMode = $dmRaw2
                            } else {
                                $DeployMode = 'User-Driven'
                            }

                            # Graph returns 'outOfBoxExperienceSettings' (plural) for Entra joined profiles
                            # and 'outOfBoxExperienceSetting' (singular) for hybrid profiles
                            $OOBE = if ($APProfile.outOfBoxExperienceSettings) { $APProfile.outOfBoxExperienceSettings }
                                    else { $APProfile.outOfBoxExperienceSetting }

                            $UserAccountType = $null
                            $utVal2 = if ($OOBE) { $OOBE.userType } else { $null }
                            if ($utVal2 -eq 'administrator') { $UserAccountType = 'Administrator' }
                            elseif ($utVal2 -eq 'standard')  { $UserAccountType = 'Standard' }
                            elseif ($utVal2)                 { $UserAccountType = $utVal2 }
                            else                             { $UserAccountType = '--' }

                            $HideShow = { param($boolVal) if ($boolVal) { 'Hide' } else { 'Show' } }

                            Section -Style Heading4 $APProfile.displayName {
                                BlankLine

                                # ── Overview table ──
                                $APDetailObj = [System.Collections.ArrayList]::new()
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Profile Name';                    Value = $APProfile.displayName }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Description';                     Value = if ($APProfile.description) { $APProfile.description } else { '--' } }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Assigned';                        Value = if ($assignResolved.AssignmentSummary -ne 'Not assigned') { 'Yes' } else { 'No' } }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Assignments';                     Value = $assignResolved.AssignmentSummary }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Created';                         Value = if ($APProfile.createdDateTime) { ([datetime]$APProfile.createdDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Last Modified';                   Value = if ($APProfile.lastModifiedDateTime) { ([datetime]$APProfile.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Deployment Mode';                 Value = $DeployMode }) | Out-Null
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Join to Microsoft Entra ID as';   Value = $JoinType }) | Out-Null

                                # Hybrid-only: skip AD connectivity check
                                if ($null -ne $APProfile.hybridAzureADJoinSkipConnectivityCheck) {
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Skip AD Connectivity Check';  Value = if ($APProfile.hybridAzureADJoinSkipConnectivityCheck) { 'Yes' } else { 'No' } }) | Out-Null
                                }

                                # Convert all targeted devices to Autopilot
                                if ($null -ne $APProfile.extractHardwareKey) {
                                    # extractHardwareKey = $true means "convert all targeted devices to Autopilot"
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Convert All Targeted Devices to Autopilot'; Value = if ($APProfile.extractHardwareKey) { 'Yes' } else { 'No' } }) | Out-Null
                                }

                                # OOBE settings
                                if ($OOBE) {
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Microsoft Software License Terms'; Value = (& $HideShow ($OOBE.eulaHidden)) }) | Out-Null
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Privacy Settings';                 Value = (& $HideShow ($OOBE.privacySettingsHidden)) }) | Out-Null
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Hide Change Account Options';      Value = (& $HideShow ($OOBE.escapeLinkHidden)) }) | Out-Null
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'User Account Type';                Value = $UserAccountType }) | Out-Null
                                }

                                # Allow pre-provisioned deployment (White Glove)
                                if ($null -ne $APProfile.enableWhiteGlove) {
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Allow Pre-Provisioned Deployment'; Value = if ($APProfile.enableWhiteGlove) { 'Yes' } else { 'No' } }) | Out-Null
                                }

                                # Language / keyboard
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Language (Region)';                Value = if ($APProfile.language) { $APProfile.language } else { 'Operating System Default' } }) | Out-Null
                                if ($null -ne $APProfile.keyboardSelectionPageSkipped) {
                                    $APDetailObj.Add([pscustomobject]@{ Setting = 'Automatically Configure Keyboard'; Value = if ($APProfile.keyboardSelectionPageSkipped) { 'Yes' } else { 'No' } }) | Out-Null
                                }

                                # Device name template
                                $APDetailObj.Add([pscustomobject]@{ Setting = 'Apply Device Name Template';       Value = if ($APProfile.deviceNameTemplate) { $APProfile.deviceNameTemplate } else { 'Not configured' } }) | Out-Null

                                $APDetailTableParams = @{ Name = "Profile Overview - $($APProfile.displayName)"; ColumnWidths = 45, 55 }
                                if ($Report.ShowTableCaptions) { $APDetailTableParams['Caption'] = "- $($APDetailTableParams.Name)" }
                                $APDetailObj | Table @APDetailTableParams
                            }
                        }
                    }
                }
            } catch {
                    if (Test-AbrGraphForbidden -ErrorRecord $_) {
                        Write-AbrPermissionError -Section 'Autopilot Deployment Profiles' -RequiredRole 'Intune Service Administrator or Global Administrator'
                    } else {
                        Write-AbrSectionError -Section 'Autopilot Deployment Profiles' -Message "$($_.Exception.Message)"
                    }
                }
            #endregion

            #region Windows Autopilot Device Preparation (V2)
            try {
                Write-Host " - Retrieving Windows Autopilot Device Preparation (V2) profiles..."
                $APDPV2Profiles = @()
                try {
                    $APDPV2Resp = Invoke-MgGraphRequest -Method GET `
                        -Uri "$($script:GraphEndpoint)/beta/deviceManagement/windowsAutopilotDevicePreparationPolicies?`$expand=assignments" `
                        -ErrorAction Stop
                    $APDPV2Profiles = if ($APDPV2Resp.value) { $APDPV2Resp.value } else { @() }
                } catch {
                    # BadRequest/404 = feature not provisioned on this tenant — skip silently
                    Write-AbrDebugLog "Autopilot V2 not available on this tenant: $($_.Exception.Message)" 'INFO' 'AutopilotV2'
                }

                if (@($APDPV2Profiles).Count -gt 0) {
                    Section -Style Heading2 'Autopilot Device Preparation (V2)' {
                        Paragraph "Windows Autopilot Device Preparation (V2) is the next-generation Autopilot enrollment flow using Entra ID Enrollment Time Grouping. The following documents V2 profiles configured in tenant $TenantId."
                        BlankLine

                        # Summary table
                        $V2SumObj = [System.Collections.ArrayList]::new()
                        foreach ($V2Prof in ($APDPV2Profiles | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $V2Prof.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            $V2SumObj.Add([pscustomobject]([ordered]@{
                                'Profile Name'    = $V2Prof.displayName
                                'Account Type'    = if ($V2Prof.accountType) {
                                                        switch ($V2Prof.accountType) {
                                                            'administrator' { 'Administrator' }
                                                            'standard'      { 'Standard User' }
                                                            default         { $V2Prof.accountType }
                                                        }
                                                    } else { '--' }
                                'Assignments'     = $assignResolved.AssignmentSummary
                                'Last Modified'   = if ($V2Prof.lastModifiedDateTime) { ([datetime]$V2Prof.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            })) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.Intune.EnrollmentRestrictions) {
                                $null = ($V2SumObj | Where-Object { $_.'Assignments' -eq 'Not assigned' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $V2TableParams = @{ Name = "Autopilot Device Preparation V2 Profiles - $TenantId"; ColumnWidths = 30, 20, 35, 15 }
                        if ($Report.ShowTableCaptions) { $V2TableParams['Caption'] = "- $($V2TableParams.Name)" }
                        $V2SumObj | Table @V2TableParams

                        # Per-profile detail
                        foreach ($V2Prof in ($APDPV2Profiles | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $V2Prof.assignments -CheckMemberCount:$script:CheckEmptyGroups

                            Section -Style Heading4 $V2Prof.displayName {
                                BlankLine
                                $V2DetailObj = [System.Collections.ArrayList]::new()
                                $V2DetailObj.Add([pscustomobject]@{ Setting = 'Profile Name';                   Value = $V2Prof.displayName }) | Out-Null
                                $V2DetailObj.Add([pscustomobject]@{ Setting = 'Description';                    Value = if ($V2Prof.description)          { $V2Prof.description }          else { '--' } }) | Out-Null
                                $V2DetailObj.Add([pscustomobject]@{ Setting = 'Assignments';                    Value = $assignResolved.AssignmentSummary }) | Out-Null
                                $V2DetailObj.Add([pscustomobject]@{ Setting = 'Created';                        Value = if ($V2Prof.createdDateTime)       { ([datetime]$V2Prof.createdDateTime).ToString('yyyy-MM-dd') }       else { '--' } }) | Out-Null
                                $V2DetailObj.Add([pscustomobject]@{ Setting = 'Last Modified';                  Value = if ($V2Prof.lastModifiedDateTime)  { ([datetime]$V2Prof.lastModifiedDateTime).ToString('yyyy-MM-dd') }  else { '--' } }) | Out-Null

                                if ($V2Prof.accountType) {
                                    $acctType = switch ($V2Prof.accountType) {
                                        'administrator' { 'Administrator' }
                                        'standard'      { 'Standard User' }
                                        default         { $V2Prof.accountType }
                                    }
                                    $V2DetailObj.Add([pscustomobject]@{ Setting = 'User Account Type';          Value = $acctType }) | Out-Null
                                }
                                if ($null -ne $V2Prof.deviceSecurityGroupId) {
                                    $grpName = Resolve-IntuneGroupName -GroupId $V2Prof.deviceSecurityGroupId
                                    $V2DetailObj.Add([pscustomobject]@{ Setting = 'Enrollment Time Device Group'; Value = if ($grpName) { $grpName } else { $V2Prof.deviceSecurityGroupId } }) | Out-Null
                                }
                                if ($null -ne $V2Prof.managedDeviceGroupId) {
                                    $grpName = Resolve-IntuneGroupName -GroupId $V2Prof.managedDeviceGroupId
                                    $V2DetailObj.Add([pscustomobject]@{ Setting = 'Device Security Group';      Value = if ($grpName) { $grpName } else { $V2Prof.managedDeviceGroupId } }) | Out-Null
                                }
                                if ($null -ne $V2Prof.allowedDeviceTypes) {
                                    $V2DetailObj.Add([pscustomobject]@{ Setting = 'Allowed Device Types';       Value = ($V2Prof.allowedDeviceTypes -join ', ') }) | Out-Null
                                }
                                if ($null -ne $V2Prof.deploymentTimeout) {
                                    $V2DetailObj.Add([pscustomobject]@{ Setting = 'Deployment Timeout (mins)';  Value = "$($V2Prof.deploymentTimeout)" }) | Out-Null
                                }

                                $V2DetailParams = @{ Name = "V2 Profile - $($V2Prof.displayName)"; ColumnWidths = 45, 55 }
                                if ($Report.ShowTableCaptions) { $V2DetailParams['Caption'] = "- $($V2DetailParams.Name)" }
                                $V2DetailObj | Table @V2DetailParams
                            }
                        }
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) {
                    Write-AbrPermissionError -Section 'Autopilot Device Preparation V2' -RequiredRole 'DeviceManagementServiceConfig.Read.All'
                } else { Write-AbrSectionError -Section 'Autopilot Device Preparation V2' -Message "$($_.Exception.Message)" }
            }
            #endregion

            #region Enrollment Status Pages
            try {
                $ESPProfiles = if ($AllRestrictions) {
                    @($AllRestrictions | Where-Object { $_.'@odata.type' -like '*windows10EnrollmentCompletionPageConfiguration*' })
                } else { @() }

                if ($ESPProfiles.Count -gt 0) {
                    Section -Style Heading2 'Enrollment Status Pages' {
                        Paragraph "The Enrollment Status Page (ESP) displays the provisioning progress to end users during device setup. The following documents ESP profiles configured in tenant $TenantId."
                        BlankLine

                        $EspObj = [System.Collections.ArrayList]::new()
                        foreach ($ESP in ($ESPProfiles | Sort-Object priority)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $ESP.assignments
                            $AssignedTo = if ($assignResolved.AssignmentSummary -ne 'Not assigned') { $assignResolved.AssignmentSummary } else { 'Default (All Devices)' }

                            $BoolLabel = { param($v) if ($null -eq $v) { 'Not Configured' } elseif ($v) { 'Yes' } else { 'No' } }
                            $espInObj = [ordered] @{
                                'ESP Profile Name'                   = $ESP.displayName
                                'Priority'                           = $ESP.priority
                                'Show Progress to End User'          = (& $BoolLabel $ESP.showInstallationProgress)
                                'Block Device Until Provisioning'    = (& $BoolLabel $ESP.blockDeviceSetupRetryByUser)
                                'Allow Reset if Installation Fails'  = (& $BoolLabel $ESP.allowDeviceResetOnInstallFailure)
                                'Allow Use of Device Before Profile' = (& $BoolLabel $ESP.allowDeviceUseOnInstallFailure)
                                'Tracking Error Timeout (minutes)'   = if ($ESP.installProgressTimeoutInMinutes) { $ESP.installProgressTimeoutInMinutes } else { 'Default (60)' }
                                'Assignments'                        = $AssignedTo
                            }
                            $EspObj.Add([pscustomobject]$espInObj) | Out-Null
                        }
                        $EspTableParams = @{ Name = "Enrollment Status Page Profiles - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                        if ($Report.ShowTableCaptions) { $EspTableParams['Caption'] = "- $($EspTableParams.Name)" }
                        $EspObj | Table @EspTableParams
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) {
                    Write-AbrPermissionError -Section 'Enrollment Status Pages' -RequiredRole 'Intune Service Administrator or Global Administrator'
                } else { Write-AbrSectionError -Section 'Enrollment Status Pages' -Message "$($_.Exception.Message)" }
            }
            #endregion

            #region ACSC E8 Assessment
            if ($script:IncludeACSCe8) {
                BlankLine
                Paragraph "ACSC Essential Eight Maturity Level Assessment -- Enrollment Restrictions:"
                BlankLine
                try {
                    $_v = @{
                        TotalEnrollmentRestrictions = $TotalEnrollmentRestrictions
                        TotalAutopilotProfiles      = $TotalAutopilotProfiles
                        TotalESPProfiles            = $TotalESPProfiles
                    }
                    $E8Checks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneE8Checks -Section 'EnrollmentRestrictions') -Framework E8 -CallerVariables $_v
                    New-AbrIntuneE8AssessmentTable -Checks $E8Checks -Name 'Enrollment Restrictions' -TenantId $TenantId
                    if ($E8Checks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8Checks | Select-Object @{N='Section';E={'EnrollmentRestrictions'}}, ML, Control, Status, Detail))) }
                } catch { Write-AbrSectionError -Section 'E8 Enrollment Assessment' -Message "$($_.Exception.Message)" }
            }
            #endregion

            #region CIS Assessment
            if ($script:IncludeCISBaseline) {
                BlankLine
                Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- Enrollment Restrictions:"
                BlankLine
                try {
                    $_v = @{
                        TotalEnrollmentRestrictions = $TotalEnrollmentRestrictions
                        TotalESPProfiles            = $TotalESPProfiles
                    }
                    $CISChecks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneCISChecks -Section 'EnrollmentRestrictions') -Framework CIS -CallerVariables $_v
                    New-AbrIntuneCISAssessmentTable -Checks $CISChecks -Name 'Enrollment Restrictions' -TenantId $TenantId
                    if ($CISChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISChecks | Select-Object @{N='Section';E={'EnrollmentRestrictions'}}, CISControl, Level, Status, Detail))) }
                } catch { Write-AbrSectionError -Section 'CIS Enrollment Assessment' -Message "$($_.Exception.Message)" }
            }
            #endregion
        }
    }

    end { Show-AbrDebugExecutionTime -End -TitleMessage 'Enrollment Restrictions' }
}