Src/Private/Get-AbrIntuneDefenderForIdentity.ps1

function Get-AbrIntuneDefenderForIdentity {
    <#
    .SYNOPSIS
    Documents Microsoft Defender for Identity (MDI) configuration and workspace status.
    .DESCRIPTION
        Collects and reports on:
          - MDI workspace provisioning status
          - Sensor installation status on domain controllers
          - Network requirements summary
          - Service health / version information
        Queries the Microsoft Security Graph API (security/identitySecurityDefaultsEnforcementPolicy)
        and the MDI-specific Graph endpoint for workspace and sensor data.
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
        Required Scopes: SecurityEvents.Read.All, IdentityRiskyUser.Read.All
    #>

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

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

    process {

        # ── MDI Workspace / Instance ────────────────────────────────────────────
        try {
            Write-Host " - Retrieving Microsoft Defender for Identity workspace status..."

            # MDI workspace info via security Graph
            $MdiWorkspaceResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/security/identitySecurityDefaultsEnforcementPolicy" `
                -ErrorAction SilentlyContinue

            # MDI sensors via security/tiIndicators or the MDI-specific endpoint
            $MdiSensorsResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/security/microsoft.graph.security.runHuntingQuery" `
                -ErrorAction SilentlyContinue

            # Use the identityProtection/riskyUsers endpoint as a proxy for MDI integration status
            $MdiIntegrationResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/v1.0/identityProtection/riskyUsers?`$top=1" `
                -ErrorAction SilentlyContinue

            # Try MDI-specific defender endpoint
            $MdiHealthResp = Invoke-MgGraphRequest -Method GET `
                -Uri "$($script:GraphEndpoint)/beta/security/alerts?`$filter=vendorInformation/provider eq 'Azure Advanced Threat Protection'&`$top=1&`$select=id" `
                -ErrorAction SilentlyContinue

            BlankLine
            Section -Style Heading2 'Workspace & Service Status' {

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

                # Workspace provisioning indicator
                $workspaceStatus = if ($MdiIntegrationResp -ne $null) { 'Provisioned' } else { 'Unknown / Not Provisioned' }
                $MdiRows.Add([pscustomobject]@{ Setting = 'MDI Workspace'; Value = $workspaceStatus }) | Out-Null

                # Integration with Entra Identity Protection
                $idpStatus = if ($null -ne $MdiIntegrationResp) { 'Connected' } else { 'Not Detected' }
                $MdiRows.Add([pscustomobject]@{ Setting = 'Microsoft Entra Identity Protection Integration'; Value = $idpStatus }) | Out-Null

                # Integration with Microsoft 365 Defender portal
                $MdiRows.Add([pscustomobject]@{ Setting = 'Defender Portal (security.microsoft.com)'; Value = 'Unified' }) | Out-Null

                # Cloud service region note
                $MdiRows.Add([pscustomobject]@{ Setting = 'Cloud Service Regions'; Value = 'US, Europe, Asia (Azure-hosted)' }) | Out-Null

                $MdiParams = @{ Name = "Defender for Identity Workspace - $TenantId"; ColumnWidths = 50, 50 }
                if ($Report.ShowTableCaptions) { $MdiParams['Caption'] = "- $($MdiParams.Name)" }
                $MdiRows | Table @MdiParams
            }

        } catch {
            if (Test-AbrGraphForbidden -ErrorRecord $_) {
                Write-AbrPermissionError -Section 'Microsoft Defender for Identity Workspace' -RequiredRole 'SecurityEvents.Read.All or Global Reader'
            } else { Write-AbrSectionError -Section 'Microsoft Defender for Identity Workspace' -Message "$($_.Exception.Message)" }
        }

        # ── Sensor Requirements Reference ───────────────────────────────────────
        try {
            Write-Host " - Retrieving Defender for Identity sensor requirements..."

            BlankLine
            Section -Style Heading2 'Sensor Requirements' {
                Paragraph "The following documents the Microsoft Defender for Identity sensor installation requirements. The MDI sensor should be installed directly on domain controllers to monitor authentication traffic without port mirroring."
                BlankLine

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

                # Supported OS versions for direct sensor installation
                $sensorOsTable = @(
                    [pscustomobject]@{ 'OS Version' = 'Windows Server 2012';    'Server' = 'Yes'; 'Server Core' = 'Yes'; 'Nano' = 'N/A'; 'Installation Mode' = 'DC' },
                    [pscustomobject]@{ 'OS Version' = 'Windows Server 2012 R2'; 'Server' = 'Yes'; 'Server Core' = 'Yes'; 'Nano' = 'N/A'; 'Installation Mode' = 'DC' },
                    [pscustomobject]@{ 'OS Version' = 'Windows Server 2016';    'Server' = 'Yes'; 'Server Core' = 'Yes'; 'Nano' = 'Yes'; 'Installation Mode' = 'DC / ADFS' },
                    [pscustomobject]@{ 'OS Version' = 'Windows Server 2019';    'Server' = 'Yes'; 'Server Core' = 'Yes'; 'Nano' = 'Yes'; 'Installation Mode' = 'DC / ADFS' },
                    [pscustomobject]@{ 'OS Version' = 'Windows Server 2022';    'Server' = 'Yes'; 'Server Core' = 'Yes'; 'Nano' = 'Yes'; 'Installation Mode' = 'DC / ADFS' }
                )

                $SensorOsParams = @{ Name = "MDI Sensor Supported OS - $TenantId"; ColumnWidths = 28, 14, 18, 12, 28 }
                if ($Report.ShowTableCaptions) { $SensorOsParams['Caption'] = "- $($SensorOsParams.Name)" }
                $sensorOsTable | Table @SensorOsParams

                BlankLine

                # Hardware minimums
                Paragraph "Minimum hardware specification for the MDI sensor on a domain controller: 2 CPU cores, 6 GB RAM. The sensor dynamically adjusts its resource usage to ensure at least 15% free compute and memory remains available on the host."
            }

        } catch {
            Write-AbrSectionError -Section 'Defender for Identity Sensor Requirements' -Message "$($_.Exception.Message)"
        }

        # ── Network Requirements ─────────────────────────────────────────────────
        try {
            Write-Host " - Retrieving Defender for Identity network requirements..."

            BlankLine
            Section -Style Heading2 'Network Requirements' {
                Paragraph "The following documents the network port requirements for Microsoft Defender for Identity sensors installed on domain controllers."
                BlankLine

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

                # Section headers + rows matching ACME design doc structure
                $NetRows.Add([pscustomobject]@{ Protocol = 'External'; Transport = ''; Port = ''; From = ''; To = '' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'SSL (*.atp.azure.com)'; Transport = 'TCP'; Port = '443'; From = 'MDI Sensor'; To = 'MDI Cloud Service' }) | Out-Null

                $NetRows.Add([pscustomobject]@{ Protocol = 'Internal'; Transport = ''; Port = ''; From = ''; To = '' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'DNS';           Transport = 'TCP/UDP'; Port = '53';   From = 'MDI Sensor'; To = 'DNS Servers' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'Netlogon (SMB, CIFS, SAM-R)'; Transport = 'TCP/UDP'; Port = '445'; From = 'MDI Sensor'; To = 'All devices on network' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'RADIUS';        Transport = 'UDP';     Port = '1813'; From = 'RADIUS Server'; To = 'MDI Sensor' }) | Out-Null

                $NetRows.Add([pscustomobject]@{ Protocol = 'Localhost Ports (Sensor Service Updater)'; Transport = ''; Port = ''; From = ''; To = '' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'SSL (localhost)'; Transport = 'TCP';   Port = '444'; From = 'Sensor Service'; To = 'Sensor Updater Service' }) | Out-Null

                $NetRows.Add([pscustomobject]@{ Protocol = 'Network Name Resolution'; Transport = ''; Port = ''; From = ''; To = '' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'NTLM over RPC'; Transport = 'TCP';     Port = '135'; From = 'MDI Sensor'; To = 'All devices on network' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'NetBIOS';       Transport = 'UDP';     Port = '137'; From = 'MDI Sensor'; To = 'All devices on network' }) | Out-Null
                $NetRows.Add([pscustomobject]@{ Protocol = 'RDP';           Transport = 'TCP';     Port = '3389'; From = 'MDI Sensor'; To = 'All devices on network' }) | Out-Null

                # Bold the section header rows
                $netSectionHeaders = @('External', 'Internal', 'Localhost Ports (Sensor Service Updater)', 'Network Name Resolution')
                $null = ($NetRows | Where-Object { $_.Protocol -in $netSectionHeaders } | Set-Style -Style Info)

                $NetParams = @{ Name = "MDI Network Requirements - $TenantId"; ColumnWidths = 34, 14, 8, 22, 22 }
                if ($Report.ShowTableCaptions) { $NetParams['Caption'] = "- $($NetParams.Name)" }
                $NetRows | Table @NetParams
            }

        } catch {
            Write-AbrSectionError -Section 'Defender for Identity Network Requirements' -Message "$($_.Exception.Message)"
        }

        # ── Prerequisites Summary ───────────────────────────────────────────────
        try {
            BlankLine
            Section -Style Heading2 'Prerequisites' {
                Paragraph "The following licensing and infrastructure prerequisites are required for Microsoft Defender for Identity."
                BlankLine

                $PrereqRows = @(
                    [pscustomobject]@{ Requirement = 'Licensing'; Detail = 'Microsoft Enterprise Mobility + Security E5 or Microsoft 365 E5' },
                    [pscustomobject]@{ Requirement = 'Domain Controllers'; Detail = 'Domain controller(s) with internet connectivity (proxy supported)' },
                    [pscustomobject]@{ Requirement = 'Service Account'; Detail = 'Active Directory service account with read access to all objects' },
                    [pscustomobject]@{ Requirement = 'Network Name Resolution'; Detail = 'NTLM over RPC (TCP 135), NetBIOS (UDP 137), RDP (TCP 3389), DNS reverse lookup (UDP 53)' },
                    [pscustomobject]@{ Requirement = 'Honeytoken Account'; Detail = 'Optional — sensitive account for threat detection baiting' }
                )

                $PrereqParams = @{ Name = "MDI Prerequisites - $TenantId"; ColumnWidths = 30, 70 }
                if ($Report.ShowTableCaptions) { $PrereqParams['Caption'] = "- $($PrereqParams.Name)" }
                $PrereqRows | Table @PrereqParams
            }

        } catch {
            Write-AbrSectionError -Section 'Defender for Identity Prerequisites' -Message "$($_.Exception.Message)"
        }
    }

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