DscResources/ActiveDirectoryAuditRuleEntry/ActiveDirectoryAuditRuleEntry.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,

        [Parameter()]
        [bool]
        $Force = $false
    )
    
    Assert-Module -ModuleName 'ActiveDirectory'
    Import-Module -Name 'ActiveDirectory' -Verbose:$false
    
    $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 -Audit -ErrorAction Stop

        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.Audit.Where({$_.IdentityReference -eq $Identity.Name})

                foreach($Access in $currentPrincipalAccess)
                {
                    $AuditFlags = $Access.AuditFlags.ToString()
                    $ActiveDirectoryRights = $Access.ActiveDirectoryRights.ToString().Split(',').Trim()
                    $InheritanceType = $Access.InheritanceType.ToString()
                    $InheritedObjectType = $Access.InheritedObjectType.ToString()

                    $CimAccessControlEntry += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName ActiveDirectoryAuditRule -Property @{
                                ActiveDirectoryRights = @($ActiveDirectoryRights)
                                AuditFlags = $AuditFlags
                                InheritanceType = $InheritanceType
                                InheritedObjectType = $InheritedObjectType
                                Ensure = ""
                            }
                }

                $CimAccessControlList += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName ActiveDirectorySystemAccessControlList -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 = @{
        Force = $Force
        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,

        [Parameter()]
        [bool]
        $Force = $false
    )
 
    Assert-Module -ModuleName 'ActiveDirectory'
    Import-Module -Name 'ActiveDirectory' -Verbose:$false
 
    $Path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName
    
    if(Test-Path -Path $Path)
    {
        $currentAcl = Get-Acl -Path $Path -Audit
        if($null -ne $currentAcl)
        {
            if($Force)
            {
                foreach($AccessControlItem in $AccessControlList)
                {
                    $Principal = $AccessControlItem.Principal
                    $Identity = Resolve-Identity -Identity $Principal
                    $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name)

                    $ACLRules += ConvertTo-ActiveDirectoryAuditRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef
                }    
        
                $actualAce = $currentAcl.Audit

                $Results = Compare-ActiveDirectoryAuditRule -Expected $ACLRules -Actual $actualAce

                $Expected = $Results.Rules
                $AbsentToBeRemoved = $Results.Absent
                $ToBeRemoved = $Results.ToBeRemoved
            }
            else
            {
                foreach($AccessControlItem in $AccessControlList)
                {
                    $Principal = $AccessControlItem.Principal
                    $Identity = Resolve-Identity -Identity $Principal
                    $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name)

                    $actualAce = $currentAcl.Audit.Where({$_.IdentityReference -eq $Identity.Name})

                    $ACLRules = ConvertTo-ActiveDirectoryAuditRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef
                    $Results = Compare-ActiveDirectoryAuditRule -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.SetAuditRuleProtection($true,$true)
                Set-Acl -Path $Path -AclObject $currentAcl
            }

            foreach($Rule in $Expected)
            {
                if($Rule.Match -eq $false)
                {
                    $NonMatch = $Rule.Rule
                    ("Adding audit rule:"),
                    ("> Path : '{0}'" -f $Path),
                    ("> IdentityReference : '{0}'" -f $NonMatch.IdentityReference),
                    ("> ActiveDirectoryRights : '{0}'" -f $NonMatch.ActiveDirectoryRights),
                    ("> AuditFlags : '{0}'" -f $NonMatch.AuditFlags),
                    ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType),
                    ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) |
                    Write-Verbose

                    $currentAcl.AddAuditRule($Rule.Rule)
                }
            }

            foreach($Rule in $AbsentToBeRemoved.Rule)
            {
                $NonMatch = $Rule.Rule
                ("Removing audit rule:"),
                ("> Path : '{0}'" -f $Path),
                ("> IdentityReference : '{0}'" -f $NonMatch.IdentityReference),
                ("> ActiveDirectoryRights : '{0}'" -f $NonMatch.ActiveDirectoryRights),
                ("> AuditFlags : '{0}'" -f $NonMatch.AuditFlags),
                ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType),
                ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) |
                Write-Verbose

                $currentAcl.RemoveAuditRule($Rule)
            }

            foreach($Rule in $ToBeRemoved.Rule)
            {
                $NonMatch = $Rule.Rule
                ("Removing audit rule:"),
                ("> Path : '{0}'" -f $Path),
                ("> IdentityReference : '{0}'" -f $NonMatch.IdentityReference),
                ("> ActiveDirectoryRights : '{0}'" -f $NonMatch.ActiveDirectoryRights),
                ("> AuditFlags : '{0}'" -f $NonMatch.AuditFlags),
                ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType),
                ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) |
                Write-Verbose
                $currentAcl.RemoveAuditRule($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,

        [Parameter()]
        [bool]
        $Force = $false
    )

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

    $InDesiredState = $True
    $Path = Join-Path -Path "ad:\" -ChildPath $DistinguishedName
    
    if(Test-Path -Path $Path)
    {
        $currentACL = Get-Acl -Path $Path -Audit

        if($null -ne $currentACL)
        {
            if($Force)
            {
                foreach($AccessControlItem in $AccessControlList)
                {
                    $Principal = $AccessControlItem.Principal
                    $Identity = Resolve-Identity -Identity $Principal
                    $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name)

                    $ACLRules += ConvertTo-ActiveDirectoryAuditRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef
                }    
        
                $actualAce = $currentAcl.Audit

                $Results = Compare-ActiveDirectoryAuditRule -Expected $ACLRules -Actual $actualAce

                $Expected = $Results.Rules
                $AbsentToBeRemoved = $Results.Absent
                $ToBeRemoved = $Results.ToBeRemoved
            }
            else
            {
                foreach($AccessControlItem in $AccessControlList)
                {
                    $Principal = $AccessControlItem.Principal
                    $Identity = Resolve-Identity -Identity $Principal
                    $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name)

                    $ACLRules = ConvertTo-ActiveDirectoryAuditRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef

                    $actualAce = $currentAcl.Audit.Where({$_.IdentityReference -eq $Identity.Name})

                    $Results = Compare-ActiveDirectoryAuditRule -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] audit rule:"),
                    ("> Principal : '{0}'" -f $Principal),
                    ("> Path : '{0}'" -f $Path),
                    ("> IdentityReference : '{0}'" -f $NonMatch.IdentityReference),
                    ("> ActiveDirectoryRights : '{0}'" -f $NonMatch.ActiveDirectoryRights),
                    ("> AuditFlags : '{0}'" -f $NonMatch.AuditFlags),
                    ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType),
                    ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) |
                    Write-Verbose

                    $InDesiredState = $False
                }
            }

            if($AbsentToBeRemoved.Count -gt 0)
            {
                $NonMatch = $Rule.Rule
                ("Found [absent] audit rule:"),
                ("> Principal : '{0}'" -f $Principal),
                ("> Path : '{0}'" -f $Path),
                ("> IdentityReference : '{0}'" -f $NonMatch.IdentityReference),
                ("> ActiveDirectoryRights : '{0}'" -f $NonMatch.ActiveDirectoryRights),
                ("> AuditFlags : '{0}'" -f $NonMatch.AuditFlags),
                ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType),
                ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType))|
                Write-Verbose

                $InDesiredState = $False
            }

            if($ToBeRemoved.Count -gt 0)
            {
                $NonMatch = $Rule.Rule
                ("Non-matching audit rule found:"),
                ("> Principal : '{0}'" -f $Principal),
                ("> Path : '{0}'" -f $Path),
                ("> IdentityReference : '{0}'" -f $NonMatch.IdentityReference),
                ("> ActiveDirectoryRights : '{0}'" -f $NonMatch.ActiveDirectoryRights),
                ("> AuditFlags : '{0}'" -f $NonMatch.AuditFlags),
                ("> InheritanceType : '{0}'" -f $NonMatch.InheritanceType),
                ("> InheritedObjectType : '{0}'" -f $(Get-SchemaObjectName -SchemaIdGuid $NonMatch.InheritedObjectType)) |
                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-ActiveDirectoryAuditRule
{
    param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.Management.Infrastructure.CimInstance]
        $AccessControlList,

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

    $refrenceObject = @()

    foreach($ace in $AccessControlList.AccessControlEntry)
    {
        $InheritedObjectType = Get-SchemaIdGuid -ObjectName $ace.InheritedObjectType
        $rule = [PSCustomObject]@{
            Rules = New-Object System.DirectoryServices.ActiveDirectoryAuditRule($IdentityRef, $ace.ActiveDirectoryRights, $ace.AuditFlags, $ace.InheritanceType, $InheritedObjectType)
            Ensure = $ace.Ensure
        }
        $refrenceObject += $rule
    }

    return $refrenceObject
}

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

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

    $results = @()
    $ToBeRemoved = @()
    $AbsentToBeRemoved = @()

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

    foreach($refrenceObject in $AbsentRules)
    {
        $match = $Actual.Where({
            $_.ActiveDirectoryRights -eq $refrenceObject.ActiveDirectoryRights -and
            $_.AuditFlags -eq $refrenceObject.AuditFlags -and
            $_.InheritanceType -eq $refrenceObject.InheritanceType -and
            $_.InheritedObjectType -eq $refrenceObject.InheritedObjectType -and
            $_.IdentityReference -eq $refrenceObject.IdentityReference
        })
        if($match.Count -gt 0)
        {
            $AbsentToBeRemoved += [PSCustomObject]@{
                Rule = $refrenceObject
            }
        }
    }

    foreach($refrenceObject in $Actual)
    {
        $match = $Expected.Rules.Where({
            $_.ActiveDirectoryRights -eq $refrenceObject.ActiveDirectoryRights -and
            $_.AuditFlags -eq $refrenceObject.AuditFlags -and
            $_.InheritanceType -eq $refrenceObject.InheritanceType -and
            $_.InheritedObjectType -eq $refrenceObject.InheritedObjectType -and
            $_.IdentityReference -eq $refrenceObject.IdentityReference
        })
        if($match.Count -eq 0)
        {
            $ToBeRemoved += [PSCustomObject]@{
                Rule = $refrenceObject
            }
        }
    }

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

function Assert-Module
{
    [CmdletBinding()]
    param
    (
        [Parameter()] [ValidateNotNullOrEmpty()]
        [System.String] $ModuleName = 'ActiveDirectory'
    )

    if (-not (Get-Module -Name $ModuleName -ListAvailable))
    {
        $errorId = '{0}_ModuleNotFound' -f $ModuleName;
        $errorMessage = $localizedString.RoleNotFoundError -f $moduleName;
        ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage;
    }
} 

Function Get-SchemaIdGuid
{
    Param 
    (
        [Parameter()]
        [string]
        $ObjectName
    )

    if($ObjectName)
    {
        $value = Get-ADObject -filter {name -eq $ObjectName} -SearchBase (Get-ADRootDSE).schemaNamingContext -prop schemaIDGUID
        return [guid]$value.schemaIDGUID 
    }
    else
    {
        return [system.guid]"00000000-0000-0000-0000-000000000000"
    }
}

Function Get-SchemaObjectName
{
    Param 
    (
        [Parameter()]
        [guid]
        $SchemaIdGuid
    )

        $value = Get-ADObject -filter {schemaIDGUID  -eq $SchemaIdGuid} -SearchBase (Get-ADRootDSE).schemaNamingContext -prop schemaIDGUID
        return $value.name 
}