DSCResources/cNtfsPermissionEntry/cNtfsPermissionEntry.psm1

<#
Author : Serge Nikalaichyk (https://www.linkedin.com/in/nikalaichyk)
Version : 1.1.1
Date : 2015-10-15
#>



function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([Hashtable])]
    param
    (
        [Parameter(Mandatory = $false)]
        [ValidateSet('Absent', 'Present')]
        [String]
        $Ensure = 'Present',

        [Parameter(Mandatory = $true)]
        [String]
        $Path,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Directory', 'File')]
        [String]
        $ItemType,

        [Parameter(Mandatory = $true)]
        [String]
        $Principal,

        [Parameter(Mandatory = $false)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $AccessControlInformation
    )

    $PSBoundParameters.GetEnumerator() |
    ForEach-Object -Begin {
        $Width = $PSBoundParameters.Keys.Length | Sort-Object -Descending | Select-Object -First 1
    } -Process {
        "{0,-$($Width)} : '{1}'" -f $_.Key, ($_.Value -join ', ') |
        Write-Verbose
    }

    if ($ItemType -eq 'Directory')
    {
        $PathType = 'Container'
    }
    else
    {
        $PathType = 'Leaf'
    }

    if (Test-Path -Path $Path -PathType $PathType)
    {
        $Acl = Get-Acl -Path $Path -ErrorAction Stop
    }
    else
    {
        throw "Could not find the item of type '$ItemType' at the specified path: '$Path'."
    }

    $Identity = Resolve-IdentityReference -Identity $Principal -ErrorAction Stop

    [System.Security.AccessControl.FileSystemAccessRule[]]$AccessRules = @(
            $Acl.Access | Where-Object {$_.IsInherited -eq $false -and $_.IdentityReference -eq $Identity.Name}
        )

    Write-Verbose -Message "Current Permission Entry Count : $($AccessRules.Count)"

    $CimAccessRules = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]'

    if ($AccessRules.Count -ne 0)
    {
        $AccessRules |
        ConvertFrom-FileSystemAccessRule -ItemType $ItemType |
        ForEach-Object {

            $CimAccessRule = New-CimInstance -ClientOnly -Namespace 'root/Microsoft/Windows/DesiredStateConfiguration' -ClassName cNtfsAccessControlInformation `
                -Property @{AccessControlType = $_.AccessControlType; FileSystemRights = $_.FileSystemRights; Inheritance = $_.Inheritance; NoPropagateInherit = $_.NoPropagateInherit}

            $CimAccessRules.Add($CimAccessRule)

        }
    }

    if ($Ensure -eq 'Absent')
    {
        if ($AccessRules.Count -eq 0)
        {
            $EnsureResult = 'Absent'
        }
        else
        {
            $EnsureResult = 'Present' 
        }
    }
    else
    {
        $EnsureResult = 'Present'

        [PSCustomObject[]]$PermissionEntries = @()

        if ($AccessControlInformation.Count -eq 0)
        {
            Write-Verbose -Message "The AccessControlInformation collection is either null or empty. The default permission entry will be used as the reference entry."

            $PermissionEntries += [PSCustomObject]@{
                    AccessControlType = 'Allow'
                    FileSystemRights = 'ReadAndExecute'
                    Inheritance = $null
                    NoPropagateInherit = $false
                }
        }
        else
        {
            foreach ($Item in $AccessControlInformation)
            {
                $AccessControlType = $Item.CimInstanceProperties['AccessControlType'].Value
                $FileSystemRights = $Item.CimInstanceProperties['FileSystemRights'].Value
                $Inheritance = $Item.CimInstanceProperties['Inheritance'].Value
                $NoPropagateInherit = $Item.CimInstanceProperties['NoPropagateInherit'].Value

                if (-not $AccessControlType)
                {
                    $AccessControlType = 'Allow'
                }

                if (-not $FileSystemRights)
                {
                    $FileSystemRights = 'ReadAndExecute'
                }

                if (-not $NoPropagateInherit)
                {
                    $NoPropagateInherit = $false
                }

                $PermissionEntries += [PSCustomObject]@{
                        AccessControlType = $AccessControlType
                        FileSystemRights = $FileSystemRights
                        Inheritance = $Inheritance
                        NoPropagateInherit = $NoPropagateInherit
                    }
            }
        }

        Write-Verbose -Message "Desired Permission Entry Count : $($PermissionEntries.Count)"

        foreach ($Item in $PermissionEntries)
        {
            $ReferenceRule = ConvertTo-FileSystemAccessRule -ItemType $ItemType -Principal $Identity.Name `
                -AccessControlType $Item.AccessControlType -FileSystemRights $Item.FileSystemRights `
                -Inheritance $Item.Inheritance -NoPropagateInherit $Item.NoPropagateInherit -ErrorAction Stop

            $MatchingRule = $AccessRules |
                Where-Object {
                    $_.AccessControlType -eq $ReferenceRule.AccessControlType -and
                    $_.FileSystemRights -eq $ReferenceRule.FileSystemRights -and
                    $_.InheritanceFlags -eq $ReferenceRule.InheritanceFlags -and
                    $_.PropagationFlags -eq $ReferenceRule.PropagationFlags
                }

            if ($MatchingRule)
            {
                "[FOUND] Permission Entry:",
                "> IdentityReference : '$($MatchingRule.IdentityReference)'",
                "> AccessControlType : '$($MatchingRule.AccessControlType)'",
                "> FileSystemRights : '$($MatchingRule.FileSystemRights)'",
                "> InheritanceFlags : '$($MatchingRule.InheritanceFlags)'",
                "> PropagationFlags : '$($MatchingRule.PropagationFlags)'" |
                Write-Verbose
            }
            else
            {
                $EnsureResult = 'Absent'

                "[NOT FOUND] Permission Entry:",
                "> IdentityReference : '$($ReferenceRule.IdentityReference)'",
                "> AccessControlType : '$($ReferenceRule.AccessControlType)'",
                "> FileSystemRights : '$($ReferenceRule.FileSystemRights)'",
                "> InheritanceFlags : '$($ReferenceRule.InheritanceFlags)'",
                "> PropagationFlags : '$($ReferenceRule.PropagationFlags)'" |
                Write-Verbose
            }
        }
    }

    $ReturnValue = @{
            Ensure = $EnsureResult
            Path = $Path
            ItemType = $ItemType
            Principal = $Principal
            AccessControlInformation = [Microsoft.Management.Infrastructure.CimInstance[]]@($CimAccessRules)
        }

    return $ReturnValue

}


function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([Boolean])]
    param
    (
        [Parameter(Mandatory = $false)]
        [ValidateSet('Absent', 'Present')]
        [String]
        $Ensure = 'Present',

        [Parameter(Mandatory = $true)]
        [String]
        $Path,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Directory', 'File')]
        [String]
        $ItemType,

        [Parameter(Mandatory = $true)]
        [String]
        $Principal,

        [Parameter(Mandatory = $false)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $AccessControlInformation
    )

    $TargetResource = Get-TargetResource @PSBoundParameters

    $InDesiredState = $Ensure -eq $TargetResource.Ensure

    if ($InDesiredState -eq $true)
    {
        Write-Verbose -Message "The target resource is already in the desired state. No action is required."
    }
    else
    {
        Write-Verbose -Message "The target resource is not in the desired state."
    }

    return $InDesiredState

}


function Set-TargetResource
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $false)]
        [ValidateSet('Absent', 'Present')]
        [String]
        $Ensure = 'Present',

        [Parameter(Mandatory = $true)]
        [String]
        $Path,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Directory', 'File')]
        [String]
        $ItemType,

        [Parameter(Mandatory = $true)]
        [String]
        $Principal,

        [Parameter(Mandatory = $false)]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $AccessControlInformation
    )

    if ($ItemType -eq 'Directory')
    {
        $PathType = 'Container'
    }
    else
    {
        $PathType = 'Leaf'
    }

    if (Test-Path -Path $Path -PathType $PathType)
    {
        $Acl = Get-Acl -Path $Path -ErrorAction Stop
    }
    else
    {
        throw "Could not find the item of type '$ItemType' at the specified path: '$Path'."
    }

    $Identity = Resolve-IdentityReference -Identity $Principal -ErrorAction Stop

    [System.Security.AccessControl.FileSystemAccessRule[]]$AccessRules = @(
            $Acl.Access | Where-Object {$_.IsInherited -eq $false -and $_.IdentityReference -eq $Identity.Name}
        )

    if ($Ensure -eq 'Absent')
    {
        if ($AccessRules.Count -ne 0)
        {
            "Removing all of the non-inherited permission entries for principal '{0}' on path '{1}'." -f
                $($AccessRules[0].IdentityReference), $Path |
            Write-Verbose

            $Result = $null
            $Acl.ModifyAccessRule('RemoveAll', $AccessRules[0], [Ref]$Result)
        }
    }
    else
    {
        if ($AccessRules.Count -ne 0)
        {
            "Removing all of the non-inherited permission entries for principal '{0}' on path '{1}'." -f
                $($AccessRules[0].IdentityReference), $Path |
            Write-Verbose

            $Result = $null
            $Acl.ModifyAccessRule('RemoveAll', $AccessRules[0], [Ref]$Result)
        }

        [PSCustomObject[]]$PermissionEntries = @()

        if ($AccessControlInformation.Count -eq 0)
        {
            $PermissionEntries += [PSCustomObject]@{
                    AccessControlType = 'Allow'
                    FileSystemRights = 'ReadAndExecute'
                    Inheritance = $null
                    NoPropagateInherit = $false
                }
        }
        else
        {
            foreach ($Item in $AccessControlInformation)
            {
                $AccessControlType = $Item.CimInstanceProperties['AccessControlType'].Value
                $FileSystemRights = $Item.CimInstanceProperties['FileSystemRights'].Value
                $Inheritance = $Item.CimInstanceProperties['Inheritance'].Value
                $NoPropagateInherit = $Item.CimInstanceProperties['NoPropagateInherit'].Value

                if (-not $AccessControlType)
                {
                    $AccessControlType = 'Allow'
                }

                if (-not $FileSystemRights)
                {
                    $FileSystemRights = 'ReadAndExecute'
                }

                if (-not $NoPropagateInherit)
                {
                    $NoPropagateInherit = $false
                }

                $PermissionEntries += [PSCustomObject]@{
                        AccessControlType = $AccessControlType
                        FileSystemRights = $FileSystemRights
                        Inheritance = $Inheritance
                        NoPropagateInherit = $NoPropagateInherit
                    }
            }
        }

        foreach ($Item in $PermissionEntries)
        {
            $ReferenceRule = ConvertTo-FileSystemAccessRule -ItemType $ItemType -Principal $Identity.Name `
                -AccessControlType $Item.AccessControlType -FileSystemRights $Item.FileSystemRights `
                -Inheritance $Item.Inheritance -NoPropagateInherit $Item.NoPropagateInherit -ErrorAction Stop

            "Adding permission entry for principal '{0}' on path '{1}'." -f $Identity.Name, $Path |
            Write-Verbose

            $Acl.AddAccessRule($ReferenceRule)
        }
    }

    if ($PSCmdlet.ShouldProcess($Path, 'SetAccessControl'))
    {
        # The Set-Acl cmdlet is not used on purpose
        if ($ItemType -eq 'Directory')
        {
            [System.IO.Directory]::SetAccessControl($Path, $Acl)
        }
        else
        {
            [System.IO.File]::SetAccessControl($Path, $Acl)
        }
    }

}


Export-ModuleMember -Function Get-TargetResource, Set-TargetResource, Test-TargetResource


#region Helper Functions

function ConvertFrom-FileSystemAccessRule
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateSet('Directory', 'File')]
        [String]
        $ItemType,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Security.AccessControl.FileSystemAccessRule]
        $InputObject
    )
    process
    {
        [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = $InputObject.InheritanceFlags
        [System.Security.AccessControl.PropagationFlags]$PropagationFlags = $InputObject.PropagationFlags

        $NoPropagateInherit = $PropagationFlags.HasFlag([System.Security.AccessControl.PropagationFlags]::NoPropagateInherit)

        if ($NoPropagateInherit)
        {
            [System.Security.AccessControl.PropagationFlags]$PropagationFlags = $PropagationFlags -bxor [System.Security.AccessControl.PropagationFlags]::NoPropagateInherit
        }

        if ($InheritanceFlags -eq 'None' -and $PropagationFlags -eq 'None')
        {
            if ($ItemType -eq 'Directory')
            {
                $Inheritance = 'ThisFolderOnly'
            }
            else
            {
                $Inheritance = 'None'
            }
        }
        elseif ($InheritanceFlags -eq 'ContainerInherit, ObjectInherit' -and $PropagationFlags -eq 'None')
        {
            $Inheritance = 'ThisFolderSubfoldersAndFiles'
        }
        elseif ($InheritanceFlags -eq 'ContainerInherit' -and $PropagationFlags -eq 'None')
        {
            $Inheritance = 'ThisFolderAndSubfolders'
        }
        elseif ($InheritanceFlags -eq 'ObjectInherit' -and $PropagationFlags -eq 'None')
        {
            $Inheritance = 'ThisFolderAndFiles'
        }
        elseif ($InheritanceFlags -eq 'ContainerInherit, ObjectInherit' -and $PropagationFlags -eq 'InheritOnly')
        {
            $Inheritance = 'SubfoldersAndFilesOnly'
        }
        elseif ($InheritanceFlags -eq 'ContainerInherit' -and $PropagationFlags -eq 'InheritOnly')
        {
            $Inheritance = 'SubfoldersOnly'
        }
        elseif ($InheritanceFlags -eq 'ObjectInherit' -and $PropagationFlags -eq 'InheritOnly')
        {
            $Inheritance = 'FilesOnly'
        }

        $OutputObject = [PSCustomObject]@{
                ItemType = $ItemType
                Principal = [String]$InputObject.IdentityReference
                AccessControlType  = [String]$InputObject.AccessControlType
                FileSystemRights = [String]$InputObject.FileSystemRights
                Inheritance = $Inheritance
                NoPropagateInherit = $NoPropagateInherit
            }

        return $OutputObject
    }
}


function ConvertTo-FileSystemAccessRule
{
    [CmdletBinding()]
    [OutputType([System.Security.AccessControl.FileSystemAccessRule])]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Directory', 'File')]
        [String]
        $ItemType,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [String]
        $Principal,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet('Allow', 'Deny')]
        [String]
        $AccessControlType,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [System.Security.AccessControl.FileSystemRights]
        $FileSystemRights,

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet(
            $null,
            'None',
            'ThisFolderOnly',
            'ThisFolderSubfoldersAndFiles',
            'ThisFolderAndSubfolders',
            'ThisFolderAndFiles',
            'SubfoldersAndFilesOnly',
            'SubfoldersOnly',
            'FilesOnly'
        )]
        [String]
        $Inheritance = 'ThisFolderSubfoldersAndFiles',

        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [Boolean]
        $NoPropagateInherit = $false
    )
    process
    {
        if ($ItemType -eq 'Directory')
        {
            switch ($Inheritance)
            {
                {$_ -in @('None', 'ThisFolderOnly')}
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'None'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'None'
                }

                'ThisFolderSubfoldersAndFiles'
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ContainerInherit', 'ObjectInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'None'
                }

                'ThisFolderAndSubfolders'
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ContainerInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'None'
                }

                'ThisFolderAndFiles'
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ObjectInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'None'
                }

                'SubfoldersAndFilesOnly'
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ContainerInherit', 'ObjectInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'InheritOnly'
                }

                'SubfoldersOnly'
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ContainerInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'InheritOnly'
                }

                'FilesOnly'
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ObjectInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'InheritOnly'
                }

                default
                {
                    [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'ContainerInherit', 'ObjectInherit'
                    [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'None'
                }
            }

            if ($NoPropagateInherit -eq $true -and $InheritanceFlags -ne 'None')
            {
                [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'NoPropagateInherit'
            }
        }
        else
        {
            [System.Security.AccessControl.InheritanceFlags]$InheritanceFlags = 'None'
            [System.Security.AccessControl.PropagationFlags]$PropagationFlags = 'None'
        }

        $OutputObject = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule `
            -ArgumentList $Principal, $FileSystemRights, $InheritanceFlags, $PropagationFlags, $AccessControlType

        return $OutputObject
    }
}


function Resolve-IdentityReference
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [String]
        $Identity
    )
    process
    {
        try
        {
            Write-Verbose -Message "Resolving identity reference '$Identity'."

            if ($Identity -match '^S-\d-(\d+-){1,14}\d+$')
            {
                [System.Security.Principal.SecurityIdentifier]$Identity = $Identity
            }
            else
            {
                [System.Security.Principal.NTAccount]$Identity = $Identity
            }

            $SID = $Identity.Translate([System.Security.Principal.SecurityIdentifier])
            $NTAccount = $SID.Translate([System.Security.Principal.NTAccount])

            $OutputObject = [PSCustomObject]@{Name = $NTAccount.Value; SID = $SID.Value}

            return $OutputObject
        }
        catch
        {
            "Unable to resolve identity reference '{0}'. Error: '{1}'" -f $Identity, $_.Exception.Message |
            Write-Error

            return
        }
    }
}


#endregion