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."
    }
}