I occasionally find myself needing to determine which folders or files a user or group does or does not have access to. With nested security groups things can get even more tricky.
I ended up writing get-ntfsAccess, a quick function that will do this for me. I feed in one or more paths to test, one more users/groups, and I get back either all the folders they have access to, or all the folders they don’t have access to. The function does the following:
- If specified, define and run Get-NestedGroups on the provided entities to provide all nested security groups
- Check ACL of each path for any ACE matching the users/groups or their nested security groups
Define and run Get-NestedGroups
This function is a bit longer than the Get-ntfsGroups function it is defined in.
$ADObjectDetails = Get-ADObject -filter "samAccountName -eq '$ADObject'" -Properties memberOf
Get the memberOf property for the object – we need this to determine if it’s a group or another object.
$ADObjectDetails | select -expand memberof | foreach { $subGroups += Get-adgroup $_ | select -ExpandProperty samaccountname }
If it isn’t a group, we want to know what security groups provide access. Loop through memberOf and get the samAccountName for each of these groups
if($group){ $subGroups = ( Get-ADGroupMember -Identity $ADObject | where {$_.objectClass -like "group"} ).samAccountName }
If it is a group, we only care what groups are inside the security group
#If there are sub groups, recurse through them if($subGroups){ #add results of current query $allNestedGroups = [string[]]$subGroups #initialize subSubgroupscollection $subSubGroupsCollection = [string[]]"" #look for subsubgroups in each sub group foreach($subgroup in $subgroups){ #Run query, return query results for verbose output, then add it to a collection $subSubGroups = Get-NestedGroups -ADObject $subgroup -nestLevel $nestLevel if($subSubGroups){ write-verbose "$tabs Level $nestLevel $subgroup returned the following groups:`n$( $subSubGroups | out-string)" } $subSubGroupsCollection += $subSubGroups } #add results from subgroups only if they aren't blank or already included $allNestedGroups += $subSubGroupsCollection | ?{$_ -and $allNestedGroups -notContains $_} } else{ #If we hit an empty group, return with nothing Return } #Once we've recursed through all groups, return results Write-Verbose "$tabs Level $nestLevel Returning from $ADObject with following nestedGroups:`n$( $allNestedGroups | Out-String)" $allNestedGroups
After this, we add each group in subGroups to allNestedGroups, and recursively call this function on each of those groups
Check out the innards of Get-ntfsAccess on Script Center for the full definition of the function.
Check ACL of each path for any matching ACE
#Loop through each folder foreach($folder in $folderlist){ #Get ACL for the folder write-verbose "Checking access for $folder" $accessList = (get-acl $folder).access #Set access to null - used for determining noAccess $access = $null #For each access item in the ACL foreach($accessItem in $accessList){ #Identify the group and track overall access for -noaccess parameter $accessItemGroup = $accessItem.IdentityReference.Value #loop through groups we are searching for foreach($group in $entity) { #if we match a group... if($accessItemGroup -like $group) { #add it to a list, if listOnly or noAccess if($listOnly) { #add the result unless $noAccess is specified if(-not $noAccess){ $folderResults += $folder | where { $folderResults -notcontains $_ } } $access = 1 write-verbose "Access for $folder" } #otherwise, add an object with access information to results else { #add the result unless $noAccess is specified if(-not $noAccess){ $folderResults += [pscustomobject] @{ Path = $folder; Group = $accessItemGroup; FileSystemRights = $accessItem.FileSystemRights; AccessControlType = $accessItem.AccessControlType; IsInherited = $accessItem.isInherited; InheritanceFlags = $accessItem.InheritanceFlags; PropagationFlags = $accessItem.PropagationFlags } } $access = 1 write-verbose "Access for $folder" } } } } #if we didn't find a group matching input, $access is still $null if(-not $access -and $noAccess){ $folderResults += $folder write-verbose "No access for $folder" } } $folderResults
This is the final piece of the code. I tried to comment things out to show what’s going on.
We have all the users/groups and folders (or files) to test. The first stage looks at a single path and gets the ACL. The next stage looks at each ACE in the ACL to see if it matches the users/groups we are interested in. When it does, add it to the results (if checking for access) or note that we do have access (if checking for no access). Once we’ve checked all ACEs in an ACL, if nothing matched the group we note that there is no access (if checking for no access).
The function in action
Pick up the script here and make sure you are running PowerShell 3
I start out with C:\temp:
Everything has default inherited ACEs, apart from one I added for a group nested under VSR VDI PFS:
Perhaps I only want a list of folders the provided entities have access to:
Lastly, the folders that VSR VDI PFS does not have access to (all but one):
Bonus: You can run this on network shares
My next step is to add a parameter that controls the depth of path recursion. As it is, I’m using the built in Get-ChildItem -Recurse parameter, but I could see situations where you only care about the first few levels of directories.
On an aside, Raimund Andrée wrote a fantastic module that covers managing permissions beyond get and set-acl – check it out here.