Src/Private/Get-AbrIntuneDefender.ps1

function Get-AbrIntuneDefender {
    <#
    .SYNOPSIS
    Documents Microsoft Defender for Endpoint (MDE) integration and configuration in Intune.
    .DESCRIPTION
        Collects and reports on:
          - MDE connector state (Intune <-> Defender integration)
          - Mobile Threat Defence (MTD) connectors
          - Defender for Endpoint compliance policy settings per platform
          - Defender Antivirus policy summary (from Endpoint Security)
          - BitLocker / Disk Encryption policy summary
          - Windows Defender Firewall policy summary
          - Attack Surface Reduction (ASR) policy summary
          - MDE onboarding status summary
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

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

    begin {
        Write-PScriboMessage -Message "Collecting Microsoft Defender for Endpoint configuration for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Defender for Endpoint'
    }

    process {

        # ── MDE Connector (Intune <-> Defender integration) ────────────────────
        try {
            Write-Host " - Retrieving Microsoft Defender for Endpoint connector settings..."
            $MDEConnResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/deviceManagement/mobileThreatDefenseConnectors" `
                -ErrorAction SilentlyContinue
            $MDEConnectors = if ($MDEConnResp.value) { $MDEConnResp.value } else { @() }

            # Also fetch the advanced threat protection connector (MDE-specific endpoint)
            $MDEATPResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/deviceManagement/windowsDefenderApplicationControlSupplementalPolicies" `
                -ErrorAction SilentlyContinue

            # MDE Intune connector settings
            $MDEIntuneConnResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/deviceManagement/advancedThreatProtectionOnboardingStateSummary" `
                -ErrorAction SilentlyContinue

            BlankLine
            Section -Style Heading2 'Microsoft Defender for Endpoint Connector' {
                Paragraph "The following documents the Microsoft Defender for Endpoint (MDE) integration with Intune for tenant $TenantId. This integration enables device risk-based compliance enforcement and conditional access."
                BlankLine

                # ── MDE Connector settings from the Intune portal ──────────────
                # These come from the intune portal: Endpoint Security > Microsoft Defender for Endpoint
                $MDESettingsResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceManagement/intuneBrandingProfiles?`$select=id" `
                    -ErrorAction SilentlyContinue

                # The real MDE connector settings are embedded in the MTD connector for 'MicrosoftDefenderATP'
                $MDEConnector = $MDEConnectors | Where-Object {
                    $_.partnerAppId -like '*MicrosoftDefenderATP*' -or
                    $_.partnerFriendlyName -like '*Defender*' -or
                    $_.partnerFriendlyName -like '*ATP*'
                } | Select-Object -First 1

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

                # Endpoint Security Profile Settings
                $ConnRows.Add([pscustomobject]@{ Setting = 'Endpoint Security Profile Settings'; Value = '' }) | Out-Null

                if ($MDEConnector) {
                    $connState = switch ($MDEConnector.partnerState) {
                        'available'    { 'Connected' }
                        'notSetUp'     { 'Not Set Up' }
                        'error'        { 'Error' }
                        'unresponsive' { 'Unresponsive' }
                        default        { if ($MDEConnector.partnerState) { $MDEConnector.partnerState } else { 'Not Connected' } }
                    }
                    $ConnRows.Add([pscustomobject]@{ Setting = 'Connector Status';                                                     Value = $connState }) | Out-Null
                    $ConnRows.Add([pscustomobject]@{ Setting = 'Allow Microsoft Defender for Endpoint to Enforce Endpoint Security Configurations'; Value = if ($MDEConnector.allowPartnerToCollectIOSApplicationMetadata -ne $null) { 'Configured' } else { '--' } }) | Out-Null

                    # Compliance Policy Evaluation
                    $ConnRows.Add([pscustomobject]@{ Setting = 'Compliance Policy Evaluation'; Value = '' }) | Out-Null
                    if ($null -ne $MDEConnector.androidEnabled) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Connect Android Devices to Microsoft Defender for Endpoint';   Value = if ($MDEConnector.androidEnabled) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.iosEnabled) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Connect iOS/iPadOS Devices to Microsoft Defender for Endpoint'; Value = if ($MDEConnector.iosEnabled) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.windowsEnabled) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Connect Windows Devices (10.0.15063+) to Microsoft Defender for Endpoint'; Value = if ($MDEConnector.windowsEnabled) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.allowPartnerToCollectIOSApplicationMetadata) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Enable App Sync (iOS/iPadOS Application Inventory)';            Value = if ($MDEConnector.allowPartnerToCollectIOSApplicationMetadata) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.allowPartnerToCollectPersonalIOSApplicationMetadata) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Send Full Application Inventory (Personal iOS/iPadOS)';         Value = if ($MDEConnector.allowPartnerToCollectPersonalIOSApplicationMetadata) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.iosTunnelEnabled) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Enable Certificate Sync for iOS/iPadOS';                        Value = if ($MDEConnector.iosTunnelEnabled) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.windowsDeviceBlockedOnMissingPartnerData) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Block Unsupported OS Versions';                                 Value = if ($MDEConnector.windowsDeviceBlockedOnMissingPartnerData) { 'On' } else { 'Off' } }) | Out-Null
                    }

                    # App protection policy evaluation
                    $ConnRows.Add([pscustomobject]@{ Setting = 'App Protection Policy Evaluation'; Value = '' }) | Out-Null
                    if ($null -ne $MDEConnector.androidMobileApplicationManagementEnabled) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Connect Android Devices to MDE (App Protection)';              Value = if ($MDEConnector.androidMobileApplicationManagementEnabled) { 'On' } else { 'Off' } }) | Out-Null
                    }
                    if ($null -ne $MDEConnector.iosMobileApplicationManagementEnabled) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Connect iOS/iPadOS Devices to MDE (App Protection)';           Value = if ($MDEConnector.iosMobileApplicationManagementEnabled) { 'On' } else { 'Off' } }) | Out-Null
                    }

                    # Shared Settings
                    $ConnRows.Add([pscustomobject]@{ Setting = 'Shared Settings'; Value = '' }) | Out-Null
                    if ($null -ne $MDEConnector.partnerUnresponsivenessThresholdInDays) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Number of Days Until Partner is Unresponsive';                 Value = "$($MDEConnector.partnerUnresponsivenessThresholdInDays)" }) | Out-Null
                    }

                    # Last heartbeat
                    if ($MDEConnector.lastHeartbeatDateTime) {
                        $ConnRows.Add([pscustomobject]@{ Setting = 'Last Heartbeat';                                               Value = ([datetime]$MDEConnector.lastHeartbeatDateTime).ToString('yyyy-MM-dd HH:mm') }) | Out-Null
                    }
                } else {
                    $ConnRows.Add([pscustomobject]@{ Setting = 'Connector Status'; Value = 'Not Connected / Not Configured' }) | Out-Null
                }

                # Style section header rows
                $connHeaders = @('Endpoint Security Profile Settings', 'Compliance Policy Evaluation', 'App Protection Policy Evaluation', 'Shared Settings')
                $null = ($ConnRows | Where-Object { $_.Setting -in $connHeaders } | Set-Style -Style Info)

                $ConnParams = @{ Name = "MDE Connector Settings - $TenantId"; ColumnWidths = 62, 38 }
                if ($Report.ShowTableCaptions) { $ConnParams['Caption'] = "- $($ConnParams.Name)" }
                $ConnRows | Table @ConnParams

                # ── MDE Onboarding Status ───────────────────────────────────────
                if ($MDEIntuneConnResp -and $null -ne $MDEIntuneConnResp.enrolledDeviceCount) {
                    BlankLine
                    Paragraph "Microsoft Defender for Endpoint Onboarding Status:"
                    BlankLine
                    $OnbRows = [System.Collections.ArrayList]::new()
                    if ($null -ne $MDEIntuneConnResp.enrolledDeviceCount)    { $OnbRows.Add([pscustomobject]@{ Setting = 'Enrolled / Onboarded Devices';  Value = "$($MDEIntuneConnResp.enrolledDeviceCount)" }) | Out-Null }
                    if ($null -ne $MDEIntuneConnResp.notOnboardedDeviceCount) { $OnbRows.Add([pscustomobject]@{ Setting = 'Devices Not Onboarded';         Value = "$($MDEIntuneConnResp.notOnboardedDeviceCount)" }) | Out-Null }
                    if ($null -ne $MDEIntuneConnResp.pendingDeviceCount)      { $OnbRows.Add([pscustomobject]@{ Setting = 'Pending Onboarding';            Value = "$($MDEIntuneConnResp.pendingDeviceCount)" }) | Out-Null }
                    if ($OnbRows.Count -gt 0) {
                        $OnbParams = @{ Name = "MDE Onboarding Status - $TenantId"; ColumnWidths = 50, 50 }
                        if ($Report.ShowTableCaptions) { $OnbParams['Caption'] = "- $($OnbParams.Name)" }
                        $OnbRows | Table @OnbParams
                    }
                }
            }
        } catch {
            if (Test-AbrGraphForbidden -ErrorRecord $_) {
                Write-AbrPermissionError -Section 'MDE Connector' -RequiredRole 'DeviceManagementServiceConfig.Read.All'
            } else { Write-AbrSectionError -Section 'MDE Connector' -Message "$($_.Exception.Message)" }
        }

        # ── Mobile Threat Defence (MTD) Connectors ─────────────────────────────
        try {
            Write-Host " - Retrieving Mobile Threat Defence connectors..."
            $MTDResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/deviceManagement/mobileThreatDefenseConnectors" `
                -ErrorAction SilentlyContinue
            $MTDConnectors = if ($MTDResp.value) { $MTDResp.value } else { @() }

            if ($MTDConnectors -and @($MTDConnectors).Count -gt 0) {
                BlankLine
                Section -Style Heading2 'Mobile Threat Defence Connectors' {
                    Paragraph "The following documents all Mobile Threat Defence (MTD) partner connectors configured in tenant $TenantId."
                    BlankLine

                    $MTDObj = [System.Collections.ArrayList]::new()
                    foreach ($MTD in ($MTDConnectors | Sort-Object partnerFriendlyName)) {
                        $state = switch ($MTD.partnerState) {
                            'available'    { 'Connected' }
                            'notSetUp'     { 'Not Set Up' }
                            'error'        { 'Error' }
                            'unresponsive' { 'Unresponsive' }
                            default        { if ($MTD.partnerState) { $MTD.partnerState } else { '--' } }
                        }
                        $MTDObj.Add([pscustomobject]([ordered]@{
                            'Partner'            = if ($MTD.partnerFriendlyName) { $MTD.partnerFriendlyName } else { $MTD.partnerAppId }
                            'State'              = $state
                            'Android Enabled'    = if ($null -ne $MTD.androidEnabled) { if ($MTD.androidEnabled) { 'Yes' } else { 'No' } } else { '--' }
                            'iOS Enabled'        = if ($null -ne $MTD.iosEnabled)     { if ($MTD.iosEnabled)     { 'Yes' } else { 'No' } } else { '--' }
                            'Windows Enabled'    = if ($null -ne $MTD.windowsEnabled) { if ($MTD.windowsEnabled) { 'Yes' } else { 'No' } } else { '--' }
                            'Last Heartbeat'     = if ($MTD.lastHeartbeatDateTime)    { ([datetime]$MTD.lastHeartbeatDateTime).ToString('yyyy-MM-dd') } else { '--' }
                        })) | Out-Null
                    }

                    $MTDParams = @{ Name = "MTD Connectors - $TenantId"; ColumnWidths = 25, 14, 14, 12, 14, 21 }
                    if ($Report.ShowTableCaptions) { $MTDParams['Caption'] = "- $($MTDParams.Name)" }
                    $MTDObj | Table @MTDParams
                }
            }
        } catch {
            if (Test-AbrGraphForbidden -ErrorRecord $_) {
                Write-AbrPermissionError -Section 'MTD Connectors' -RequiredRole 'DeviceManagementServiceConfig.Read.All'
            } else { Write-AbrSectionError -Section 'MTD Connectors' -Message "$($_.Exception.Message)" }
        }

        # ── Defender Endpoint Security Policies (Antivirus, Firewall, EDR, ASR, Encryption) ──
        try {
            Write-Host " - Retrieving Defender Endpoint Security policies..."
            $DefESResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/deviceManagement/configurationPolicies?`$expand=assignments" `
                -ErrorAction SilentlyContinue
            $AllDefPolicies = if ($DefESResp.value) { $DefESResp.value } else { @() }
            while ($DefESResp -and $DefESResp.'@odata.nextLink') {
                $DefESResp = Invoke-MgGraphRequest -Method GET -Uri $DefESResp.'@odata.nextLink' -ErrorAction SilentlyContinue
                if ($DefESResp.value) { $AllDefPolicies += $DefESResp.value }
            }

            # Filter to Defender-relevant endpoint security policy families
            $DefenderFamilies = @('*antivirus*', '*endpointDetection*', '*attackSurface*', '*diskEncryption*', '*firewall*', '*accountProtection*')
            $DefPolicies = $AllDefPolicies | Where-Object {
                $family = $_.templateReference.templateFamily
                $family -and $family -ne 'none' -and ($DefenderFamilies | Where-Object { $family -like $_ })
            }

            if ($DefPolicies -and @($DefPolicies).Count -gt 0) {
                BlankLine
                Section -Style Heading2 'Defender Endpoint Security Policies' {
                    Paragraph "The following documents all Microsoft Defender endpoint security policies configured in tenant $TenantId, grouped by policy type."
                    BlankLine

                    $PolicyGroups = $DefPolicies | Group-Object { $_.templateReference.templateFamily }

                    foreach ($Group in ($PolicyGroups | Sort-Object Name)) {
                        $GroupLabel = switch -Wildcard ($Group.Name) {
                            '*antivirus*'         { 'Antivirus' }
                            '*diskEncryption*'    { 'Disk Encryption (BitLocker)' }
                            '*firewall*'          { 'Firewall' }
                            '*endpointDetection*' { 'Endpoint Detection & Response (EDR)' }
                            '*attackSurface*'     { 'Attack Surface Reduction (ASR)' }
                            '*accountProtection*' { 'Account Protection' }
                            default               { $Group.Name }
                        }

                        BlankLine
                        Paragraph "$GroupLabel ($(@($Group.Group).Count) policy/policies):"
                        BlankLine

                        $DefPolObj = [System.Collections.ArrayList]::new()
                        foreach ($Pol in ($Group.Group | Sort-Object name)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Pol.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            $Platform = switch ($Pol.platforms) {
                                'windows10' { 'Windows 10/11' }
                                'macOS'     { 'macOS' }
                                'linux'     { 'Linux' }
                                default     { if ($Pol.platforms) { $Pol.platforms } else { '--' } }
                            }
                            $DefPolObj.Add([pscustomobject]([ordered]@{
                                'Policy Name'   = $Pol.name
                                'Platform'      = $Platform
                                'Assignments'   = $assignResolved.AssignmentSummary
                                'Last Modified' = if ($Pol.lastModifiedDateTime) { ([datetime]$Pol.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            })) | Out-Null
                        }

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

                        $DefPolParams = @{ Name = "$GroupLabel Policies - $TenantId"; ColumnWidths = 37, 18, 28, 17 }
                        if ($Report.ShowTableCaptions) { $DefPolParams['Caption'] = "- $($DefPolParams.Name)" }
                        $DefPolObj | Table @DefPolParams

                        # Per-policy settings detail at InfoLevel 2
                        if ($InfoLevel.EndpointSecurity -ge 2) {
                            foreach ($Pol in ($Group.Group | Sort-Object name)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Pol.assignments
                                Section -Style Heading3 $Pol.name {
                                    BlankLine
                                    $PolDetailObj = [System.Collections.ArrayList]::new()
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Policy Name';       Value = $Pol.name }) | Out-Null
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Description';       Value = if ($Pol.description) { $Pol.description } else { '--' } }) | Out-Null
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Platform';          Value = if ($Pol.platforms) { $Pol.platforms } else { '--' } }) | Out-Null
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Policy Type';       Value = $GroupLabel }) | Out-Null
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Included Groups';   Value = $assignResolved.IncludedGroups }) | Out-Null
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Excluded Groups';   Value = $assignResolved.ExcludedGroups }) | Out-Null
                                    $PolDetailObj.Add([pscustomobject]@{ Setting = 'Last Modified';     Value = if ($Pol.lastModifiedDateTime) { ([datetime]$Pol.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                    $PolDetailParams = @{ Name = "Policy Detail - $($Pol.name)"; ColumnWidths = 30, 70 }
                                    if ($Report.ShowTableCaptions) { $PolDetailParams['Caption'] = "- $($PolDetailParams.Name)" }
                                    $PolDetailObj | Table @PolDetailParams

                                    # Fetch and render policy settings
                                    try {
                                        $SetResp = Invoke-MgGraphRequest -Method GET `
                                            -Uri "$($script:GraphEndpoint)/beta/deviceManagement/configurationPolicies/$($Pol.id)/settings" `
                                            -ErrorAction SilentlyContinue
                                        $Settings = if ($SetResp.value) { $SetResp.value } else { @() }
                                        if (@($Settings).Count -gt 0) {
                                            $SetRows = [System.Collections.ArrayList]::new()
                                            foreach ($s in $Settings) {
                                                $inst = $s.settingInstance
                                                if (-not $inst) { continue }
                                                $defName = if ($inst.settingDefinitionId) {
                                                    ($inst.settingDefinitionId -split '_' | Select-Object -Last 1) -creplace '([A-Z])', ' $1' -replace '^\s+', ''
                                                } else { '--' }
                                                $val = switch -Wildcard ($inst.'@odata.type') {
                                                    '*choiceSettingInstance'           { if ($inst.choiceSettingValue.value) { $inst.choiceSettingValue.value -replace '^.*_', '' } else { '--' } }
                                                    '*simpleSettingInstance'           { if ($null -ne $inst.simpleSettingValue.value) { "$($inst.simpleSettingValue.value)" } else { '--' } }
                                                    '*simpleSettingCollectionInstance' { if ($inst.simpleSettingCollectionValue) { ($inst.simpleSettingCollectionValue | ForEach-Object { $_.value }) -join ', ' } else { '--' } }
                                                    default                            { '--' }
                                                }
                                                if ($val -in @('--', 'notConfigured', 'notSet')) { continue }
                                                if ($val.Length -gt 80) { $val = "$($val.Substring(0,80))..." }
                                                $SetRows.Add([pscustomobject]([ordered]@{ 'Setting' = $defName; 'Value' = $val })) | Out-Null
                                            }
                                            if ($SetRows.Count -gt 0) {
                                                BlankLine
                                                Paragraph "Configured Settings ($($SetRows.Count) setting(s)):"
                                                BlankLine
                                                $SetParams = @{ Name = "Settings - $($Pol.name)"; ColumnWidths = 50, 50 }
                                                if ($Report.ShowTableCaptions) { $SetParams['Caption'] = "- $($SetParams.Name)" }
                                                $SetRows | Table @SetParams
                                            }
                                        }
                                    } catch { Paragraph " Could not retrieve settings: $($_.Exception.Message)" }
                                }
                            }
                        }
                    }

                    # Excel export
                    if (Get-IntuneExcelSheetEnabled -SheetKey 'EndpointSecurity') {
                        $DefExcelObj = $DefPolicies | ForEach-Object {
                            [pscustomobject]@{
                                'Policy Name'     = $_.name
                                'Platform'        = $_.platforms
                                'Template Family' = $_.templateReference.templateFamily
                                'Assignments'     = if ($_.assignments) { @($_.assignments).Count } else { 0 }
                                'Last Modified'   = if ($_.lastModifiedDateTime) { ([datetime]$_.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }
                        }
                        if (-not $script:ExcelSheets.ContainsKey('Defender Policies')) {
                            $script:ExcelSheets['Defender Policies'] = $DefExcelObj
                        }
                    }
                }
            } else {
                BlankLine
                Section -Style Heading2 'Defender Endpoint Security Policies' {
                    Paragraph "No Defender Endpoint Security policies (Antivirus, Firewall, EDR, ASR, Disk Encryption) found in tenant $TenantId."
                }
            }
        } catch {
            if (Test-AbrGraphForbidden -ErrorRecord $_) {
                Write-AbrPermissionError -Section 'Defender Endpoint Security Policies' -RequiredRole 'DeviceManagementConfiguration.Read.All'
            } else { Write-AbrSectionError -Section 'Defender Endpoint Security Policies' -Message "$($_.Exception.Message)" }
        }

    }

    end { Show-AbrDebugExecutionTime -End -TitleMessage 'Defender for Endpoint' }
}