functions/gppermissions/Test-DMGPPermission.ps1

function Test-DMGPPermission {
    <#
    .SYNOPSIS
        Tests whether the existing Group Policy permissions reflect the desired state.
 
    .DESCRIPTION
        Tests whether the existing Group Policy permissions reflect the desired state.
        Use Register-DMGPPermission and Register-DMGPPermissionFilter to define the desired state.
     
    .PARAMETER Server
        The server / domain to work with.
     
    .PARAMETER Credential
        The credentials to use for this operation.
     
    .PARAMETER EnableException
        This parameters disables user-friendly warnings and enables the throwing of exceptions.
        This is less user friendly, but allows catching exceptions in calling scripts.
 
    .EXAMPLE
        PS C:\> Test-DMGPPermission -Server corp.contoso.com
 
        Tests whether the domain of corp.contoso.com has the desired GP Permission configuration.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")]
    [CmdletBinding()]
    param (
        [PSFComputer]
        $Server,
        
        [PSCredential]
        $Credential,

        [switch]
        $EnableException
    )
    
    begin {
        $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential
        $parameters['Debug'] = $false
        Assert-ADConnection @parameters -Cmdlet $PSCmdlet
        Invoke-Callback @parameters -Cmdlet $PSCmdlet
        Assert-Configuration -Type GroupPolicyPermissions -Cmdlet $PSCmdlet
        Set-DMDomainContext @parameters
        $computerName = (Get-ADDomain @parameters).PDCEmulator
        $psParameter = $PSBoundParameters | ConvertTo-PSFHashtable -Include ComputerName, Credential -Inherit
        try { $session = New-PSSession @psParameter -ErrorAction Stop }
        catch {
            Stop-PSFFunction -String 'Test-DMGPPermission.WinRM.Failed' -StringValues $computerName -ErrorRecord $_ -EnableException $EnableException -Cmdlet $PSCmdlet -Target $computerName
            return
        }

        #region Utility Functions
        function Compare-GPAccessRules {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
            [CmdletBinding()]
            param (
                [AllowNull()]
                [AllowEmptyCollection()]
                $ADRules,

                [AllowNull()]
                [AllowEmptyCollection()]
                $ConfiguredRules,

                [bool]
                $Managed
            )
            if (-not $Managed -and -not $ConfiguredRules) { return }
            
            $configuredRules | Where-Object {
                $_ -and
                -not ($_ | Compare-ObjectProperty -ReferenceObject $ADRules -PropertyName 'Identity to String', Permission, Allow)
            } | ForEach-Object {
                $_.Action = 'Add'
                $_
            }
            if (-not $Managed) { return }
            $ADRules | Where-Object {
                $_ -and
                -not ($_ | Compare-ObjectProperty -ReferenceObject $ConfiguredRules -PropertyName 'Identity to String', Permission, Allow)
            } | ForEach-Object {
                $_.Action = 'Remove'
                $_
            }
        }
        function Convert-GPAccessRuleIdentity {
            [CmdletBinding()]
            param (
                [Parameter(ValueFromPipeline = $true)]
                $InputObject,

                $ADObject,

                [PSFComputer]
                $Server,
                
                [PSCredential]
                $Credential
            )

            begin {
                $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential
                $parameters['Debug'] = $false
            }
            process {
                foreach ($inputItem in $InputObject) {
                    #region Case: Input from AD
                    if ($inputItem.PSObject.TypeNames -like "*Microsoft.GroupPolicy.GPPermission") {
                        [PSCustomObject]@{
                            PSTypeName = 'DomainManagement.Result.GPPermission.Action'
                            Identity = $inputItem.Trustee.Sid
                            DisplayName = '{0}\{1}' -f $inputItem.Trustee.Domain, $inputItem.Trustee.Name
                            Permission = $inputItem.Permission -as [string]
                            Allow = -not $inputItem.Denied
                            Action = $null
                            ADObject = $ADObject
                        }
                    }
                    #endregion Case: Input from AD

                    #region Case: Input from Configuration
                    else {
                        # Convert to SecurityIdentifier (preferred) or NT Account
                        $identity = Resolve-Identity -IdentityReference $inputItem.Identity
                        if ($identity -is [System.Security.Principal.NTAccount]) {
                            $domainName, $identityName = $identity -replace '^(.+)@(.+)$','$2\$1' -split "\\"
                            try { $principal = Get-Principal @parameters -Name $identityName -Domain $domainName -ObjectClass $inputItem.ObjectClass }
                            catch { throw }
                            $identity = $principal.ObjectSID
                        }
                        [PSCustomObject]@{
                            PSTypeName = 'DomainManagement.Result.GPPermission.Action'
                            Identity = $identity
                            DisplayName = $inputItem.Identity
                            Permission = $inputItem.Permission
                            Allow = -not $inputItem.Deny
                            Action = $null
                            ADObject = $ADObject
                        }
                    }
                    #endregion Case: Input from Configuration
                }
            }
        }
        function Resolve-Identity {
            [CmdletBinding()]
            param (
                [string]
                $IdentityReference
            )

            #region Default Resolution
            $identity = Resolve-String -Text $IdentityReference
            if ($identity -as [System.Security.Principal.SecurityIdentifier]) {
                $identity = $identity -as [System.Security.Principal.SecurityIdentifier]
            }
            else {
                $identity = $identity -as [System.Security.Principal.NTAccount]
                try { $identity = $identity.Translate([System.Security.Principal.SecurityIdentifier])  }
                catch { $null = $null } # Do nothing intentionally, but shut up PSSA anyway
            }
            if ($null -eq $identity) { $identity = (Resolve-String -Text $IdentityReference) -as [System.Security.Principal.NTAccount] }

            $identity
            #endregion Default Resolution
        }
        #endregion Utility Functions
    }
    process {
        if (Test-PSFFunctionInterrupt) { return }
        
        try {
            #region Data Preparation
            $allFilters = @{ }
            foreach ($filterObject in (Get-DMGPPermissionFilter)) {
                $allFilters[$filterObject.Name] = $filterObject
            }

            $allPermissions = Get-DMGPPermission
            $allConditions = $allPermissions | Where-Object FilterConditions | Select-Object -ExpandProperty FilterConditions | Write-Output | Select-Object -Unique
            $missingConditions = $allConditions | Where-Object { $_ -notin $allFilters.Keys }
            if ($missingConditions) {
                Stop-PSFFunction -String 'Test-DMGPPermission.Validate.MissingFilterConditions' -StringValues ($missingConditions -join ", ") -EnableException $EnableException -Cmdlet $PSCmdlet -Tag Error, Panic
                return
            }
            if ($allConditions) { $relevantFilters = $allFilters | ConvertTo-PSFHashtable -Include $allConditions }
            else { $relevantFilters = @() }

            $allGpos = Get-ADObject @parameters -LDAPFilter '(objectCategory=groupPolicyContainer)' -Properties DisplayName

            #region Process relevant filters
            $filterToGPOMapping = @{ }
            $managedGPONames = (Get-DMGroupPolicy).DisplayName | Resolve-String
            :conditions foreach ($condition in $relevantFilters.Values) {
                switch ($condition.Type) {
                    #region Managed - Do we define the policy using the GroupPolicy Component?
                    'Managed' {
                        if ($condition.Reverse -xor (-not $condition.Managed)) { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -notin $managedGPONames }
                        else { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -in $managedGPONames }
                    }
                    #endregion Managed - Do we define the policy using the GroupPolicy Component?

                    #region Path - Resolve by where GPOs are linked
                    'Path' {
                        $searchBase = Resolve-String -Text $condition.Path
                        if (-not (Test-ADObject @parameters -Identity $searchBase)) {
                            if ($condition.Optional) {
                                Write-PSFMessage -String 'Test-DMGPPermission.Filter.Path.DoesNotExist.SilentlyContinue' -StringValues $Condition.Name, $searchBase -Target $condition
                                continue conditions
                            }
                            Stop-PSFFunction -String 'Test-DMGPPermission.Filter.Path.DoesNotExist.Stop' -StringValues $searchBase -Target $Condition.Name, $condition -EnableException $EnableException -Tag Panic, Error
                            return
                        }

                        $objects = Get-ADObject @parameters -SearchBase $searchBase -SearchScope $condition.Scope -LDAPFilter '(|(objectCategory=OrganizationalUnit)(objectCategory=domainDNS))' -Properties gPLink
                        $allLinkedGpoDNs = $objects | ConvertTo-GPLink | Select-Object -ExpandProperty DistinguishedName -Unique
                        if ($condition.Reverse) { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DistinguishedName -notin $allLinkedGpoDNs }
                        else { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DistinguishedName -in $allLinkedGpoDNs }
                    }
                    #endregion Path - Resolve by where GPOs are linked

                    #region GPName - Match by name, using either direct comparison, wildcard or regex
                    'GPName' {
                        $resolvedGpoName = Resolve-String -Text $condition.GPName
                        switch ($condition.Mode) {
                            'Explicit' {
                                if ($condition.Reverse) { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -ne $resolvedGpoName }
                                else { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -eq $resolvedGpoName }
                            }
                            'Wildcard' {
                                if ($condition.Reverse) { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -notlike $resolvedGpoName }
                                else { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -like $resolvedGpoName }
                            }
                            'Regex' {
                                if ($condition.Reverse) { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -notmatch $resolvedGpoName }
                                else { $filterToGPOMapping[$condition.Name] = $allGpos | Where-Object DisplayName -match $resolvedGpoName }
                            }
                        }
                    }
                    #endregion GPName - Match by name, using either direct comparison, wildcard or regex
                }
            }
            foreach ($key in $filterToGPOMapping.Keys) {
                Write-PSFMessage -Level Debug -String 'Test-DMGPPermission.Filter.Result' -StringValues $key, ($filterToGPOMapping[$key].DisplayName -join ', ')
            }
            #endregion Process relevant filters

            #endregion Data Preparation

            #region Process GPO Permissions
            $domainObject = Get-Domain2 @parameters
            $permissionObjects = Invoke-Command -Session $session -ScriptBlock {
                Update-TypeData -TypeName Microsoft.GroupPolicy.GPPermission -SerializationDepth 4
                foreach ($policyObject in $using:allGpos) {
                    $resultObject = [PSCustomObject]@{
                        Name        = $policyObject.DisplayName
                        Permissions = @()
                        Error       = $null
                    }

                    try { $resultObject.Permissions = Get-GPPermission -All -Name $resultObject.Name -Server localhost -Domain $using:domainObject.DNSRoot -ErrorAction Stop }
                    catch { $resultObject.Error = $_ }
                    $resultObject
                }
            }

            $resultDefaults = @{
                Server = $Server
                ObjectType = 'GPPermission'
            }

            foreach ($permissionObject in $permissionObjects) {
                $applicableSettings = $allPermissions | Where-Object {
                    $_.All -or
                    (Resolve-String -Text $_.GpoName) -eq $permissionObject.Name -or
                    ($_.Filter -and (Test-GPPermissionFilter -GpoName $permissionObject.Name -Filter $_.Filter -Conditions $_.FilterConditions -FilterHash $filterToGPOMapping))
                }
                $adObject = $allGpos | Where-Object DisplayName -eq $permissionObject.Name
                Add-Member -InputObject $permissionObject -MemberType ScriptMethod -Name ToString -Value { $this.Name } -Force

                if ($permissionObject.Error) {
                    New-TestResult @resultDefaults -Type AccessError -Identity $permissionObject -Configuration $applicableSettings -ADObject $adObject -Changed $permissionObject
                    continue
                }

                $shouldManage = $applicableSettings.Managed -contains $true
                try {
                    $compareParameter = @{
                        ADRules = ($permissionObject.Permissions | Convert-GPAccessRuleIdentity @parameters -ADObject $adObject)
                        ConfiguredRules = ($applicableSettings | Where-Object Identity | Convert-GPAccessRuleIdentity @parameters -ADObject $adObject)
                        Managed = $shouldManage
                    }
                }
                catch {
                    Stop-PSFFunction -String 'Test-DMGPPermission.Identity.Resolution.Error' -StringValues $adObject.DisplayName -Target $permissionObject -Continue -EnableException $EnableException -Tag Panic, Error
                }
                $delta = Compare-GPAccessRules @compareParameter

                if ($delta) {
                    New-TestResult @resultDefaults -Type Update -Identity $permissionObject -Changed $delta -Configuration $applicableSettings -ADObject $adObject
                    continue
                }
            }
            #endregion Process GPO Permissions
        }
        finally {
            if ($session) { $session | Remove-PSSession -WhatIf:$false -Confirm:$false}
        }
    }
}