Public/Get-IntunePolicyAssignmentReview.ps1
|
function Get-IntunePolicyAssignmentReview { <# .SYNOPSIS Reviews Intune configuration profiles and compliance policies for assignment gaps. .DESCRIPTION Enumerates device configuration profiles and compliance policies, checks for unassigned policies, policies assigned to empty groups, and conflicting policy settings. Helps identify configuration drift and policy sprawl. .PARAMETER IncludeCompliance Include compliance policies in addition to configuration profiles. Defaults to true. .EXAMPLE Get-IntunePolicyAssignmentReview #> [CmdletBinding()] param( [Parameter()] [bool]$IncludeCompliance = $true ) begin { Test-GraphConnection $results = [System.Collections.Generic.List[PSObject]]::new() } process { # --- Configuration Profiles --- Write-Verbose "Retrieving device configuration profiles..." $configProfiles = Get-MgDeviceManagementDeviceConfiguration -All -Property @( 'id', 'displayName', '@odata.type', 'lastModifiedDateTime', 'createdDateTime', 'version' ) foreach ($profile in $configProfiles) { $findings = @() $profileType = ($profile.AdditionalProperties.'@odata.type' ?? $profile.'@odata.type') -replace '#microsoft.graph.', '' # Get assignments try { $assignments = Get-MgDeviceManagementDeviceConfigurationAssignment -DeviceConfigurationId $profile.Id -ErrorAction Stop $assignmentCount = ($assignments | Measure-Object).Count } catch { $assignments = @() $assignmentCount = 0 } if ($assignmentCount -eq 0) { $findings += 'UNASSIGNED' } # Check assignment targets $targetTypes = @() foreach ($assignment in $assignments) { $targetType = $assignment.Target.AdditionalProperties.'@odata.type' -replace '#microsoft.graph.', '' $targetTypes += $targetType if ($targetType -eq 'allDevicesAssignmentTarget') { $findings += 'ASSIGNED TO ALL DEVICES' } if ($targetType -eq 'allLicensedUsersAssignmentTarget') { $findings += 'ASSIGNED TO ALL USERS' } } # Check for stale profiles if ($profile.LastModifiedDateTime) { $daysSinceModified = [math]::Round(((Get-Date) - $profile.LastModifiedDateTime).TotalDays) if ($daysSinceModified -gt 365) { $findings += "NOT MODIFIED IN $daysSinceModified DAYS" } } # Get device status summary try { $statusOverview = Get-MgDeviceManagementDeviceConfigurationDeviceStatusOverview -DeviceConfigurationId $profile.Id -ErrorAction Stop $errorCount = $statusOverview.ErrorCount $conflictCount = $statusOverview.ConflictCount if ($errorCount -gt 0) { $findings += "ERRORS: $errorCount devices" } if ($conflictCount -gt 0) { $findings += "CONFLICTS: $conflictCount devices" } } catch { } $results.Add([PSCustomObject]@{ PolicyName = $profile.DisplayName PolicyType = 'Configuration Profile' ProfileType = $profileType Version = $profile.Version Assignments = $assignmentCount TargetTypes = ($targetTypes | Select-Object -Unique) -join ', ' LastModified = $profile.LastModifiedDateTime Errors = if ($errorCount) { $errorCount } else { 0 } Conflicts = if ($conflictCount) { $conflictCount } else { 0 } Finding = if ($findings.Count -gt 0) { $findings -join ' | ' } else { 'OK' } }) } # --- Compliance Policies --- if ($IncludeCompliance) { Write-Verbose "Retrieving device compliance policies..." $compliancePolicies = Get-MgDeviceManagementDeviceCompliancePolicy -All -Property @( 'id', 'displayName', '@odata.type', 'lastModifiedDateTime', 'createdDateTime', 'version' ) foreach ($policy in $compliancePolicies) { $findings = @() $policyOdataType = ($policy.AdditionalProperties.'@odata.type' ?? $policy.'@odata.type') -replace '#microsoft.graph.', '' try { $assignments = Get-MgDeviceManagementDeviceCompliancePolicyAssignment -DeviceCompliancePolicyId $policy.Id -ErrorAction Stop $assignmentCount = ($assignments | Measure-Object).Count } catch { $assignments = @() $assignmentCount = 0 } if ($assignmentCount -eq 0) { $findings += 'UNASSIGNED' } $targetTypes = @() foreach ($assignment in $assignments) { $targetType = $assignment.Target.AdditionalProperties.'@odata.type' -replace '#microsoft.graph.', '' $targetTypes += $targetType } try { $statusOverview = Get-MgDeviceManagementDeviceCompliancePolicyDeviceStatusOverview -DeviceCompliancePolicyId $policy.Id -ErrorAction Stop $errorCount = $statusOverview.ErrorCount $conflictCount = $statusOverview.ConflictCount $nonCompliantCount = $statusOverview.NonCompliantCount if ($errorCount -gt 0) { $findings += "ERRORS: $errorCount" } if ($conflictCount -gt 0) { $findings += "CONFLICTS: $conflictCount" } if ($nonCompliantCount -gt 0) { $findings += "NON-COMPLIANT: $nonCompliantCount devices" } } catch { $errorCount = 0 $conflictCount = 0 $nonCompliantCount = 0 } $results.Add([PSCustomObject]@{ PolicyName = $policy.DisplayName PolicyType = 'Compliance Policy' ProfileType = $policyOdataType Version = $policy.Version Assignments = $assignmentCount TargetTypes = ($targetTypes | Select-Object -Unique) -join ', ' LastModified = $policy.LastModifiedDateTime Errors = $errorCount Conflicts = $conflictCount Finding = if ($findings.Count -gt 0) { $findings -join ' | ' } else { 'OK' } }) } } } end { $flagged = $results | Where-Object { $_.Finding -ne 'OK' } Write-Host " Policies reviewed: $($results.Count) | Findings: $($flagged.Count)" -ForegroundColor Gray $results | Sort-Object PolicyType, Finding, PolicyName } } |