ACLCleanup.psm1

#Requires -version 3


<#
        #############
        Created By:
        Robert Amartinesei
 
        Email feedback to:
        robertamartinesei@gmail.com
 
        Disclaimer: These scripts are provided in good faith and with no warranty as to their fitness of purpose. Use this software at your own risk.
        The author accepts no liabiliy for any losses or damages resulting from the use thereof.
 
 
#>


#####################################################################################################################################################################

#Helper Functions

Function ProcessNextItem {
        
        $PathName = $_.FullName
        $Acl = Get-ACL -Path $PathName

        if ($IncludeInherited) {
            $AccessObjects = $Acl.access
        }else {
            $AccessObjects = $Acl.access | Where-Object -FilterScript {$_.isinherited -eq $false}
        }
        
        Write-Verbose -Message "Checking $PathName"
        
        foreach ($ref in $AccessObjects) {
            
            $Username = $ref.IdentityReference #-replace ".+\\"
            
            Write-Verbose "trying $SamAccountName"
            
            if ($Username -in $Allusers) {
            
                $props = [Ordered]@{
                    
                    'Username' = "$Username" 
                    'IsInherited' = $ref.IsInherited #bool
                    'Path' = $PathName #Fullname of path searched.
                
                }

                New-Object -TypeName psobject -Property $props
                
            
            }

        }

}


Function ProcessNextOrphanedItem {

        $PathName = $_.FullName
        $Acl = Get-ACL -Path $PathName

        if ($IncludeInherited) {
            $AccessObjects = $Acl.access
        }else {
            $AccessObjects = $Acl.access | Where-Object -FilterScript {$_.isinherited -eq $false}
        }
        
        Write-Verbose -Message "Checking $PathName"
        
        foreach ($ref in $AccessObjects) {
            
            $SIDPattern = "^S-\d-\d+-(\d+-){1,14}\d+$"
            $ACEName = $ref.IdentityReference.value
            
            Write-Verbose "trying $SamAccountName"
            
            if ($ACEName -match $SIDPattern) {
            
                $props = [Ordered]@{
                    
                    'SID' = $ACEName 
                    'IsInherited' = $ref.IsInherited #bool
                    'Path' = $PathName #Fullname of path searched.
                
                }

                New-Object -TypeName psobject -Property $props
                
            
            }

        }

}

#Help Functions End

Function Get-ExplicitUserPermission {

    <#
    .Synopsis
       Find user ACE in ACLs
    .DESCRIPTION
       This function will help you find entries in ACLs where a user has been set instead of a group which is generally considered best practice.
       You can find ACE that has been explicitly set or include inherited ones. You can also return files/directories only based on your needs with the respective parameter.
       Use this function to clean your filestructure.
 
    .EXAMPLE
        
        PS> Get-ExplicitUserPermission -username (get-aduser -filter *).samaccountname
         
        Username IsInherited Path
        -------------- ----------- ----
        ITM\Robama False C:\users\RObama\play2\xml.xml
 
        This command checks every directory and file in the current path after a match in the domain. Since I am running this as a member of the domain I don't have to specify the parameter Userdomain
 
    .EXAMPLE
 
        PS> Get-ExplicitUserPermission -username "Robama"
         
        Username IsInherited Path
        -------------- ----------- ----
        ITM\Robama False C:\users\RObama\play2\xml.xml
 
        Same as above but searching for a specific match.
 
    .EXAMPLE
 
        PS> Get-ExplicitUserPermission -Username (get-aduser -filter *).samaccountname -Path .\folder1\ -SingleItem
         
        Username IsInherited Path
        -------- ----------- ----
        ITM\robama False C:\users\RObama\folder1\
 
        Gets explicit permission for every user in the domain on the specific path.
 
 
 
    .OUTPUTS
        PSCustom Object
        
        SamAccountName IsInherited Name Path
        -------------- ----------- ---- ----
        robama False Robert Amartinesei C:\Users\Robama\Desktop
 
    .NOTES
       Created by Robert Amartinesei
       2017-01-20
 
       Disclaimer: These scripts are provided in good faith and with no warranty as to their fitness of purpose. Use this software at your own risk. The author accepts no liabiliy for any losses or damages resulting from the use thereof.
 
    #>


    [Cmdletbinding(DefaultParametersetName="MultipleMode")]

    Param (
    
        #Supply a valid path, either locally or UNC.
        [ValidateScript({Test-Path $_})]
        [String]$Path = $pwd,

        #A comma separated list of samaccountNames from your AD or localcomputer
        [Parameter(Mandatory,ValueFromPipelineByPropertyName,Helpmessage="Please provide a samaccountname from your domain or local computer")]
        [Alias('SamAccountName')]
        [String[]]$Username,

        #This parameter will include Inherited permissions as part of the Functions Output.
        [Switch]$IncludeInherited,

        [Parameter(Parametersetname="MultipleMode")]
        #Search recursively throught the file tree relative to the -Path parameter. Cannot be used with -SingleItem
        [Switch]$Recurse,

        [Parameter(Parametersetname="SingleMode")]
        #Tells the function to get the ACL of the path specified. Cannot be used with -Recurse
        [Switch]$SingleItem,

        [Parameter(Parametersetname="MultipleMode")]       
        #Return only objects of the type directory.
        [Switch]$Directory,

        [Parameter(Parametersetname="MultipleMode")]
        #Return only objects of the type file.
        [Switch]$File,

        [Alias("Domainname")]
        #The userdomain name of your domain. If your domain is corporate.local then your userdomain will probably be corporate. Therefor the default value vill be the userdomain of the user running the script
        [ValidateNotNullOrEmpty()]
        [String]$Userdomain = $env:USERDOMAIN

    )

    

    Process {

        $AllUsers = $Username | % {$_.insert(0,"$Userdomain\")}
        #$AllUsers
    
        #Uses the function ProcessNextItem based on the presence or abscence of the parameter SingleItem
        if ($SingleItem) {
            Write-Verbose -Message "SingleMode - Testing specific path $Path"
            Get-Item -Path $Path | ForEach-Object {
                ProcessNextItem
            }
        }else {
            Write-Verbose -Message "MultipleMode - Testing paths recursively"
            Get-ChildItem -Path $Path -Directory:$Directory -file:$file -Recurse:$Recurse  | ForEach-Object {
    
                ProcessNextItem

            }

        }

    }

}

Function Remove-ExplicitUserPermission {

    <#
    .Synopsis
       Remove user ACEs from ACLs
    .DESCRIPTION
       This function will help you remove entries in ACLs where a user has been set instead of a group, which is generally considered best practice.
       The functions supports confirm and WhatIf and can also take pipeline input which makes it very easy to use together with Get-ExplicitUserPermission
    .EXAMPLE
        
        PS> Get-ExplicitUserPermission -Username Robama -Path C:\Users\Robama\Desktop\subfolder -SingleItem | Remove-ExplicitUserPermission -WhatIf
         
        What if: Performing the operation "Remove "ITM\robama" from ACL" on target "C:\Users\Robama\Desktop\subfolder".
 
        This commands shows you that the functions takes pipeline input and supports the use of "WhatIf"
 
    .EXAMPLE
        
        PS> Get-ExplicitUserPermission -Username Robama | Remove-ExplicitUserPermission -Verbose
 
            Username Path Action
            -------- ---- ------
            ITM\Robama C:\users\RObama\folder2 Remove
 
 
    .OUTPUTS
        PSCustom Object
        
        Username Path Action
        -------- ---- ------
        ITM\robama C:\Users\Robama\Desktop\subfolder Remove
 
    .NOTES
       Created by Robert Amartinesei
       2017-01-20
 
       Disclaimer: These scripts are provided in good faith and with no warranty as to their fitness of purpose. Use this software at your own risk. The author accepts no liabiliy for any losses or damages resulting from the use thereof.
 
    #>

    [Cmdletbinding(SupportsShouldProcess)]

    Param (
    
        [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelinebyPropertyName)]
        [ValidateNotNullOrEmpty()]
        #Provide one or more samaccountNames in that exists in your AD. Only specified SamAccountNames will be removed
        [String[]]$Username,
    
        [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
        #Specify a path to a directory or file where you want ACEs to be removed.
        [ValidateScript({Test-Path $_})]
        [ValidateNotNullOrEmpty()]
        [String]$Path,

        [Alias("Domainname")]
        #The userdomain name of your domain. If your domain is corporate.local then your userdomain will probably be corporate. Therefor the default value vill be the userdomain of the user running the script
        [ValidateNotNullOrEmpty()]
        [String]$Userdomain = $env:USERDOMAIN
    )


    PROCESS {
        
    
        foreach ($Id in $Username) {

        
            #Check userdomain of incoming variable
            if ($Id -replace '\\.+' -ne $Userdomain) {
                $Id = $Id -replace '.+\\'
                $ACE = "$Userdomain\$Id"
            }else {
                $ACE = $Id
            }

            

        
            Try {
            
                $ErrorActionPreference = "Stop"
                $PerformString = "Remove `"$Ace`" from ACL"
                $ACL = Get-Acl -Path $Path

                if ($PSCmdlet.ShouldProcess("$Path","$PerformString")){


                    $ACL.Access | where-object {$_.identityreference -eq $ACE} | foreach {
                            
                            $ACL.PurgeAccessRules($_.Identityreference)
                            Set-ACL -Path $Path -AclObject $acl

                    }


                    $props = [ordered]@{
                        'Username' = $ACE
                        'Path' = $Path
                        'Action' = "Remove"
                    }

                    New-Object -TypeName psobject -Property $props
                }



                $ErrorActionPreference = "Continue"
            
            }Catch {
                
                Write-Error $Error[0]
            
                #

            }
        
        }
    
    }

}

Function Get-OrphanedAce {
    
    <#
    .Synopsis
       Gets SIDS that are explicitly set in an ACL
    .DESCRIPTION
       This function will get you the SIDs of any file or folder, Inherited or not. SIDS are often the remains of a user ACE and appears when there isn't any longer a mapping between sid
       and user. For example if the user has been deleted.
    .EXAMPLE
        
        PS> Get-OrphanedAce
 
        SID IsInherited Path
        --- ----------- ----
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\play2
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\7.txt
 
        This command will list any sid on any directory or file in your current directory.
 
    .EXAMPLE
         
        PS> Get-OrphanedAce -Recurse
 
        SID IsInherited Path
        --- ----------- ----
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\play2
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\7.txt
        S-1-15-3-4096 False C:\Users\RObama\Favorites\Bing.url
        S-1-5-21-3986840155-3541320725-2334626613-1015 False C:\Users\RObama\play2\xml.xml
 
        This command will recursively list any sid on any directory or file in your current directory and subdirectories/files.
 
 
    .EXAMPLE
         
        PS> Get-OrphanedAce -Recurse -IncludeInherited
 
        SID IsInherited Path
        --- ----------- ----
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\play2
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\7.txt
        S-1-15-3-4096 True C:\Users\RObama\Favorites\Bing.url
        S-1-5-21-3986840155-3541320725-2334626613-1015 False C:\Users\RObama\play2\xml.xml
 
        This command will recursively list any sid on any directory or file in your current directory and subdirectories/files and also include inherited ACEs.
 
    .OUTPUTS
        PSCustom Object
        
        SID IsInherited Path
        --- ----------- ----
        S-1-5-21-3986840155-3541320725-2334626613-1014 False C:\Users\RObama\play2
 
    .NOTES
       Created by Robert Amartinesei
       2017-01-20
 
       Disclaimer: These scripts are provided in good faith and with no warranty as to their fitness of purpose. Use this software at your own risk. The author accepts no liabiliy for any losses or damages resulting from the use thereof.
 
    #>

    [Cmdletbinding(DefaultParametersetName="MultipleMode")]

    Param (
    
        #Supply a valid path, either local or UNC
        [ValidateScript({Test-Path $_})]
        $Path = $pwd,

        #Includes inherited ACEs
        [Switch]$IncludeInherited,

        [Parameter(Parametersetname="MultipleMode")]
        #Will search recursively relative to the Path parameter
        [Switch]$Recurse,

        [Parameter(Parametersetname="SingleMode")]
        #Indicates that the function should only list the ACL for the specified path
        [Switch]$SingleItem,

        [Parameter(Parametersetname="MultipleMode")]       
        #Shows only directories
        [Switch]$Directory,

        [Parameter(Parametersetname="MultipleMode")]
        #Shows only files
        [Switch]$File

    )


    

    #Uses the function ProcessNextOrphanedItem based on the presence or abscence of the parameter SingleItem
    if ($SingleItem) {
        Write-Verbose -Message "SingleMode - Testing specific path $Path"
        Get-Item -Path $Path | ForEach-Object {
            ProcessNextOrphanedItem
        }
    }else {
        Write-Verbose -Message "MultipleMode - Testing paths recursively"
        Get-ChildItem -Path $Path -Directory:$Directory -file:$file -Recurse:$Recurse  | ForEach-Object {
    
            ProcessNextOrphanedItem

        }

    }

}

Function Remove-OrphanedAce {

    <#
    .Synopsis
       Removes SIDS that are explicitly set in an ACL
    .DESCRIPTION
       This function will remove the SIDs of any file or folder, Inherited or not. SIDS are often the remains of a user ACE and appears when there isn't any longer a mapping between sid
       and user. For example if the user has been deleted.
 
       Use this function preferrably with the Get-OrphanedAce function
    .EXAMPLE
        
        PS> Get-OrphanedAce | Remove-OrphanedAce -whatif
 
        What if: Performing the operation "Remove "S-1-5-21-3986840155-3541320725-2334626613-1014" from ACL" on target "C:\users\robama\play2".
        What if: Performing the operation "Remove "S-1-5-21-3986840155-3541320725-2334626613-1014" from ACL" on target "C:\users\robama\7.txt".
 
        Pipes the object from Get-OrphanedAce to Remove-Orphaned and uses that whatif statement.
 
    .EXAMPLE
        
        PS> Get-OrphanedAce | Remove-OrphanedAce -Verbose
 
        VERBOSE: Performing the operation "Remove "S-1-5-21-3986840155-3541320725-2334626613-1014" from ACL" on target "C:\users\robama\play2".
 
        VERBOSE: Performing the operation "Remove "S-1-5-21-3986840155-3541320725-2334626613-1014" from ACL" on target "C:\users\robama\7.txt".
 
        SID Path Action
        --- ---- ------
        S-1-5-21-3986840155-3541320725-2334626613-1014 C:\users\robama\play2 Remove
        S-1-5-21-3986840155-3541320725-2334626613-1014 C:\users\robama\7.txt Remove
 
        Pipes the object from Get-OrphanedAce to Remove-Orphaned and uses that Verbose statement.
 
    .OUTPUTS
        PSCustom Object
        
        SID Path Action
        --- ---- ------
        S-1-5-21-3986840155-3541320725-2334626613-1014 C:\users\robama\play2 Remove
 
    .NOTES
       Created by Robert Amartinesei
       2017-01-20
 
       Disclaimer: These scripts are provided in good faith and with no warranty as to their fitness of purpose. Use this software at your own risk. The author accepts no liabiliy for any losses or damages resulting from the use thereof.
 
    #>

    [Cmdletbinding(SupportsShouldProcess)]

    Param (
    
        [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelinebyPropertyName)]
        [ValidateNotNullOrEmpty()]
        [String[]]$SID,
    
        [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({Test-Path $_})]
        [String]$Path
    )


    PROCESS {
    
        foreach ($Id in $SID) {

            $ACE = "$Id"
        
            Try {
            
                $ErrorActionPreference = "Stop"
                $PerformString = "Remove `"$ACE`" from ACL"
                
                if ($PSCmdlet.ShouldProcess("$Path","$PerformString")){
                                        
                    $ACL = get-acl -Path $Path

                    #CHANGE SID PATTERN TO ID
                    $ACL.Access | where-object {$_.identityreference -eq $ID} | foreach {
                            
                            $ACL.PurgeAccessRules($_.Identityreference)
                            Set-ACL -Path $Path -AclObject $acl

                    }

                    $props = [ordered]@{
                        'SID' = $ACE
                        'Path' = $Path
                        'Action' = "Remove"
                    }

                    New-Object -TypeName psobject -Property $props

                }

                #cmd /C "icacls `"$Path`" /Remove `"$ACE`" " | Out-Null
                    
                #Custom object

                $ErrorActionPreference = "Continue"

            }Catch {
                
                Write-Error $Error[0]
            
                #

            }
             
        
        }
    
    }

}

#Export-ModuleMember #-Function "Get-ExplicitUserPermission","Remove-ExplicitUserPermission","Get-OrphanedAce","Remove-OrphanedAce"