Public/Get-GPOPermissionReport.ps1
|
function Get-GPOPermissionReport { <# .SYNOPSIS Audits Group Policy Object permissions for common security and application issues. .DESCRIPTION Examines the ACL on every GPO in the domain to detect three categories of problems: 1. MISSING APPLY PERMISSION - GPOs where neither "Authenticated Users" nor "Domain Computers" has the GpoApply right. These GPOs will silently fail to apply to any machine or user, which is the #1 GPO troubleshooting issue. 2. EXCESSIVE EDIT RIGHTS - GPOs where "Everyone" or "Domain Users" has GpoEdit or GpoEditDeleteModifySecurity rights. This is a privilege escalation risk. 3. BROKEN DELEGATION - GPOs where permission queries throw access-denied errors, indicating orphaned SIDs or corrupted ACLs. This function is read-only and never modifies GPO permissions. .PARAMETER IncludeDefaultPermissions Also include GPOs with standard/healthy permissions in the output. By default, only GPOs with issues are returned. .EXAMPLE Get-GPOPermissionReport Returns only GPOs that have permission issues. .EXAMPLE Get-GPOPermissionReport -IncludeDefaultPermissions | Format-Table Returns all GPOs including those with healthy permissions. .OUTPUTS [PSCustomObject] with properties: DisplayName, Id, Issue, AffectedPrincipal, Permission, Finding #> [CmdletBinding()] param( [Parameter()] [switch]$IncludeDefaultPermissions ) begin { Write-Verbose 'Get-GPOPermissionReport: Starting GPO permission audit' if (-not (Get-Module -ListAvailable -Name GroupPolicy)) { throw 'The GroupPolicy RSAT module is required but not installed. Install RSAT tools and try again.' } Import-Module GroupPolicy -ErrorAction Stop -Verbose:$false } process { try { $AllGPOs = @(Get-GPO -All -ErrorAction Stop) Write-Verbose "Get-GPOPermissionReport: Retrieved $($AllGPOs.Count) GPOs from domain" } catch { Write-Error "Failed to retrieve GPOs: $_" return } $IssueCount = 0 foreach ($GPO in $AllGPOs) { Write-Verbose "Get-GPOPermissionReport: Checking permissions for '$($GPO.DisplayName)'" $HasIssue = $false $HasApplyRight = $false $Issues = [System.Collections.ArrayList]::new() # Retrieve all permission entries for this GPO try { $Permissions = @(Get-GPPermission -Guid $GPO.Id -All -ErrorAction Stop) } catch { # Access denied or corrupted ACL $HasIssue = $true $IssueCount++ [void]$Issues.Add([PSCustomObject]@{ DisplayName = $GPO.DisplayName Id = $GPO.Id.ToString() Issue = 'BROKEN_DELEGATION' AffectedPrincipal = 'N/A' Permission = 'ACCESS_DENIED' Finding = 'CRITICAL' }) # Output the finding and move to the next GPO $Issues | ForEach-Object { $_ } continue } foreach ($Perm in $Permissions) { $TrusteeName = $Perm.Trustee.Name $PermLevel = $Perm.Permission # Check 1: Does any standard principal have GpoApply? if ($PermLevel -eq 'GpoApply') { if ($TrusteeName -in @('Authenticated Users', 'Domain Computers')) { $HasApplyRight = $true } } # Check 2: Excessive edit rights for broad groups if ($TrusteeName -in @('Everyone', 'Domain Users') -and $PermLevel -in @('GpoEdit', 'GpoEditDeleteModifySecurity')) { $HasIssue = $true [void]$Issues.Add([PSCustomObject]@{ DisplayName = $GPO.DisplayName Id = $GPO.Id.ToString() Issue = 'EXCESSIVE_EDIT_RIGHTS' AffectedPrincipal = $TrusteeName Permission = $PermLevel Finding = 'CRITICAL' }) } } # Check 1 result: Missing apply permission if (-not $HasApplyRight) { $HasIssue = $true [void]$Issues.Add([PSCustomObject]@{ DisplayName = $GPO.DisplayName Id = $GPO.Id.ToString() Issue = 'MISSING_APPLY_PERMISSION' AffectedPrincipal = 'Authenticated Users / Domain Computers' Permission = 'GpoApply' Finding = 'WARNING' }) } if ($HasIssue) { $IssueCount += $Issues.Count $Issues | ForEach-Object { $_ } } elseif ($IncludeDefaultPermissions) { [PSCustomObject]@{ DisplayName = $GPO.DisplayName Id = $GPO.Id.ToString() Issue = 'NONE' AffectedPrincipal = 'N/A' Permission = 'Standard' Finding = 'OK' } } } Write-Verbose "Get-GPOPermissionReport: Audit complete. Found $IssueCount permission issue(s) across $($AllGPOs.Count) GPOs." } } |