Src/Private/Get-AbrIntuneEndpointSecurity.ps1
|
function Get-AbrIntuneEndpointSecurity { <# .SYNOPSIS Documents Intune Endpoint Security policies (Antivirus, Firewall, Disk Encryption, EDR, ASR). .DESCRIPTION Collects and reports on: - Antivirus policies - Disk Encryption policies - Firewall policies - Endpoint Detection and Response (EDR) policies - Attack Surface Reduction (ASR) policies - Account Protection policies .NOTES Version: 0.1.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Intune Endpoint Security policies for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'Endpoint Security' } process { Section -Style Heading2 'Endpoint Security' { Paragraph "The following section documents Endpoint Security policies configured in tenant $TenantId." BlankLine try { Write-Host " - Retrieving Endpoint Security policies..." # Endpoint Security policies use the configurationPolicies endpoint with specific templateFamilies # /beta required -- v1.0 configurationPolicies does not support $filter on templateReference # Also $filter on templateFamily not supported server-side in v1.0; filter client-side instead $ESPoliciesResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceManagement/configurationPolicies?`$expand=assignments" ` -ErrorAction SilentlyContinue $AllConfigPolicies = $ESPoliciesResp.value # Page through all results while ($ESPoliciesResp -and $ESPoliciesResp.'@odata.nextLink') { $ESPoliciesResp = Invoke-MgGraphRequest -Method GET -Uri $ESPoliciesResp.'@odata.nextLink' -ErrorAction SilentlyContinue if ($ESPoliciesResp.value) { $AllConfigPolicies += $ESPoliciesResp.value } } # Filter client-side: keep only endpoint security policies (non-'none' templateFamily) $ESPolicies = $AllConfigPolicies | Where-Object { $_.templateReference -and $_.templateReference.templateFamily -and $_.templateReference.templateFamily -ne 'none' -and $_.templateReference.templateFamily -ne '' } if ($ESPolicies -and @($ESPolicies).Count -gt 0) { # Group by templateFamily $PolicyGroups = $ESPolicies | Group-Object { $_.templateReference.templateFamily } foreach ($Group in ($PolicyGroups | Sort-Object Name)) { $GroupLabel = switch -Wildcard ($Group.Name) { '*antivirus*' { 'Antivirus' } '*diskEncryption*' { 'Disk Encryption' } '*firewall*' { 'Firewall' } '*endpointDetection*' { 'Endpoint Detection & Response (EDR)' } '*attackSurface*' { 'Attack Surface Reduction (ASR)' } '*accountProtection*' { 'Account Protection' } default { $Group.Name } } Section -Style Heading3 "$GroupLabel Policies" { BlankLine $EPObj = [System.Collections.ArrayList]::new() foreach ($ESPolicy in ($Group.Group | Sort-Object name)) { $Platform = switch ($ESPolicy.platforms) { 'windows10' { 'Windows 10/11' } 'macOS' { 'macOS' } 'linux' { 'Linux' } default { if ($ESPolicy.platforms) { $ESPolicy.platforms } else { '--' } } } $assignResolved = Resolve-IntuneAssignments -Assignments $ESPolicy.assignments -CheckMemberCount:$script:CheckEmptyGroups $AssignedTo = $assignResolved.AssignmentSummary $epInObj = [ordered] @{ 'Policy Name' = $ESPolicy.name 'Platform' = $Platform 'Assignments' = $AssignedTo 'Last Modified' = if ($ESPolicy.lastModifiedDateTime) { ([datetime]$ESPolicy.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } } $EPObj.Add([pscustomobject]$epInObj) | Out-Null } $null = (& { if ($HealthCheck.Intune.EndpointSecurity) { $null = ($EPObj | Where-Object { $_.'Assignments' -eq 'Not assigned' } | Set-Style -Style Warning | Out-Null) } }) $EPTableParams = @{ Name = "$GroupLabel - $TenantId"; ColumnWidths = 35, 20, 28, 17 } if ($Report.ShowTableCaptions) { $EPTableParams['Caption'] = "- $($EPTableParams.Name)" } $EPObj | Table @EPTableParams } } # Excel export (flattened) if (Get-IntuneBackupSectionEnabled -SectionKey 'EndpointSecurity') { $script:BackupData['EndpointSecurity'] = $ESPolicies } #region InfoLevel 2 -- per-policy detail + settings if ($InfoLevel.EndpointSecurity -ge 2) { foreach ($ESPolicy in ($ESPolicies | Sort-Object displayName)) { $assignResolved = Resolve-IntuneAssignments -Assignments $ESPolicy.assignments $templateFamily = if ($ESPolicy.templateReference.templateFamily) { $ESPolicy.templateReference.templateFamily } else { '--' } $ESPolicyLabel = if ($ESPolicy.displayName) { $ESPolicy.displayName } elseif ($ESPolicy.name) { $ESPolicy.name } else { 'Unnamed Policy' } Section -Style Heading3 $ESPolicyLabel { BlankLine $ovObj = [System.Collections.ArrayList]::new() $ovObj.Add([pscustomobject]@{ Setting = 'Policy Name'; Value = $ESPolicyLabel }) | Out-Null $ovObj.Add([pscustomobject]@{ Setting = 'Description'; Value = if ($ESPolicy.description) { $ESPolicy.description } else { '--' } }) | Out-Null $ovObj.Add([pscustomobject]@{ Setting = 'Platform'; Value = if ($ESPolicy.platforms) { $ESPolicy.platforms } else { '--' } }) | Out-Null $ovObj.Add([pscustomobject]@{ Setting = 'Template Family'; Value = $templateFamily }) | Out-Null $ovObj.Add([pscustomobject]@{ Setting = 'Included Groups'; Value = $assignResolved.IncludedGroups }) | Out-Null $ovObj.Add([pscustomobject]@{ Setting = 'Excluded Groups'; Value = $assignResolved.ExcludedGroups }) | Out-Null $ovObj.Add([pscustomobject]@{ Setting = 'Last Modified'; Value = if ($ESPolicy.lastModifiedDateTime) { ([datetime]$ESPolicy.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null $OvParams = @{ Name = "Policy Detail - $ESPolicyLabel"; ColumnWidths = 30, 70 } if ($Report.ShowTableCaptions) { $OvParams['Caption'] = "- $($OvParams.Name)" } $ovObj | Table @OvParams # Fetch ALL settings pages with paging support $ResolveName = { param($defId, $defsColl) if ($defsColl) { $m = $defsColl | Where-Object { $_.id -eq $defId } | Select-Object -First 1; if ($m -and $m.displayName) { return $m.displayName } } $part = $defId -replace '^.*_policy_config_[^_]+_','' -replace '^.*_policy_','' -replace '^[^_]+_[^_]+_[^_]+_[^_]+_','' $s = $part -creplace '([a-z])([A-Z])','$1 $2' -replace '_',' ' return ($s.Substring(0,1).ToUpper() + $s.Substring(1)) } $ResolveCategory = { param($defId, $defsColl) if ($defsColl) { $m = $defsColl | Where-Object { $_.id -eq $defId } | Select-Object -First 1 if ($m -and $m.categoryId) { $catId = $m.categoryId if ($catId -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') { if (-not $script:CatGuidNameCache) { $script:CatGuidNameCache = [System.Collections.Generic.Dictionary[string,string]]::new([System.StringComparer]::OrdinalIgnoreCase) } if ($script:CatGuidNameCache.ContainsKey($catId)) { return $script:CatGuidNameCache[$catId] } try { $catApiResp = $null try { $catApiResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceManagement/configurationCategories/$catId`?`$select=displayName" -ErrorAction Stop } catch { } $catDn = if ($catApiResp -is [System.Collections.IDictionary]) { $catApiResp['displayName'] } else { $catApiResp.displayName } if ($catDn) { $script:CatGuidNameCache[$catId] = $catDn; return $catDn } } catch { } $script:CatGuidNameCache[$catId] = $catId; return $catId } $cat = $catId -replace '^.*_config_','' -replace '_',' '; $cat = $cat -creplace '([a-z])([A-Z])','$1 $2'; return ($cat.Substring(0,1).ToUpper() + $cat.Substring(1)) } } if ($defId -match '_policy_config_([^_]+)_') { $cat = $Matches[1] -creplace '([a-z])([A-Z])','$1 $2' -replace '_',' '; return ($cat.Substring(0,1).ToUpper() + $cat.Substring(1)) } return 'General' } $RenderChoice = { param($choiceVal, $defsColl) if (-not $choiceVal -or -not $choiceVal.value) { return '--' } $raw = $choiceVal.value -replace '^.*_','' $label = switch ($raw) { '0' { 'Disabled' } '1' { 'Enabled' } '2' { 'Audit Mode' } '3' { 'Block' } 'true' { 'Enabled' } 'false' { 'Disabled' } 'enabled' { 'Enabled' } 'disabled' { 'Disabled' } 'allowed' { 'Allowed' } 'blocked' { 'Blocked' } 'notConfigured' { 'Not Configured' } 'warn' { 'Warn' } 'auditMode' { 'Audit Mode' } 'blockMode' { 'Block' } 'userDefined' { 'User Defined' } default { $t = $raw -creplace '([a-z])([A-Z])','$1 $2'; $t.Substring(0,1).ToUpper() + $t.Substring(1) } } if ($choiceVal.children -and $choiceVal.children.Count -gt 0) { $parts = @(); foreach ($child in $choiceVal.children) { $cName = & $ResolveName $child.settingDefinitionId $defsColl $cVal = if ($child.simpleSettingValue) { "$($child.simpleSettingValue.value)" } elseif ($child.simpleSettingCollectionValue) { ($child.simpleSettingCollectionValue | ForEach-Object { $_.value }) -join '; ' } else { '' } if ($cVal) { $parts += "$cName`: $cVal" } } if ($parts.Count -gt 0) { $label = "$label`n" + ($parts -join "`n") } } return $label } $RenderGroup = { param($groupVal, $defsColl) if (-not $groupVal) { return '--' } $parts = @() foreach ($grp in $groupVal) { if ($grp.children) { foreach ($child in $grp.children) { $cName = & $ResolveName $child.settingDefinitionId $defsColl $cVal = if ($child.choiceSettingValue) { & $RenderChoice $child.choiceSettingValue $defsColl } elseif ($child.simpleSettingValue) { "$($child.simpleSettingValue.value)" } elseif ($child.simpleSettingCollectionValue) { ($child.simpleSettingCollectionValue | ForEach-Object { $_.value }) -join '; ' } else { '' } if ($cVal -and $cVal -ne 'Not Configured') { $parts += "$cName`: $cVal" } } } } $groupResult = if ($parts.Count -gt 0) { $parts -join "`n" } else { 'Configured' } return $groupResult } $PolicySettings = [System.Collections.ArrayList]::new() $esSettingsUri = "$($script:GraphEndpoint)/beta/deviceManagement/configurationPolicies/$($ESPolicy.id)/settings?`$expand=settingDefinitions" do { try { $EsPageResp = Invoke-MgGraphRequest -Method GET -Uri $esSettingsUri -ErrorAction Stop if ($EsPageResp.value) { $null = $PolicySettings.AddRange([object[]]$EsPageResp.value) } $esSettingsUri = $EsPageResp.'@odata.nextLink' } catch { Write-AbrDebugLog "ES settings unavailable for '$ESPolicyLabel': $($_.Exception.Message)" 'WARN' 'EndpointSecurity' $esSettingsUri = $null } } while ($esSettingsUri) if ($PolicySettings.Count -gt 0) { $catGroups = [ordered]@{} foreach ($setting in $PolicySettings) { $instance = $setting.settingInstance; if (-not $instance) { continue } $defs = $setting.settingDefinitions $defName = & $ResolveName $instance.settingDefinitionId $defs $category = & $ResolveCategory $instance.settingDefinitionId $defs $sv = switch -Wildcard ($instance.'@odata.type') { '*choiceSettingInstance' { & $RenderChoice $instance.choiceSettingValue $defs } '*simpleSettingInstance' { if ($instance.simpleSettingValue) { "$($instance.simpleSettingValue.value)" } else { '--' } } '*simpleSettingCollectionInstance' { if ($instance.simpleSettingCollectionValue) { ($instance.simpleSettingCollectionValue | ForEach-Object { $_.value }) -join ', ' } else { '--' } } '*groupSettingCollectionInstance' { & $RenderGroup $instance.groupSettingCollectionValue $defs } '*choiceSettingCollectionInstance' { if ($instance.choiceSettingCollectionValue) { ($instance.choiceSettingCollectionValue | ForEach-Object { (& $RenderChoice $_ $defs) }) -join ', ' } else { '--' } } default { $defId = $instance.settingDefinitionId if ($defId -and $defId -like '*reusable*') { $refId = if ($instance.simpleSettingValue) { "$($instance.simpleSettingValue.value)" } else { $instance.settingDefinitionId } try { $reuseResp = $null try { $reuseResp = Invoke-MgGraphRequest -Method GET -Uri "$($script:GraphEndpoint)/beta/deviceManagement/reusablePolicySettings/$refId" -ErrorAction Stop } catch { } if ($reuseResp -and $reuseResp.displayName) { "Reusable group: $($reuseResp.displayName)" } else { 'Reusable group (ref)' } } catch { 'Reusable group (ref)' } } else { '--' } } } if ($sv -eq '--' -or $sv -eq 'Not Configured' -or [string]::IsNullOrWhiteSpace($sv)) { continue } if (-not $catGroups.Contains($category)) { $catGroups[$category] = [System.Collections.ArrayList]::new() } $catGroups[$category].Add([pscustomobject]([ordered]@{ 'Setting' = $defName; 'Value' = $sv })) | Out-Null } if ($catGroups.Count -gt 0) { $allEsRows = [System.Collections.ArrayList]::new() foreach ($cat in $catGroups.Keys) { $allEsRows.Add([pscustomobject]([ordered]@{ 'Setting' = $cat; 'Value' = '' })) | Out-Null $allEsRows.AddRange($catGroups[$cat]) } BlankLine Paragraph "Configured Settings ($(@($allEsRows | Where-Object { $_.Value -ne '' }).Count) setting(s)):" BlankLine $null = ($allEsRows | Where-Object { $_.Value -eq '' } | Set-Style -Style 'TableSectionHeader') $EsSetParams = @{ Name = "Settings - $ESPolicyLabel"; ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $EsSetParams['Caption'] = "- $($EsSetParams.Name)" } $allEsRows | Table @EsSetParams } else { Paragraph "No explicitly configured settings found (all values are at template defaults)." } } } } } #endregion if (Get-IntuneExcelSheetEnabled -SheetKey 'EndpointSecurity') { $ESExcelObj = $ESPolicies | 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 { '--' } } } $script:ExcelSheets['Endpoint Security'] = $ESExcelObj } } else { Paragraph "No Endpoint Security policies found in tenant $TenantId." } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Endpoint Security' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'Endpoint Security' -Message "$($_.Exception.Message)" } } #region ACSC E8 Assessment if ($script:IncludeACSCe8) { BlankLine Paragraph "ACSC Essential Eight Maturity Level Assessment -- Endpoint Security:" BlankLine try { # Count policies by category from already-collected $ESPolicies $TotalAntivirusPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*antivirus*' }).Count } else { 0 } $TotalFirewallPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*firewall*' }).Count } else { 0 } $TotalDiskEncryptionPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*diskEncryption*' }).Count } else { 0 } $TotalASRPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*attackSurface*' }).Count } else { 0 } $TotalEDRPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*endpointDetection*' }).Count } else { 0 } # Store for other sections $null = ($script:TotalAntivirusPolicies = $TotalAntivirusPolicies) $null = ($script:TotalFirewallPolicies = $TotalFirewallPolicies) $null = ($script:TotalDiskEncryptionPolicies = $TotalDiskEncryptionPolicies) $null = ($script:TotalASRPolicies = $TotalASRPolicies) $null = ($script:TotalEDRPolicies = $TotalEDRPolicies) $_v = @{ TotalAntivirusPolicies = $TotalAntivirusPolicies TotalFirewallPolicies = $TotalFirewallPolicies TotalDiskEncryptionPolicies = $TotalDiskEncryptionPolicies TotalASRPolicies = $TotalASRPolicies TotalEDRPolicies = $TotalEDRPolicies } $E8Checks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneE8Checks -Section 'EndpointSecurity') -Framework E8 -CallerVariables $_v New-AbrIntuneE8AssessmentTable -Checks $E8Checks -Name 'Endpoint Security' -TenantId $TenantId if ($E8Checks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8Checks | Select-Object @{N='Section';E={'EndpointSecurity'}}, ML, Control, Status, Detail))) } } catch { Write-AbrSectionError -Section 'E8 Endpoint Security Assessment' -Message "$($_.Exception.Message)" } } #endregion #region CIS Assessment if ($script:IncludeCISBaseline) { BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- Endpoint Security:" BlankLine try { $TotalAntivirusPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*antivirus*' }).Count } else { 0 } $TotalFirewallPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*firewall*' }).Count } else { 0 } $TotalDiskEncryptionPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*diskEncryption*' }).Count } else { 0 } $TotalASRPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*attackSurface*' }).Count } else { 0 } $TotalEDRPolicies = if ($ESPolicies) { @($ESPolicies | Where-Object { $_.templateReference.templateFamily -like '*endpointDetection*' }).Count } else { 0 } $_v = @{ TotalAntivirusPolicies = $TotalAntivirusPolicies TotalFirewallPolicies = $TotalFirewallPolicies TotalDiskEncryptionPolicies = $TotalDiskEncryptionPolicies TotalASRPolicies = $TotalASRPolicies TotalEDRPolicies = $TotalEDRPolicies } $CISChecks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneCISChecks -Section 'EndpointSecurity') -Framework CIS -CallerVariables $_v New-AbrIntuneCISAssessmentTable -Checks $CISChecks -Name 'Endpoint Security' -TenantId $TenantId if ($CISChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISChecks | Select-Object @{N='Section';E={'EndpointSecurity'}}, CISControl, Level, Status, Detail))) } } catch { Write-AbrSectionError -Section 'CIS Endpoint Security Assessment' -Message "$($_.Exception.Message)" } } #endregion } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'Endpoint Security' } } |