Src/Private/Get-AbrIntuneE8Summary.ps1
|
function Get-AbrIntuneE8Summary { <# .SYNOPSIS Renders the ACSC Essential Eight Maturity Level assessment chapter, structured exactly as the ACME Modern Endpoint Management design document. .DESCRIPTION Structure mirrors the design doc: 1. Intro paragraph listing the 6 pillars 2. Per-pillar Heading2 sections, each containing: - A description paragraph - A live-queried table of E8-tagged policies (Policy Name | Platform | Profile Type | Assignments) matched by "Essential 8" name prefix from Intune - Remediation scripts sub-table where applicable 3. Assessment Results sub-section with rollup counts, pillar summary, ML breakdown, and controls-requiring-attention table Pillars (matching the design doc's 6): 1. User Application Hardening 2. Restrict Microsoft Office Macros 3. Patch Applications 4. Patch Operating Systems 5. Restrict Administrative Privileges 6. Application Control Called AFTER all other sections so $script:E8AllChecks is fully populated. .NOTES Version: 0.2.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Rendering ACSC Essential Eight Summary for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'E8 Summary' } process { if (-not $script:IncludeACSCe8) { return } #───────────────────────────────────────────────────────────────────────── # Inner helpers (defined before Section block so they are in scope) #───────────────────────────────────────────────────────────────────────── # Fetch live Intune Settings Catalog + legacy config profiles matching a name pattern function Get-E8PoliciesByName { param([string[]]$Filters) $results = [System.Collections.ArrayList]::new() $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($filter in $Filters) { # Settings Catalog (configurationPolicies) try { $resp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceManagement/configurationPolicies?`$select=id,name,platforms&`$top=999" ` -ErrorAction Stop foreach ($p in $resp.value) { if ($p.name -notlike "*$filter*") { continue } if (-not $seen.Add($p.id)) { continue } $assignText = Get-E8AssignmentText -ResourceType 'configurationPolicies' -ResourceId $p.id $platform = Convert-E8Platform $p.platforms $null = $results.Add([pscustomobject]([ordered]@{ 'Policy Name' = $p.name 'Platform' = $platform 'Profile Type' = 'Settings Catalog' 'Assignments' = $assignText })) } } catch {} # Legacy device configurations try { $resp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceManagement/deviceConfigurations?`$select=id,displayName,`@odata.type&`$top=999" ` -ErrorAction Stop foreach ($p in $resp.value) { if ($p.displayName -notlike "*$filter*") { continue } if (-not $seen.Add($p.id)) { continue } $assignText = Get-E8AssignmentText -ResourceType 'deviceConfigurations' -ResourceId $p.id $profileType = Convert-E8OdataType $p.'@odata.type' $null = $results.Add([pscustomobject]([ordered]@{ 'Policy Name' = $p.displayName 'Platform' = 'Windows 10 and later' 'Profile Type' = $profileType 'Assignments' = $assignText })) } } catch {} } return @($results | Sort-Object 'Policy Name') } # Fetch E8-tagged Proactive Remediation + PS scripts function Get-E8ScriptsByName { param([string[]]$Filters) $results = [System.Collections.ArrayList]::new() $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $endpoints = @( @{ Type = 'deviceHealthScripts'; NameProp = 'displayName' }, @{ Type = 'deviceManagementScripts'; NameProp = 'displayName' } ) foreach ($ep in $endpoints) { try { $resp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceManagement/$($ep.Type)?`$select=id,displayName,description&`$top=999" ` -ErrorAction Stop foreach ($s in $resp.value) { $match = $Filters | Where-Object { $s.displayName -like "*$_*" } if (-not $match) { continue } if (-not $seen.Add($s.id)) { continue } $assignText = Get-E8AssignmentText -ResourceType $ep.Type -ResourceId $s.id $null = $results.Add([pscustomobject]([ordered]@{ 'Script Name' = $s.displayName 'Description' = if ($s.description) { $s.description } else { '--' } 'Assignment' = $assignText })) } } catch {} } return @($results | Sort-Object 'Script Name') } # Resolve assignments to a display string function Get-E8AssignmentText { param([string]$ResourceType, [string]$ResourceId) try { $aResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/beta/deviceManagement/$ResourceType/$ResourceId/assignments" ` -ErrorAction Stop $resolved = Resolve-IntuneAssignments -Assignments $aResp.value $parts = @() if ($resolved.AllUsers) { $parts += 'All Users' } if ($resolved.AllDevices) { $parts += 'All Devices' } if ($resolved.IncludedGroups -ne '--') { $parts += "Include: $($resolved.IncludedGroups)" } if ($resolved.ExcludedGroups -ne '--') { $parts += "Exclude: $($resolved.ExcludedGroups)" } return if ($parts.Count -gt 0) { $parts -join '; ' } else { 'Not Assigned' } } catch { return '--' } } # Convert platform string to human-readable function Convert-E8Platform ([string]$raw) { switch -Wildcard ($raw) { '*windows*' { return 'Windows 10 and later' } '*iOS*' { return 'iOS / iPadOS' } '*android*' { return 'Android' } '*macOS*' { return 'macOS' } default { return if ($raw) { $raw } else { 'Windows 10 and later' } } } } # Convert OData type to profile type label function Convert-E8OdataType ([string]$raw) { switch -Wildcard ($raw) { '*endpointProtection*' { return 'Endpoint Protection' } '*deviceRestrictions*' { return 'Device Restrictions' } '*adminTemplate*' { return 'Administrative Template' } '*windows10General*' { return 'General' } default { return 'Configuration Profile' } } } # Render a 4-column policy table; highlight unassigned rows function Write-E8PolicyTable { param([object[]]$Policies, [string]$TableName, [string]$EmptyMessage) if (-not $Policies -or $Policies.Count -eq 0) { Paragraph " $EmptyMessage" } else { $null = ($Policies | Where-Object { $_.Assignments -eq 'Not Assigned' } | Set-Style -Style Warning | Out-Null) $tbl = @{ Name = $TableName; ColumnWidths = 42, 20, 18, 20 } if ($Report.ShowTableCaptions) { $tbl['Caption'] = "- $TableName" } $Policies | Table @tbl } } # Render a 3-column script table function Write-E8ScriptTable { param([object[]]$Scripts, [string]$TableName) if ($Scripts -and $Scripts.Count -gt 0) { BlankLine Paragraph 'Proactive Remediation Scripts:' BlankLine $null = ($Scripts | Where-Object { $_.Assignment -eq 'Not Assigned' } | Set-Style -Style Warning | Out-Null) $tbl = @{ Name = $TableName; ColumnWidths = 30, 45, 25 } if ($Report.ShowTableCaptions) { $tbl['Caption'] = "- $TableName" } $Scripts | Table @tbl } } #───────────────────────────────────────────────────────────────────────── # Main section #───────────────────────────────────────────────────────────────────────── Section -Style Heading1 'Essential 8 Level 1,2,3 Maturity' { Paragraph "To ensure the devices meet the Australian Cyber Security Centre (ACSC) Essential 8 compliance, the following policies are configured in Microsoft Intune." BlankLine Paragraph "Essential 8 consists of 6 main pillars and the required configurations are listed in the sub-sections below:" BlankLine Paragraph " - User Application Hardening" Paragraph " - Restrict Microsoft Office Macros" Paragraph " - Patch Applications" Paragraph " - Patch Operating Systems" Paragraph " - Restrict Administrative Privileges" Paragraph " - Application Control" BlankLine # ══════════════════════════════════════════════════════════════════ # PILLAR 1 — User Application Hardening # ══════════════════════════════════════════════════════════════════ Section -Style Heading2 'User Application Hardening' { Paragraph "This pillar involves configuring web browsers and other applications to block or limit the functionality of potentially malicious features. The purpose of this strategy is to reduce the attack surface and exposure to malicious content or scripts." BlankLine try { Write-Host " - Retrieving E8 User Application Hardening policies..." $uahPol = Get-E8PoliciesByName -Filters @('Essential 8 - User Application Hardening', 'Essential 8 - User App Hardening') Write-E8PolicyTable -Policies $uahPol ` -TableName "User Application Hardening Policies - $TenantId" ` -EmptyMessage "No 'Essential 8 - User Application Hardening' policies found. Create Settings Catalog profiles for PowerShell logging, Office add-in control, Java hardening, Edge/Chrome security, and legacy feature removal (IE, PS v2, .NET 3.5)." $uahScr = Get-E8ScriptsByName -Filters @('Essential 8 - User Application Hardening', 'Essential 8 - User App Hardening') Write-E8ScriptTable -Scripts $uahScr -TableName "User Application Hardening Scripts - $TenantId" } catch { Write-AbrSectionError -Section 'E8 User Application Hardening' -Message "$($_.Exception.Message)" } } # ══════════════════════════════════════════════════════════════════ # PILLAR 2 — Restrict Microsoft Office Macros # ══════════════════════════════════════════════════════════════════ Section -Style Heading2 'Restrict Microsoft Office Macros' { Paragraph "This pillar blocks or restricts Microsoft Office macros to prevent macro-based malware. Only macros from trusted locations or digitally signed by trusted publishers are permitted. An exclusion policy allows approved users to run macros where operationally required." BlankLine try { Write-Host " - Retrieving E8 Office Macros policies..." $macroPol = Get-E8PoliciesByName -Filters @('Essential 8 - Office Hardening', 'Essential 8 - Exclusion - Office Macro', 'Essential 8 - Office Macro') Write-E8PolicyTable -Policies $macroPol ` -TableName "Office Macros Restriction Policies - $TenantId" ` -EmptyMessage "No 'Essential 8 - Office Hardening' policies found. Create policies to disable all macros, block macros from the internet, and create an exclusion policy for approved macro users." } catch { Write-AbrSectionError -Section 'E8 Office Macros' -Message "$($_.Exception.Message)" } } # ══════════════════════════════════════════════════════════════════ # PILLAR 3 — Patch Applications # ══════════════════════════════════════════════════════════════════ Section -Style Heading2 'Patch Applications' { Paragraph "This pillar ensures all software applications are maintained at the latest vendor-supported version and are free from known security vulnerabilities. Application patching is managed via a third-party patch management solution (Ivanti Patch for Intune), which integrates directly with Intune for deployment and compliance enforcement." BlankLine try { Write-Host " - Retrieving E8 Patch Applications policies..." $patchAppPol = Get-E8PoliciesByName -Filters @('Essential 8 - Patch App', 'Essential 8 - Application Patch', 'Ivanti Patch', 'Ivanti Neurons') if ($patchAppPol.Count -gt 0) { Write-E8PolicyTable -Policies $patchAppPol ` -TableName "Patch Applications Policies - $TenantId" ` -EmptyMessage '' } else { Paragraph "Application patching is managed via Ivanti Patch for Intune. Intune delivers the patch management agent and enforces patch compliance via device compliance policies. Refer to the Patch Management section for Ivanti configuration details." } $patchCompPol = Get-E8PoliciesByName -Filters @('Essential 8 - Compliance') if ($patchCompPol.Count -gt 0) { BlankLine Paragraph 'Compliance Policies (enforcing patch state):' BlankLine Write-E8PolicyTable -Policies $patchCompPol ` -TableName "Patch Compliance Policies - $TenantId" ` -EmptyMessage '' } } catch { Write-AbrSectionError -Section 'E8 Patch Applications' -Message "$($_.Exception.Message)" } } # ══════════════════════════════════════════════════════════════════ # PILLAR 4 — Patch Operating Systems # ══════════════════════════════════════════════════════════════════ Section -Style Heading2 'Patch Operating Systems' { Paragraph "This pillar ensures all devices are maintained at current supported OS versions with security patches applied regularly. Windows Autopatch is configured to ensure all devices are kept up to date with OS and security patches. To meet the 48-hour critical patch requirement, Ivanti Neurons Patch Management supplements Autopatch for out-of-band critical updates." BlankLine try { Write-Host " - Retrieving E8 Patch OS policies..." $patchOsPol = Get-E8PoliciesByName -Filters @('Essential 8 - Patch OS', 'Essential 8 - OS Patch', 'Update Ring', 'Windows Update for Business', 'Autopatch') if ($patchOsPol.Count -gt 0) { Write-E8PolicyTable -Policies $patchOsPol ` -TableName "Patch Operating System Policies - $TenantId" ` -EmptyMessage '' } else { Paragraph "OS patching is managed via Windows Autopatch update rings (configured under Devices > Windows > Update rings for Windows 10 and later). Autopatch automatically manages update delivery to ensure timely OS security updates across the device fleet." } } catch { Write-AbrSectionError -Section 'E8 Patch Operating Systems' -Message "$($_.Exception.Message)" } } # ══════════════════════════════════════════════════════════════════ # PILLAR 5 — Restrict Administrative Privileges # ══════════════════════════════════════════════════════════════════ Section -Style Heading2 'Restrict Administrative Privileges' { Paragraph "This pillar minimises the risk of system compromise by ensuring administrative privileges are granted only to authorised IT personnel and used solely for legitimate administrative tasks. Microsoft Security Baselines are complemented by Proactive Remediation scripts to enforce privilege restrictions to Essential 8 Level 3." BlankLine try { Write-Host " - Retrieving E8 Administrative Privileges policies..." $adminPol = Get-E8PoliciesByName -Filters @('Essential 8 - Admin', 'Essential 8 - Restrict Admin', 'Essential 8 - Remote Credential', 'Security Baseline', 'Microsoft Security Baseline') if ($adminPol.Count -gt 0) { Write-E8PolicyTable -Policies $adminPol ` -TableName "Restrict Administrative Privileges Policies - $TenantId" ` -EmptyMessage '' } else { Paragraph "Administrative privilege restrictions are enforced via Microsoft Security Baselines (Endpoint Security > Security Baselines) and Proactive Remediation scripts. Refer to the Endpoint Security section for deployed Security Baseline configuration details." } $adminScr = Get-E8ScriptsByName -Filters @('Essential 8 - Remote Credential', 'Essential 8 - Restrict Admin', 'Essential 8 - Admin') Write-E8ScriptTable -Scripts $adminScr -TableName "Restrict Administrative Privileges Scripts - $TenantId" } catch { Write-AbrSectionError -Section 'E8 Restrict Administrative Privileges' -Message "$($_.Exception.Message)" } } # ══════════════════════════════════════════════════════════════════ # PILLAR 6 — Application Control # ══════════════════════════════════════════════════════════════════ Section -Style Heading2 'Application Control' { Paragraph "This pillar prevents execution of unapproved or malicious software by allowing only trusted, signed applications to run. Windows Defender Application Control (WDAC) policies are deployed via Intune Endpoint Security. Attack Surface Reduction (ASR) rules provide additional protection against common attack vectors." BlankLine try { Write-Host " - Retrieving E8 Application Control policies..." $acPol = Get-E8PoliciesByName -Filters @('Essential 8 - Application Control', 'Essential 8 - App Control', 'WDAC', 'AppLocker') Write-E8PolicyTable -Policies $acPol ` -TableName "Application Control Policies - $TenantId" ` -EmptyMessage "No Application Control policies found. Deploy WDAC policies via Intune Endpoint Security > Application Control to restrict execution to trusted, signed applications." $asrPol = Get-E8PoliciesByName -Filters @('Essential 8 - ASR', 'Attack Surface Reduction') if ($asrPol.Count -gt 0) { BlankLine Paragraph 'Attack Surface Reduction (ASR) Policies:' BlankLine Write-E8PolicyTable -Policies $asrPol ` -TableName "Application Control ASR Policies - $TenantId" ` -EmptyMessage '' } } catch { Write-AbrSectionError -Section 'E8 Application Control' -Message "$($_.Exception.Message)" } } # ══════════════════════════════════════════════════════════════════ # ASSESSMENT RESULTS (accumulated check data from all sections) # ══════════════════════════════════════════════════════════════════ if ($script:E8AllChecks -and $script:E8AllChecks.Count -gt 0) { $AllChecks = @($script:E8AllChecks) Section -Style Heading2 'Assessment Results' { Paragraph "The following automated assessment evaluates Essential 8 controls against the Intune configuration collected across all report sections." BlankLine # Overall rollup $TotalOK = @($AllChecks | Where-Object { $_.Status -eq '[OK]' }).Count $TotalWarn = @($AllChecks | Where-Object { $_.Status -eq '[WARN]' }).Count $TotalFail = @($AllChecks | Where-Object { $_.Status -eq '[FAIL]' }).Count $TotalInfo = @($AllChecks | Where-Object { $_.Status -eq '[INFO]' }).Count $SummObj = [System.Collections.ArrayList]::new() $SummObj.Add([pscustomobject]@{ Result = '[OK]'; Count = $TotalOK; Description = 'Controls meeting maturity requirements' }) | Out-Null $SummObj.Add([pscustomobject]@{ Result = '[WARN]'; Count = $TotalWarn; Description = 'Controls partially configured or with recommended improvements' }) | Out-Null $SummObj.Add([pscustomobject]@{ Result = '[FAIL]'; Count = $TotalFail; Description = 'Controls not meeting maturity requirements' }) | Out-Null $SummObj.Add([pscustomobject]@{ Result = '[INFO]'; Count = $TotalInfo; Description = 'Informational checks (manual verification recommended)' }) | Out-Null $SummObj.Add([pscustomobject]@{ Result = 'Total'; Count = $AllChecks.Count; Description = 'All controls evaluated' }) | Out-Null $null = ($SummObj | Where-Object { $_.Result -eq '[OK]' } | Set-Style -Style OK | Out-Null) $null = ($SummObj | Where-Object { $_.Result -eq '[WARN]' } | Set-Style -Style Warning | Out-Null) $null = ($SummObj | Where-Object { $_.Result -eq '[FAIL]' } | Set-Style -Style Critical | Out-Null) $SummParams = @{ Name = "E8 Assessment Summary - $TenantId"; ColumnWidths = 12, 12, 76 } if ($Report.ShowTableCaptions) { $SummParams['Caption'] = "- $($SummParams.Name)" } $SummObj | Table @SummParams BlankLine # Pillar rollup $PillarMap = [ordered]@{ 'User Application Hardening' = @('ConfigurationProfiles', 'Scripts') 'Restrict Microsoft Office Macros' = @('AppManagement') 'Patch Applications' = @('DeviceCompliance', 'Devices') 'Patch Operating Systems' = @('DeviceCompliance', 'Devices') 'Restrict Administrative Privileges' = @('Scripts', 'EndpointSecurity') 'Application Control' = @('EndpointSecurity') } $PillarObj = [System.Collections.ArrayList]::new() foreach ($Pillar in $PillarMap.GetEnumerator()) { $pChecks = @($AllChecks | Where-Object { $Pillar.Value -contains $_.Section }) $pOK = @($pChecks | Where-Object { $_.Status -eq '[OK]' }).Count $pWarn = @($pChecks | Where-Object { $_.Status -eq '[WARN]' }).Count $pFail = @($pChecks | Where-Object { $_.Status -eq '[FAIL]' }).Count $pSt = if ($pChecks.Count -eq 0) { '[INFO]' } elseif ($pFail -gt 0) { '[FAIL]' } elseif ($pWarn -gt 0) { '[WARN]' } else { '[OK]' } $null = $PillarObj.Add([pscustomobject]([ordered]@{ 'Pillar' = $Pillar.Key 'Status' = $pSt 'OK' = $pOK 'Warn' = $pWarn 'Fail' = $pFail 'Total Checks' = $pChecks.Count })) } $null = ($PillarObj | Where-Object { $_.Status -eq '[OK]' } | Set-Style -Style OK | Out-Null) $null = ($PillarObj | Where-Object { $_.Status -eq '[WARN]' } | Set-Style -Style Warning | Out-Null) $null = ($PillarObj | Where-Object { $_.Status -eq '[FAIL]' } | Set-Style -Style Critical | Out-Null) $PillarParams = @{ Name = "E8 Pillar Assessment - $TenantId"; ColumnWidths = 38, 10, 9, 9, 9, 25 } if ($Report.ShowTableCaptions) { $PillarParams['Caption'] = "- $($PillarParams.Name)" } $PillarObj | Table @PillarParams BlankLine # ML breakdown foreach ($ML in @('ML1', 'ML2', 'ML3')) { $mlChecks = @($AllChecks | Where-Object { $_.ML -eq $ML }) if ($mlChecks.Count -eq 0) { continue } $mlOK = @($mlChecks | Where-Object { $_.Status -eq '[OK]' }).Count $mlF = @($mlChecks | Where-Object { $_.Status -eq '[FAIL]' }).Count $mlW = @($mlChecks | Where-Object { $_.Status -eq '[WARN]' }).Count $mlPct = [int](($mlOK / $mlChecks.Count) * 100) $mlLabel = switch ($ML) { 'ML1' {'Maturity Level 1'} 'ML2' {'Maturity Level 2'} 'ML3' {'Maturity Level 3'} } Paragraph "${mlLabel}: ${mlOK}/$($mlChecks.Count) controls passing (${mlPct}%) — ${mlF} failing, ${mlW} warnings." BlankLine $null = ($mlChecks | Where-Object { $_.Status -eq '[FAIL]' } | Set-Style -Style Critical | Out-Null) $null = ($mlChecks | Where-Object { $_.Status -eq '[WARN]' } | Set-Style -Style Warning | Out-Null) $null = ($mlChecks | Where-Object { $_.Status -eq '[OK]' } | Set-Style -Style OK | Out-Null) $MLParams = @{ Name = "E8 $ML Controls - $TenantId"; ColumnWidths = 8, 33, 9, 50 } if ($Report.ShowTableCaptions) { $MLParams['Caption'] = "- $($MLParams.Name)" } ($mlChecks | Select-Object ML, Control, Status, Detail) | Table @MLParams BlankLine } # Controls requiring attention $FailWarn = @($AllChecks | Where-Object { $_.Status -in '[FAIL]','[WARN]' } | Sort-Object ML, Status) if ($FailWarn.Count -gt 0) { Paragraph "Controls Requiring Attention ($($FailWarn.Count) item(s)):" BlankLine $null = ($FailWarn | Where-Object { $_.Status -eq '[FAIL]' } | Set-Style -Style Critical | Out-Null) $null = ($FailWarn | Where-Object { $_.Status -eq '[WARN]' } | Set-Style -Style Warning | Out-Null) $AttnParams = @{ Name = "E8 Controls Requiring Attention - $TenantId"; ColumnWidths = 8, 33, 9, 50 } if ($Report.ShowTableCaptions) { $AttnParams['Caption'] = "- $($AttnParams.Name)" } ($FailWarn | Select-Object ML, Control, Status, Detail) | Table @AttnParams } # Excel export if ((Get-IntuneExcelSheetEnabled -SheetKey 'ACSCe8Assessment') -and $AllChecks.Count -gt 0) { $script:ExcelSheets['ACSC E8 Assessment'] = $AllChecks } } } else { Write-AbrDebugLog 'E8 Summary: no assessment checks accumulated -- assessment results skipped' 'WARN' 'E8SUMMARY' } } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'E8 Summary' } } |