ACLHelpers.psm1


Set-StrictMode -Version Latest 

Enum RuleCompareResult
{
    Same
    Child
    Parent
    Different
}

Function Compare-Rule
{
    # Sould handle all types https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.accessrule(v=vs.110).aspx
    #region Params
    [CmdletBinding(DefaultParameterSetName)]
    Param
    (
        [Parameter(Mandatory=$true,Position =0)]
        [System.Security.AccessControl.AccessRule]$Rule1,
        [Parameter(Mandatory=$true,Position =1)]
        [System.Security.AccessControl.AccessRule]$Rule2
    )
    #endregion
    if($Rule1.GetType() -eq $Rule2.GetType() -and $Rule1.IdentityReference.Value -eq $Rule2.IdentityReference.Value -and $Rule1.AccessControlType -eq $Rule2.AccessControlType -and $Rule1.InheritanceFlags -eq $Rule2.InheritanceFlags -and $Rule1.PropagationFlags -eq $Rule2.PropagationFlags -and $Rule1.IsInherited -eq $Rule2.IsInherited)
    { 
        if($Rule1.GetType() -eq [System.Security.AccessControl.FileSystemAccessRule])
        {
            if($Rule1.FileSystemRights -eq $Rule2.FileSystemRights)
                { return [RuleCompareResult]::Same }
            else
                { return [RuleCompareResult]::Different }
        }
        elseif($Rule1.GetType() -eq [System.Security.AccessControl.RegistryAccessRule])
        {
            if($Rule1.RegistryRights -eq $Rule2.RegistryRights)
                { return [RuleCompareResult]::Same }
            else
                { return [RuleCompareResult]::Different }
        }

        return [RuleCompareResult]::Different
    }
    else
        { return [RuleCompareResult]::Different }
}

Function Parse-Enum
{
    [CmdletBinding()]
    Param
    (        
        [Parameter(Mandatory=$true,Position =0)]
        [string[]]$TextInput,
        [Parameter(Mandatory=$true,Position =1)]
        $DesiredEnum
    )
    
    $results = ,@{}
    $results.clear()
    
    foreach($string in $TextInput)
    {
        $Result = $string -as $DesiredEnum
        if($Result -eq $null)
        { 
            $options = $DesiredEnum.GetEnumNames() -join ", "
            throw "[$string] is not a recognized [$DesiredEnum] value. Allowed options are as follows:`r{$options}"  
        }
        else
        { $results += $Result }
    }
    
    $results = $results -ne $null
    if($results.Count -eq 1)
        { return $results[0] }
        
    return $results
}

<#
.SYNOPSIS
    Adds ACL Entries to Objects.
.DESCRIPTION
    Used to simplify ACL setting work. Allows for more natural syntax
.PARAMETER Path
    Changes the security descriptor of the specified item.
    Enter the path to an item, such as a path to a file or registry key.
.PARAMETER Principal
    The user/group principal to be used.
.PARAMETER Rights
    The permission(s) the should be used.
.PARAMETER Rules
    Pre-set rule(s) that inherits from [System.Security.AccessControl.AccessRule].
.PARAMETER PropagationFlags
    Specifies how Access Control Entries (ACEs) are propagated to child objects. These flags are significant only if inheritance flags are present.
.PARAMETER InheritanceFlags
    Inheritance flags specify the semantics of inheritance for access control entries (ACEs).
.PARAMETER Inherit
    Should the permission be viewed as inherited by child objects.
.PARAMETER Allow
    Represents an Allow permission.
.PARAMETER Deny
    Represents an Deny permission overrides -Allow.
.EXAMPLE
    Add-Acl -Path "c:\temp" -Principal "UserName" -Rights "Read"
    Update Folder ACL by username
.EXAMPLE
    Add-Acl "HKCU:\Test" $StrSID "ReadKey"
    Update Registry ACL by username
.NOTES
#>

Function Add-Acl
{
    #region Params
    [CmdletBinding(DefaultParameterSetName='ByRights')]
    Param
    (
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline)]
        [Parameter(ParameterSetName = 'ByRules')]
        [Parameter(ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [string[]] $Path,

        [Parameter(Mandatory=$true, Position=1,ParameterSetName = 'ByRules')]
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.AccessRule[]]$Rules,

        [Parameter(Mandatory=$true, Position=1,ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        $Principal,
            
        [Parameter(Mandatory=$true, Position=2,ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [array]$Rights,
            
        #[Parameter(HelpUri='https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags(v=vs.110).aspx')]
        [Parameter(ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.PropagationFlags] $PropagationFlags = "None",
            
        #[Parameter(HelpUri="https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags(v=vs.110).aspx")]
        [Parameter(ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.InheritanceFlags] $InheritanceFlags = "ContainerInherit, ObjectInherit",
            
        [Parameter(ParameterSetName = 'ByRights')]
        [bool] $Inherit = $true,
            
        [Parameter(ParameterSetName = 'ByRights')]
        [switch] $Allow = $true,
        [Parameter(ParameterSetName = 'ByRights')]
        [switch] $Deny
    )
    #endregion
    Begin
    {
        $ErrorActionPreference = "Stop"
        if($Allow)
            { $ActionVerb ="Allow" }
            
        if($Deny)
        { 
            $Allow = $false
            $ActionVerb ="Deny" 
        }

        if($Principal -is [System.String] -and $Principal -notmatch "\\")
        {
            $Principal = "$env:computername\$Principal"
        }
    }
    Process
    {      
        $ErrorActionPreference = "Stop"
        foreach($item in $Path)
        {
            if(-not (Test-Path $item))
            {
                Throw "Path [$item] not found"
                continue;
            }

            $Found = $false    
            $HasWork = $false
            $Acl = Get-Acl $item
            $Ar=$null
            $List=$Rules

            if($Rights)
            {
                if($Rights.count -eq 1 -and $Rights[0].GetType() -eq [string])
                    { $List= $Rights[0].Split(",") }
                else
                    { $List = $Rights }
            }
                
            foreach ($i in $List)
            {
                if($i.GetType() -eq $Acl.AccessRuleType)
                {
                    $Ar= $i
                }
                else
                {
                    $right = Parse-Enum $i $Acl.AccessRightType
                
                    if($Inherit)
                        { $Ar = New-Object ($Acl.AccessRuleType)($Principal, $right, $InheritanceFlags, $PropagationFlags, $ActionVerb) }
                    else
                        { $Ar = New-Object ($Acl.AccessRuleType)($Principal, $right, $ActionVerb) }
                  }

                if($Ar)
                {
                    $HasWork = $true
                    $Acl.AddAccessRule($Ar)
                }
            }
            if($HasWork)
            {
                Set-Acl $item $Acl 
            }
        }
    }
}
Set-Alias aacl Add-Acl

<#
.SYNOPSIS
    Removes ACL Entries to Objects.
.DESCRIPTION
    Used to simplify ACL removing work. Allows for more natural syntax
.PARAMETER Path
    Changes the security descriptor of the specified item.
    Enter the path to an item, such as a path to a file or registry key.
.PARAMETER Principal
    The user/group principal to be used.
.PARAMETER Rights
    The permission(s) the should be used.
.PARAMETER PropagationFlags
    Specifies how Access Control Entries (ACEs) are propagated to child objects. These flags are significant only if inheritance flags are present.
.PARAMETER InheritanceFlags
    Inheritance flags specify the semantics of inheritance for access control entries (ACEs).
.PARAMETER Inherit
    Should the permission be viewed as inherited by child objects.
.PARAMETER Allow
    Represents an Allow permission.
.PARAMETER Deny
    Represents an Deny permission.
.EXAMPLE
    Add-Acl -Path "c:\temp" -Principal "UserName" -Rights "Read"
    Update Folder ACL by username
.EXAMPLE
    Add-Acl "HKCU:\Test" $StrSID "ReadKey"
    Update Registry ACL by username
.NOTES
#>

Function Remove-Acl
{
    #region Params
    [CmdletBinding(DefaultParameterSetName='ByRights')]
    Param
    (
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline)]
        [Parameter(ParameterSetName = 'ByRules')]
        [Parameter(ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [string[]] $Path,
            
        [Parameter(Mandatory=$true, Position=1,ParameterSetName = 'ByRules')]
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.AccessRule[]]$Rules,

        [Parameter(Mandatory=$true, Position=1,ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        $Principal,
            
        [Parameter(Mandatory=$true, Position=2,ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [array]$Rights,
            
        [Parameter(ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.PropagationFlags] $PropagationFlags = "None",
            
        #[Parameter(HelpUri="https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags(v=vs.110).aspx")]
        [Parameter(ParameterSetName = 'ByRights')]
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.InheritanceFlags] $InheritanceFlags = "ContainerInherit, ObjectInherit",
            
        [Parameter(ParameterSetName = 'ByRights')]
        [bool] $Inherit = $true,
            
        [Parameter(ParameterSetName = 'ByRights')]
        [switch] $Allow = $true,
        [Parameter(ParameterSetName = 'ByRights')]
        [switch] $Deny
    )
    #endregion
    Begin
    {
        $ErrorActionPreference = "Stop"
        if($Allow)
            { $ActionVerb ="Allow" }
            
        if($Deny)
        { 
            $Allow = $false
            $ActionVerb ="Deny" 
        }

        $ErrorActionPreference = "Continue"
        if($Principal -is [System.Security.Principal.SecurityIdentifier])
        {
            try
            {
                $Principal = $Principal.Translate([System.Security.Principal.NTAccount]).Value # | select Name
            }
            catch [Security.Principal.IdentityNotMappedException]
            {
                Write-Warning "Can't Translate $Principal"
            }
        }
        elseif($Principal -is [System.String] -and $Principal -notmatch "\\")
        {
            $Principal = "$env:computername\$Principal"
        }
    }
    Process
    {
        foreach($item in $Path)
        {
            if(-not (Test-Path $item))
            {
                Throw "Path [$item] not found"
                continue;
            }
                  
            $HasWork = $false
            $Acl = Get-Acl $item
            $List=$Rules

            if($PSCmdlet.ParameterSetName -eq 'ByRights')
            {            
                if($Rights.count -eq 1 -and $Rights[0].GetType() -eq [string])
                    { $List= $Rights[0].Split(",") }
                else
                    { $List = $Rights }
                    
                foreach ($rule in $Acl.Access | Where-Object {$_.IdentityReference.Value -eq $Principal -and $_.AccessControlType -eq $ActionVerb -and $_.InheritanceFlags -eq $InheritanceFlags -and $_.PropagationFlags -eq $PropagationFlags})
                {  
                    $CurrentRightType=$Acl.AccessRightType
                    $CurrentRights=$rule.($CurrentRightType.Name)
                    $NewRights = $CurrentRights

                    foreach($i in $List)
                    {
                        $right = parse-enum $i $CurrentRightType
                        if($CurrentRights -band $i)
                        {
                            $NewRights = $NewRights - $I
                            $HasWork = $true
                        }
                    }
                    
                    if($HasWork)
                    {
                        $acl.RemoveAccessRule($rule) | Out-Null
                        if($NewRights -gt 0 -and $NewRights -ne "Synchronize")
                        {
                            $NewRule = New-Object ($Acl.AccessRuleType)($rule.IdentityReference, $NewRights, $rule.InheritanceFlags, $rule.PropagationFlags, $rule.AccessControlType) 
                            $Acl.AddAccessRule($NewRule)  | Out-Null
                        }
                    }
                }
            }
            elseif($PSCmdlet.ParameterSetName -eq 'ByRules')
            {
                foreach($ProvidedRule in $List)
                {
                    foreach ($rule in $Acl.Access | Where-Object { (Compare-Rule $_ $ProvidedRule) -eq [RuleCompareResult]::Same})
                    {
                        $acl.RemoveAccessRule($rule) | Out-Null
                        $HasWork = $true
                    }
                }
            }
            
            if($HasWork)
                {Set-Acl $item $Acl }
            else
            {
                if($PSCmdlet.ParameterSetName -eq 'ByRights')
                    { Write-Warning "[$Principal] dosent have [$ActionVerb] [$Rights] access on [$item]" }
                elseif($PSCmdlet.ParameterSetName -eq 'ByRules')
                    { Write-Warning "Provided rule not found on [$item]" }
            }
        }                  
    }
}
Set-Alias racl Remove-Acl

<#
.SYNOPSIS
    Checks ACL Entries to Objects.
.DESCRIPTION
    Used to simplify checking ACL settings. Allows for more natural syntax
.PARAMETER Path
    Changes the security descriptor of the specified item.
    Enter the path to an item, such as a path to a file or registry key.
.PARAMETER Principal
    The user/group principal to be used.
.PARAMETER Rights
    The permission(s) the should be used.
.PARAMETER PropagationFlags
    Specifies how Access Control Entries (ACEs) are propagated to child objects. These flags are significant only if inheritance flags are present.
.PARAMETER InheritanceFlags
    Inheritance flags specify the semantics of inheritance for access control entries (ACEs).
.PARAMETER Inherit
    Should the permission be viewed as inherited by child objects.
.PARAMETER Allow
    Represents an Allow permission.
.PARAMETER Deny
    Represents an Deny permission.
.EXAMPLE
    Test-Acl -Path "c:\temp" -Principal "UserName" -Rights "Read"
    Update Folder ACL by username
.EXAMPLE
    Test-Acl "HKCU:\Test" $StrSID "ReadKey"
    Update Registry ACL by username
.NOTES
#>

Function Test-Acl
{
    #region Params
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        $Path,
            
        [Parameter(Mandatory=$true, Position=1)]
        [ValidateNotNullOrEmpty()]
        $Principal,
            
        [Parameter(Mandatory=$true, Position=2)]
        [ValidateNotNullOrEmpty()]
        $Rights,
            
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.PropagationFlags] $PropagationFlags = "None",
            
        [ValidateNotNullOrEmpty()]
        [System.Security.AccessControl.InheritanceFlags] $InheritanceFlags = "ContainerInherit, ObjectInherit",
            
        [bool] $Inherit = $true,
        [switch] $Allow = $true,
        [switch] $Deny
    )
    #endregion
    Begin
    {
        $ErrorActionPreference = "Stop"
        if($Allow)
            { $ActionVerb ="Allow" }
            
        if($Deny)
        { 
            $Allow = $false
            $ActionVerb ="Deny" 
        }

        if($Principal -is [System.String] -and $Principal -notmatch "\\")
            { $Principal = "$env:computername\$Principal" }
            
        $Results = ,@{}
    }
    Process
    {
        $ErrorActionPreference = "STOP"
        foreach($item in $Path)
        {           
            $HasWork = $false
            $Acl =$item | Get-Acl 
            $Any = $false
            
            foreach ($rule in $Acl.Access | Where-Object {$_.IdentityReference.Value -eq $Principal })
            {   
                $ExistingPermission =$rule.($Acl.AccessRightType.Name)
                
                Parse-Enum $Rights $Acl.AccessRightType | Out-Null
                $CheckingPermission = $Rights -as $Acl.AccessRightType
                
                if($ExistingPermission -band $CheckingPermission)
                { 
                    $Any = $true
                    $Results += New-Object �TypeName PSObject �Prop (@{'Path'=$item; 'Result'=$true;})
                    break
                }
            }
            if($Any -eq $false)
            {
                $Results += New-Object �TypeName PSObject �Prop    (@{'Path'=$item; 'Result'=$false;})
            }
        }
    }
    End
    {
        return $Results    | Format-Table
    }
}
Set-Alias tacl Test-Acl

<#
.SYNOPSIS
    Checks ACL Entries to Objects.
.DESCRIPTION
    Used to simplify checking ACL settings. Allows for more natural syntax
.PARAMETER Path
    Changes the security descriptor of the specified item.
    Enter the path to an item, such as a path to a file or registry key.
.PARAMETER Principal
    The user/group principal to be used as master set.
.PARAMETER DestinationPrincipal
    The user/group principal to be coped to.
.EXAMPLE
    Copy-Acl -Path "c:\temp" -Principal "User1" -DestinationPrincipal "User2"
    Copy Folder ACL by username
.NOTES
#>

Function Copy-Acl
{
      #region Params
      [CmdletBinding()]
      Param
      (
            [Parameter(Mandatory=$true,Position=0,ValueFromPipeline)]
            [ValidateNotNullOrEmpty()]
            [string[]] $Path,
            
            [Parameter(Mandatory=$true, Position=1)]
            [ValidateNotNullOrEmpty()]
            $Principal,
            
            [Parameter(Mandatory=$true, Position=2)]
            [ValidateNotNullOrEmpty()]
            $DestinationPrincipal
      )
      #endregion
      Begin
      {
          if($Principal -is [System.Security.Principal.SecurityIdentifier])
        {
            $Principal = $Principal.Translate([System.Security.Principal.NTAccount]).Value # | select Name
        }
        elseif($Principal -is [System.String] -and $Principal -notmatch "\\")
        {
            $Principal = "$env:computername\$Principal"
        }
      }
      Process
      {
        $ErrorActionPreference = "Continue"
        foreach($Item in $Path)
        {
            $HasWork= $False
            $Acl = Get-Acl $item
            foreach ($rule in $Acl.Access | Where-Object {$_.IdentityReference.Value -eq $Principal})
            {
                $Ar=$null
                $HasWork = $true
                $Ar = New-Object($Acl.AccessRuleType)($DestinationPrincipal, $rule.($Acl.AccessRightType.Name), $rule.InheritanceFlags, $rule.PropagationFlags, $rule.AccessControlType)
                if($Ar)
                    { $Acl.AddAccessRule($Ar) | Out-Null }
            }
            
            if($HasWork)
                { Set-Acl $Item $Acl }
        }
     }
}
Set-Alias cacl Copy-Acl

Export-ModuleMember -Function Add-Acl, Remove-Acl, Test-Acl,Update-Acl,Copy-Acl -Alias aacl, racl, tacl, uacl, cacl