Src/Private/Get-AbrIntuneDeviceCompliance.ps1
|
function Get-AbrIntuneDeviceCompliance { <# .SYNOPSIS Documents Intune Device Compliance policies and their assignments. .DESCRIPTION Collects and reports on: - Compliance policy inventory (name, platform, state) - Per-policy settings summary (InfoLevel 2) - Policy assignments (groups targeted) - ACSC E8 and CIS M365 compliance assessments .NOTES Version: 0.1.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Intune Device Compliance Policies for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'Device Compliance' } process { Section -Style Heading2 'Device Compliance Policies' { Paragraph "The following section documents the Device Compliance Policies configured in tenant $TenantId." BlankLine try { Write-Host " - Retrieving compliance policies..." $PoliciesResp = Invoke-MgGraphRequest -Method GET ` -Uri "$($script:GraphEndpoint)/v1.0/deviceManagement/deviceCompliancePolicies?$expand=assignments" ` -ErrorAction Stop $Policies = $PoliciesResp.value if ($Policies -and @($Policies).Count -gt 0) { #region Compliance Policy Summary $SumObj = [System.Collections.ArrayList]::new() $UnassignedPolicies = 0 $WindowsBitLockerRequired = $false $WindowsDefenderRequired = $false $WindowsOsMinVersionConfigured = $false $MobileOsMinVersionConfigured = $false $MobileEncryptionRequired = $false foreach ($Policy in ($Policies | Sort-Object displayName)) { $OdataType = $Policy.'@odata.type' -replace '#microsoft.graph.', '' $Platform = switch -Wildcard ($OdataType) { '*Windows*' { 'Windows' } '*Ios*' { 'iOS / iPadOS' } '*Android*' { 'Android' } '*MacOs*' { 'macOS' } default { $OdataType } } # Resolve assignment groups to display names $assignResolved = Resolve-IntuneAssignments ` -Assignments $Policy.assignments ` -CheckMemberCount:$script:CheckEmptyGroups $AssignedTo = $assignResolved.AssignmentSummary if ($assignResolved.AssignmentSummary -eq 'Not assigned') { $null = ($UnassignedPolicies++) } if ($assignResolved.HasEmptyGroup) { Write-AbrDebugLog "Empty group in compliance policy '$($Policy.displayName)'" 'WARN' 'ASSIGNMENTS' } # Aggregate compliance-check metrics if ($OdataType -like '*Windows*') { if ($Policy.bitLockerEnabled -eq $true) { $WindowsBitLockerRequired = $true } if ($Policy.defenderEnabled -eq $true -or $Policy.antivirusRequired -eq $true) { $WindowsDefenderRequired = $true } if ($Policy.osMinimumVersion) { $WindowsOsMinVersionConfigured = $true } } if ($OdataType -like '*Ios*' -or $OdataType -like '*Android*') { if ($Policy.osMinimumVersion) { $MobileOsMinVersionConfigured = $true } if ($Policy.storageRequireEncryption -eq $true) { $MobileEncryptionRequired = $true } } $scopeTagStr = if ($script:ResolveScopeTagNames -and $Policy.roleScopeTagIds) { Get-IntuneScopeTagNames -ScopeTagIds $Policy.roleScopeTagIds } elseif ($Policy.roleScopeTagIds -and $Policy.roleScopeTagIds.Count -gt 0) { $Policy.roleScopeTagIds -join ', ' } else { 'Default' } $policyInObj = [ordered] @{ 'Policy Name' = $Policy.displayName 'Platform' = $Platform 'Included Groups' = $assignResolved.IncludedGroups 'Excluded Groups' = if ($script:ShowExcludedGroups) { $assignResolved.ExcludedGroups } else { $null } 'Scheduled Actions' = if ($Policy.scheduledActionsForRule) { @($Policy.scheduledActionsForRule).Count } else { 0 } 'Scope Tags' = $scopeTagStr 'Last Modified' = if ($Policy.lastModifiedDateTime) { ([datetime]$Policy.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } } # Remove null keys (hidden columns) $SumObj.Add([pscustomobject]$policyInObj) | Out-Null } $null = (& { if ($HealthCheck.Intune.DeviceCompliance) { $null = ($SumObj | Where-Object { $_.'Included Groups' -eq '--' } | Set-Style -Style Warning | Out-Null) if ($script:CheckEmptyGroups) { # Re-flag policies with empty groups $null = ($SumObj | Where-Object { $p = $Policies | Where-Object { $_.displayName -eq $_.'Policy Name' } if ($p) { $r = Resolve-IntuneAssignments -Assignments $p.assignments -CheckMemberCount:$true $r.HasEmptyGroup } } | Set-Style -Style Warning | Out-Null) } } }) $SumTableParams = @{ Name = "Compliance Policy Summary - $TenantId"; ColumnWidths = 20, 11, 18, 15, 10, 9, 17 } if ($Report.ShowTableCaptions) { $SumTableParams['Caption'] = "- $($SumTableParams.Name)" } $SumObj | Table @SumTableParams if (Get-IntuneExcelSheetEnabled -SheetKey 'CompliancePolicies') { $script:ExcelSheets['Compliance Policies'] = $SumObj } if (Get-IntuneBackupSectionEnabled -SectionKey 'CompliancePolicies') { $script:BackupData['CompliancePolicies'] = $Policies } #endregion #region Detailed per-policy settings (InfoLevel 2) if ($InfoLevel.DeviceCompliance -ge 2) { foreach ($Policy in ($Policies | Sort-Object displayName)) { $OdataType = $Policy.'@odata.type' -replace '#microsoft.graph.', '' $Platform = switch -Wildcard ($OdataType) { '*Windows*' { 'Windows' } '*Ios*' { 'iOS / iPadOS' } '*Android*' { 'Android' } '*MacOs*' { 'macOS' } default { $OdataType } } Section -Style Heading3 "$($Policy.displayName)" { BlankLine $DetailObj = [System.Collections.ArrayList]::new() $detailInObj = [ordered] @{ 'Policy Name' = $Policy.displayName 'Platform' = $Platform 'Policy Type' = $OdataType 'Created' = if ($Policy.createdDateTime) { ([datetime]$Policy.createdDateTime).ToString('yyyy-MM-dd HH:mm') } else { '--' } 'Last Modified' = if ($Policy.lastModifiedDateTime) { ([datetime]$Policy.lastModifiedDateTime).ToString('yyyy-MM-dd HH:mm') } else { '--' } } if ($OdataType -like '*Windows*') { if ($null -ne $Policy.bitLockerEnabled) { $detailInObj['BitLocker Enabled'] = $Policy.bitLockerEnabled } if ($null -ne $Policy.codeIntegrityEnabled) { $detailInObj['Code Integrity'] = $Policy.codeIntegrityEnabled } if ($null -ne $Policy.secureBootEnabled) { $detailInObj['Secure Boot'] = $Policy.secureBootEnabled } if ($null -ne $Policy.antivirusRequired) { $detailInObj['Antivirus Required'] = $Policy.antivirusRequired } if ($null -ne $Policy.defenderEnabled) { $detailInObj['Defender Enabled'] = $Policy.defenderEnabled } if ($null -ne $Policy.firewallEnabled) { $detailInObj['Firewall Enabled'] = $Policy.firewallEnabled } if ($null -ne $Policy.passwordRequired) { $detailInObj['Password Required'] = $Policy.passwordRequired } if ($null -ne $Policy.passwordMinimumLength) { $detailInObj['Min Password Length'] = $Policy.passwordMinimumLength } if ($Policy.osMinimumVersion) { $detailInObj['Min OS Version'] = $Policy.osMinimumVersion } } if ($OdataType -like '*Ios*') { if ($null -ne $Policy.passcodeRequired) { $detailInObj['Passcode Required'] = $Policy.passcodeRequired } if ($null -ne $Policy.passcodeMinimumLength) { $detailInObj['Min Passcode Length'] = $Policy.passcodeMinimumLength } if ($null -ne $Policy.jailbrokenDevice) { $detailInObj['Jailbroken Blocked'] = $Policy.jailbrokenDevice } if ($null -ne $Policy.deviceThreatProtectionEnabled) { $detailInObj['Threat Protection'] = $Policy.deviceThreatProtectionEnabled } if ($Policy.osMinimumVersion) { $detailInObj['Min OS Version'] = $Policy.osMinimumVersion } } if ($OdataType -like '*Android*') { if ($null -ne $Policy.passwordRequired) { $detailInObj['Password Required'] = $Policy.passwordRequired } if ($null -ne $Policy.securityBlockJailbrokenDevices) { $detailInObj['Rooted Blocked'] = $Policy.securityBlockJailbrokenDevices } if ($null -ne $Policy.storageRequireEncryption) { $detailInObj['Storage Encryption'] = $Policy.storageRequireEncryption } if ($null -ne $Policy.deviceThreatProtectionEnabled) { $detailInObj['Threat Protection'] = $Policy.deviceThreatProtectionEnabled } if ($Policy.osMinimumVersion) { $detailInObj['Min OS Version'] = $Policy.osMinimumVersion } } $DetailObj.Add([pscustomobject](ConvertTo-HashToYN $detailInObj)) | Out-Null $DetailTableParams = @{ Name = "Policy Settings - $($Policy.displayName)"; List = $true; ColumnWidths = 45, 55 } if ($Report.ShowTableCaptions) { $DetailTableParams['Caption'] = "- $($DetailTableParams.Name)" } $DetailObj | Table @DetailTableParams if ($Policy.assignments -and @($Policy.assignments).Count -gt 0) { BlankLine $AssignObj = [System.Collections.ArrayList]::new() foreach ($Assignment in $Policy.assignments) { $Target = $Assignment.target $TargetType = ($Target.'@odata.type' -replace '#microsoft.graph.', '') -replace 'AssignmentTarget', '' $assignInObj = [ordered] @{ 'Target Type' = $TargetType 'Group ID' = if ($Target.groupId) { $Target.groupId } else { '--' } } $AssignObj.Add([pscustomobject]$assignInObj) | Out-Null } $AssignTableParams = @{ Name = "Assignments - $($Policy.displayName)"; ColumnWidths = 40, 60 } if ($Report.ShowTableCaptions) { $AssignTableParams['Caption'] = "- $($AssignTableParams.Name)" } $AssignObj | Table @AssignTableParams } } } } #endregion #region ACSC E8 Assessment if ($script:IncludeACSCe8) { BlankLine Paragraph "ACSC Essential Eight Maturity Level Assessment -- Device Compliance Policies:" BlankLine try { $_ComplianceVars = @{ 'TotalCompliancePolicies' = @($Policies).Count 'UnassignedPolicies' = $UnassignedPolicies 'WindowsBitLockerRequired' = $WindowsBitLockerRequired 'WindowsDefenderRequired' = $WindowsDefenderRequired 'WindowsOsMinVersionConfigured' = $WindowsOsMinVersionConfigured 'MobileOsMinVersionConfigured' = $MobileOsMinVersionConfigured # Device metrics populated later in Devices section -- stub here 'NonCompliantDevices' = if ($null -ne $script:NonCompliantCount) { $script:NonCompliantCount } else { 0 } 'NonCompliantPct' = if ($null -ne $script:NonCompliantPct) { $script:NonCompliantPct } else { 0 } 'StaleDevices' = if ($null -ne $script:StaleDeviceCount) { $script:StaleDeviceCount } else { 0 } 'StaleDevicePct' = if ($null -ne $script:StaleDevicePct) { $script:StaleDevicePct } else { 0 } 'MobileEncryptionRequired' = $MobileEncryptionRequired } $E8Checks = Build-AbrIntuneComplianceChecks ` -Definitions (Get-AbrIntuneE8Checks -Section 'DeviceCompliance') ` -Framework E8 ` -CallerVariables $_ComplianceVars New-AbrIntuneE8AssessmentTable -Checks $E8Checks -Name 'Device Compliance' -TenantId $TenantId if ($E8Checks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8Checks | Select-Object @{N='Section';E={'DeviceCompliance'}}, ML, Control, Status, Detail))) } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'E8 Device Compliance Assessment' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'E8 Device Compliance Assessment' -Message "$($_.Exception.Message)" } } } #endregion #region CIS Baseline Assessment if ($script:IncludeCISBaseline) { BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- Device Compliance Policies:" BlankLine try { $_ComplianceVars = @{ 'TotalCompliancePolicies' = @($Policies).Count 'UnassignedPolicies' = $UnassignedPolicies 'NonCompliantDevices' = if ($null -ne $script:NonCompliantCount) { $script:NonCompliantCount } else { 0 } 'NonCompliantPct' = if ($null -ne $script:NonCompliantPct) { $script:NonCompliantPct } else { 0 } 'WindowsBitLockerRequired' = $WindowsBitLockerRequired 'MobileEncryptionRequired' = $MobileEncryptionRequired } $CISChecks = Build-AbrIntuneComplianceChecks ` -Definitions (Get-AbrIntuneCISChecks -Section 'DeviceCompliance') ` -Framework CIS ` -CallerVariables $_ComplianceVars New-AbrIntuneCISAssessmentTable -Checks $CISChecks -Name 'Device Compliance' -TenantId $TenantId if ($CISChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISChecks | Select-Object @{N='Section';E={'DeviceCompliance'}}, CISControl, Level, Status, Detail))) } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'CIS Device Compliance Assessment' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'CIS Device Compliance Assessment' -Message "$($_.Exception.Message)" } } } #endregion } else { Paragraph "No Device Compliance Policies found in tenant $TenantId." } } catch { if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Device Compliance Policies' -RequiredRole 'Intune Service Administrator or Global Administrator' } else { Write-AbrSectionError -Section 'Device Compliance Policies' -Message "$($_.Exception.Message)" } } } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'Device Compliance' } } |