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' } } |