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" |