DscResources/ActiveDirectoryAccessEntry/ActiveDirectoryAccessEntry.psm1

Import-Module -Name (Join-Path -Path ( Split-Path $PSScriptRoot -Parent ) `
                               -ChildPath 'AccessControlResourceHelper\AccessControlResourceHelper.psm1') `
                               -Force

# Localized messages
data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData -StringData @'
        ErrorPathNotFound = The requested path "{0}" cannot be found.
        AclNotFound = Error obtaining "{0}" ACL
        AclFound = Obtained "{0}" ACL
        RemoveAccessError = "Unable to remove Access for "{0}"
'@

}

Function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.String]
        $DistinguishedName,

        [Parameter(Mandatory=$true)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $AccessControlList
    )
    
    Assert-Module -ModuleName 'ActiveDirectory'
    Import-Module -Name 'ActiveDirectory' -Verbose:$false -force
    
    $namespace = "root/Microsoft/Windows/DesiredStateConfiguration"
    $cimAccessControlList = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]'

    $path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName

    if(Test-Path -Path $path)
    {
        $currentAcl = Get-Acl -Path $path

        if($null -ne $currentAcl)
        {
            $message = $LocalizedData.AclFound -f $path
            Write-Verbose -Message $message
            
            foreach($principal in $AccessControlList)
            {
                $cimAccessControlEntry = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]'

                $principalName = $principal.Principal
                $forcePrincipal = $principal.ForcePrincipal

                $identity = Resolve-Identity -Identity $principalName
                $currentPrincipalAccess = $currentAcl.Access.Where({$_.IdentityReference -eq $identity.Name})

                foreach($access in $currentPrincipalAccess)
                {
                    $accessControlType = $access.AccessControlType.ToString()
                    $activeDirectoryRights = $access.ActiveDirectoryRights.ToString().Split(',').Trim()
                    $inheritanceType = $access.InheritanceType.ToString()
                    $inheritedObjectType = $access.InheritedObjectType.ToString()
                    $objectType = $access.ObjectType.ToString()

                    $cimAccessControlEntry += New-CimInstance -ClientOnly -Namespace $namespace -ClassName ActiveDirectoryAccessRule -Property @{
                                AccessControlType = $accessControlType
                                ActiveDirectoryRights = @($activeDirectoryRights)
                                InheritanceType = $inheritanceType
                                InheritedObjectType = $inheritedObjectType
                                ObjectType = $objectType
                                Ensure = ""
                            }
                }

                $CimAccessControlList += New-CimInstance -ClientOnly -Namespace $namespace -ClassName ActiveDirectoryAccessControlList -Property @{
                                Principal = $principalName
                                ForcePrincipal = $forcePrincipal
                                AccessControlEntry = [Microsoft.Management.Infrastructure.CimInstance[]]@($cimAccessControlEntry)
                            }
            }
        }
        else
        {
            $message = $LocalizedData.AclNotFound -f $path
            Write-Verbose -Message $message
        }
    }
    else
    {
        $Message = $LocalizedData.ErrorPathNotFound -f $path
        Write-Verbose -Message $Message
    }

    $ReturnValue = @{
        DistinguishedName = $DistinguishedName
        AccessControlList = $CimAccessControlList
    }

    return $ReturnValue
}

Function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.String]
        $DistinguishedName,

        [Parameter(Mandatory=$true)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $AccessControlList
    )
 
    Assert-Module -ModuleName 'ActiveDirectory'
    Import-Module -Name 'ActiveDirectory' -Verbose:$false -force
 
    $path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName
    
    if(Test-Path -Path $path)
    {
        $currentAcl = Get-Acl -Path $path
        if($null -ne $currentAcl)
        {
            foreach($accessControlItem in $AccessControlList)
            {
                $principal = $accessControlItem.Principal
                $identity = Resolve-Identity -Identity $principal
                $identityRef = New-Object System.Security.Principal.NTAccount($identity.Name)

                $actualAce = $currentAcl.Access.Where({$_.IdentityReference -eq $identity.Name})

                $aclRules = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $accessControlItem -IdentityRef $identityRef
                $results = Compare-ActiveDirectoryAccessRule -Expected $aclRules -Actual $actualAce

                $expected += $results.Rules
                $absentToBeRemoved += $results.Absent

                if($accessControlItem.ForcePrinciPal)
                {
                    $toBeRemoved += $results.ToBeRemoved
                }
            }

            $isInherited = 0
            $isInherited += $absentToBeRemoved.Rule.Where({$_.IsInherited -eq $true}).Count
            $isInherited += $toBeRemoved.Rule.Where({$_.IsInherited -eq $true}).Count

            if($isInherited -gt 0)
            {
                $currentAcl.SetAccessRuleProtection($true,$true)
                Set-Acl -Path $path -AclObject $currentAcl
            }

            foreach($rule in $expected)
            {
                if($rule.Match -eq $false)
                {
                    $nonMatch = $rule.Rule
                    ("Adding Access rule:"),
                    ("> Path : '{0}'" -f $path),
                    ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference),
                    ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights),
                    ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType),
                    ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType),
                    ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)),
                    ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) |
                    Write-Verbose

                    $currentAcl.AddAccessRule($rule.Rule)
                }
            }

            foreach($rule in $absentToBeRemoved)
            {
                $nonMatch = $rule.Rule
                ("Removing Access rule:"),
                ("> Path : '{0}'" -f $path),
                ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference),
                ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights),
                ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType),
                ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType),
                ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)),
                ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) |
                Write-Verbose

                $currentAcl.RemoveAccessRule($rule.Rule)
            }

            foreach($rule in $toBeRemoved)
            {
                $nonMatch = $rule.Rule
                ("Removing Access rule:"),
                ("> Path : '{0}'" -f $path),
                ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference),
                ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights),
                ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType),
                ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType),
                ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)),
                ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) |
                Write-Verbose
                $currentAcl.RemoveAccessRule($rule.Rule)
            }

            Set-Acl -Path $path -AclObject $currentAcl
        }
        else
        {
            $message = $LocalizedData.AclNotFound -f $path
            Write-Verbose -Message $message
        }
    }
    else
    {
        $Message = $LocalizedData.ErrorPathNotFound -f $path
        Write-Verbose -Message $Message
    }
}

Function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory=$true)]
        [System.String]
        $DistinguishedName,

        [Parameter(Mandatory=$true)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $AccessControlList
    )

    Assert-Module -ModuleName 'ActiveDirectory'
    Import-Module -Name 'ActiveDirectory' -Verbose:$false -force

    $inDesiredState = $True
    $path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName
    
    if(Test-Path -Path $path)
    {
        $currentAcl = Get-Acl -Path $path

        if($null -ne $currentAcl)
        {
            foreach($accessControlItem in $AccessControlList)
            {
                $principal = $accessControlItem.Principal
                $identity = Resolve-Identity -Identity $principal
                $identityRef = New-Object System.Security.Principal.NTAccount($identity.Name)

                $aclRules = ConvertTo-ActiveDirectoryAccessRule -AccessControlList $accessControlItem -IdentityRef $identityRef

                $actualAce = $currentAcl.Access.Where({$_.IdentityReference -eq $identity.Name})

                $results = Compare-ActiveDirectoryAccessRule -Expected $aclRules -Actual $actualAce

                $expected += $results.Rules
                $absentToBeRemoved += $results.Absent

                if($accessControlItem.ForcePrincipal)
                {
                    $toBeRemoved += $results.ToBeRemoved
                }

            }

            foreach($rule in $expected)
            {
                if($rule.Match -eq $false)
                {
                    $nonMatch = $rule.Rule
                    ("Found missing [present] Access rule:"),
                    ("> Principal : '{0}'" -f $principal),
                    ("> Path : '{0}'" -f $path),
                    ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference),
                    ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights),
                    ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType),
                    ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType),
                    ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)),
                    ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) |
                    Write-Verbose

                    $inDesiredState = $False
                }
            }

            if($absentToBeRemoved.Count -gt 0)
            {
                foreach($rule in $absentToBeRemoved)
                {
                    $nonMatch = $rule.Rule
                    ("Found [absent] Access rule:"),
                    ("> Principal : '{0}'" -f $principal),
                    ("> Path : '{0}'" -f $path),
                    ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference),
                    ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights),
                    ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType),
                    ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType),
                    ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)),
                    ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) |
                    Write-Verbose
                    $inDesiredState = $False
                }
            }

            if($toBeRemoved.Count -gt 0)
            {
                foreach($rule in $toBeRemoved)
                {
                    $nonMatch = $rule.Rule
                    ("Non-matching Access rule found:"),
                    ("> Principal : '{0}'" -f $principal),
                    ("> Path : '{0}'" -f $path),
                    ("> IdentityReference : '{0}'" -f $nonMatch.IdentityReference),
                    ("> ActiveDirectoryRights : '{0}'" -f $nonMatch.ActiveDirectoryRights),
                    ("> AccessControlType : '{0}'" -f $nonMatch.AccessControlType),
                    ("> InheritanceType : '{0}'" -f $nonMatch.InheritanceType),
                    ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.InheritedObjectType)),
                    ("> ObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $nonMatch.ObjectType)) |
                    Write-Verbose
                    $inDesiredState = $False                   
                }
            }
        }
        else
        {
            $message = $LocalizedData.AclNotFound -f $path
            Write-Verbose -Message $message
            $inDesiredState = $False
        }
    }
    else
    {
        $message = $LocalizedData.ErrorPathNotFound -f $path
        Write-Verbose -Message $Message
        $inDesiredState = $False
    }
    
    return $inDesiredState
}

Function ConvertTo-ActiveDirectoryAccessRule
{
    param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]
        $AccessControlList,

        [Parameter(Mandatory = $true)]
        [System.Security.Principal.NTAccount]
        $IdentityRef
    )

    $referenceObject = @()

    foreach($ace in $AccessControlList.AccessControlEntry)
    {
        $inheritedObjectType = Get-DelegationRightsGuid -ObjectName $ace.InheritedObjectType
        $objectType = Get-DelegationRightsGuid -ObjectName $ace.ObjectType
        $rule = [PSCustomObject]@{
            Rules = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($IdentityRef, $ace.ActiveDirectoryRights, $ace.AccessControlType, $objectType, $ace.InheritanceType, $inheritedObjectType)
            Ensure = $ace.Ensure
        }
        $referenceObject += $rule
    }

    return $referenceObject
}

Function Compare-ActiveDirectoryAccessRule
{
    param
    (
        [Parameter(Mandatory = $true)]
        [PSCustomObject[]]
        $Expected,

        [Parameter()]
        [System.DirectoryServices.ActiveDirectoryAccessRule[]]
        $Actual
    )

    $results = @()
    $toBeRemoved = @()
    $absentToBeRemoved = @()

    $presentRules = $Expected.Where({$_.Ensure -eq 'Present'}).Rules
    $absentRules = $Expected.Where({$_.Ensure -eq 'Absent'}).Rules
    foreach($referenceObject in $presentRules)
    {
        $match = $Actual.Where({
            $_.ActiveDirectoryRights -eq $referenceObject.ActiveDirectoryRights -and
            $_.AccessControlType -eq $referenceObject.AccessControlType -and
            $_.InheritanceType -eq $referenceObject.InheritanceType -and
            $_.InheritedObjectType -eq $referenceObject.InheritedObjectType -and
            $_.ObjectType -eq $referenceObject.ObjectType -and
            $_.IdentityReference.Value -eq $referenceObject.IdentityReference.Value
        })
        if($match.Count -ge 1)
        {
            $results += [PSCustomObject]@{
                Rule = $referenceObject
                Match = $true
            }
        }
        else
        {
            $results += [PSCustomObject]@{
                Rule = $referenceObject
                Match = $false
            }
        }
    }

    foreach($referenceObject in $absentRules)
    {
        $match = $Actual.Where({
            $_.ActiveDirectoryRights -eq $referenceObject.ActiveDirectoryRights -and
            $_.AccessControlType -eq $referenceObject.AccessControlType -and
            $_.InheritanceType -eq $referenceObject.InheritanceType -and
            $_.InheritedObjectType -eq $referenceObject.InheritedObjectType -and
            $_.ObjectType -eq $referenceObject.ObjectType -and
            $_.IdentityReference.Value -eq $referenceObject.IdentityReference.Value
        })
        if($match.Count -gt 0)
        {
            $absentToBeRemoved += [PSCustomObject]@{
                Rule = $referenceObject
            }
        }
    }

    foreach($referenceObject in $Actual)
    {
        $match = $Expected.Rules.Where({
            $_.ActiveDirectoryRights -eq $referenceObject.ActiveDirectoryRights -and
            $_.AccessControlType -eq $referenceObject.AccessControlType -and
            $_.InheritanceType -eq $referenceObject.InheritanceType -and
            $_.InheritedObjectType -eq $referenceObject.InheritedObjectType -and
            $_.ObjectType -eq $referenceObject.ObjectType -and
            $_.IdentityReference.Value -eq $referenceObject.IdentityReference.Value
        })
        if($match.Count -eq 0)
        {
            $toBeRemoved += [PSCustomObject]@{
                Rule = $referenceObject
            }
        }
    }

    return [PSCustomObject]@{
        Rules = $results
        ToBeRemoved = $toBeRemoved
        Absent = $absentToBeRemoved
    }
}