ArcticWFox
ArcticWFox

Reputation: 13

Get ACL Folder & Subfolder + Users Using Powershell

Is it possible to get the permissions of a folder and its sub-folders then display the path, group, and users associated to that group? So, to look something like this. Or will it have to be one folder at a time.

-Folder1

-Line separator
-Group
-Line separator
-List of users

-Folder2

 -Line separator
 -Group
 -Line separator
 -List of users

The script I've come up with so far be warned I have very little experience with powershell. (Don't worry my boss knows.)

Param([string]$filePath)

$Version=$PSVersionTable.PSVersion
if ($Version.Major -lt 3) {Throw "Powershell version out of date. Please update powershell." }

Get-ChildItem $filePath -Recurse | Get-Acl | where { $_.Access | where { $_.IsInherited -eq $false } } | select -exp Access | select IdentityReference -Unique | Out-File .\Groups.txt

$Rspaces=(Get-Content .\Groups.txt) -replace 'JAC.*?\\|','' |
 Where-Object {$_ -notmatch 'BUILTIN|NT AUTHORITY|CREATOR|-----|Identity'} | ForEach-Object {$_.TrimEnd()}
$Rspaces | Out-File .\Groups.txt

$ErrorActionPreference= 'SilentlyContinue'
$Groups=Get-Content .\Groups.txt
ForEach ($Group in $Groups)
{Write-Host"";$Group;Write-Host --------------
;Get-ADGroupMember -Identity $Group -Recursive | Get-ADUser -Property DisplayName | Select Name}

This only shows the groups and users, but not the path.

Upvotes: 1

Views: 18089

Answers (2)

Paul Machin
Paul Machin

Reputation: 39

I have managed to get this working for me.

I edited the below section to show the Name and Username of the user.

$Members = Get-ADGroupMember $Group -Recursive -ErrorAction Ignore | % Name | Get-ADUser -Property DisplayName | Select-Object DisplayName,Name | Sort-Object DisplayName

This works really well, but would there be a way to get it to stop listing the same group access if it's repeated down the folder structure?

For example, "\example1\example2" was assigned a group called "group1" and we had the following folder structure:

\\example1\example2\folder1 
\\example1\example2\folder2
\\example1\example2\folder1\randomfolder
\\example1\example2\folder2\anotherrandomfolder

All the folders are assigned the group "group1", and the current code will list each directory's group and users, even though it's the same. Would it be possible to get it to only list the group and users once if it's repeated down the directory structure?

The -notcontains doesn't seem to work for me

If that makes sense?

Upvotes: 0

TheMadTechnician
TheMadTechnician

Reputation: 36277

Ok, let's take it from the top! Excellent, you actually declare a parameter. What you might want to consider is setting a default value for the parameter. What I would do is use the current directory, which conveniently has an automatic variable $PWD (I believe that's short for PowerShell Working Directory).

Param([string]$filePath = $PWD)

Now if a path is provided it will use that, but if no path is provided it just uses the current folder as a default value.

Version check is fine. I'm pretty sure there's more elegant ways to do it, but I honestly don't have never done any version checking.

Now you are querying AD for each group and user that is found (after some filtering, granted). I would propose that we keep track of groups and members so that we only have to query AD once for each one. It may not save a lot of time, but it'll save some if any group is used more than once. So for that purpose we're going to make an empty hashtable to track groups and their members.

$ADGroups = @{}

Now starts a bad trend... writing to files and then reading those files back in. Outputting to a file is fine, or saving configurations, or something that you'll need again outside of the current PowerShell session, but writing to a file just to read it back into the current session is just a waste. Instead you should either save the results to a variable, or work with them directly. So, rather than getting the folder listing, piping it directly into Get-Acl, and losing the paths we're going to do a ForEach loop on the folders. Mind you, I added the -Directory switch so it will only look at folders and ignore files. This happens at the provider level, so you will get much faster results from Get-ChildItem this way.

ForEach($Folder in (Get-ChildItem $filePath -Recurse -Directory)){

Now, you wanted to output the path of the folder, and a line. That's easy enough now that we aren't ditching the folder object:

    $Folder.FullName
    '-'*$Folder.FullName.Length

Next we get the ACLs for the current folder:

    $ACLs = Get-Acl -Path $Folder.FullName

And here's where things get complicated. I'm getting the group names from the ACLs, but I've combined a couple of your Where statements, and also added a check to see if it is an Allow rule (because including Deny rules in this would just be confusing). I've used ? which is an alias for Where, as well as % which is an alias for ForEach-Object. You can have a natural line brake after a pipe, so I've done that for ease of reading. I included comments on each line for what I'm doing, but if any of it is confusing just let me know what you need clarification on.

    $Groups = $ACLs.Access | #Expand the Access property
        ?{ $_.IsInherited -eq $false -and $_.AccessControlType -eq 'Allow' -and $_.IdentityReference -notmatch 'BUILTIN|NT AUTHORITY|CREATOR|-----|Identity'} | #Only instances that allow access, are not inherited, and aren't a local group or special case
        %{$_.IdentityReference -replace 'JAC.*?\\'} | #Expand the IdentityReference property, and replace anything that starts with JAC all the way to the first backslash (likely domain name trimming)
        Select -Unique #Select only unique values

Now we'll loop through the groups, starting off by outputting the group name and a line.

    ForEach ($Group in $Groups){
        $Group
        '-'*$Group.Length

For each group I'll see if we already know who's in it by checking the list of keys on the hashtable. If we don't find the group there we'll query AD and add the group as a key, and the members as the associated value.

        If($ADGroups.Keys -notcontains $Group){
            $Members = Get-ADGroupMember $Group -Recursive -ErrorAction Ignore | % Name
            $ADGroups.Add($Group,$Members)
        }

Now that we're sure that we have the group members we will display them.

        $ADGroups[$Group]

We can close the ForEach loop pertaining to groups, and since this is the end of the loop for the current folder we'll add a blank line to the output, and close that loop as well

    }
    "`n"
}

So I wrote this up and then ran it against my C:\temp folder. It did tell me that I need to clean up that folder, but more importantly it showed me that most of the folders don't have any non-inherited permissions, so it would just give me the path with an underline, a blank line, and move to the next folder so I had a ton of things like:

C:\Temp\FolderA
---------------

C:\Temp\FolderB
---------------

C:\Temp\FolderC
---------------

That doesn't seem useful to me. If it is to you then use the lines above as I have them. Personally I chose to get the ACLs, check for groups, and then if there are no groups move to the next folder. The below is the product of that.

Param([string]$filePath = $PWD)

$Version=$PSVersionTable.PSVersion
if ($Version.Major -lt 3) {Throw "Powershell version out of date. Please update powershell." }

#Create an empty hashtable to track groups
$ADGroups = @{}

#Get a recursive list of folders and loop through them
ForEach($Folder in (Get-ChildItem $filePath -Recurse -Directory)){
    # Get ACLs for the folder
    $ACLs = Get-Acl -Path $Folder.FullName

    #Do a bunch of filtering to just get AD groups
    $Groups = $ACLs | 
        % Access | #Expand the Access property
        where { $_.IsInherited -eq $false -and $_.AccessControlType -eq 'Allow' -and $_.IdentityReference -notmatch 'BUILTIN|NT AUTHORITY|CREATOR|-----|Identity'} | #Only instances that allow access, are not inherited, and aren't a local group or special case
        %{$_.IdentityReference -replace 'JAC.*?\\'} | #Expand the IdentityReference property, and replace anything that starts with JAC all the way to the first backslash (likely domain name trimming)
        Select -Unique #Select only unique values

    #If there are no groups to display for this folder move to the next folder
    If($Groups.Count -eq 0){Continue}

    #Display Folder Path
    $Folder.FullName
    #Put a dashed line under the folder path (using the length of the folder path for the length of the line, just to look nice)
    '-'*$Folder.FullName.Length

    #Loop through each group and display its name and users
    ForEach ($Group in $Groups){
        #Display the group name
        $Group
        #Add a line under the group name
        '-'*$Group.Length
        #Check if we already have this group, and if not get the group from AD
        If($ADGroups.Keys -notcontains $Group){
            $Members = Get-ADGroupMember $Group -Recursive -ErrorAction Ignore | % Name
            $ADGroups.Add($Group,$Members)
        }
        #Display the group members
        $ADGroups[$Group]
    }
    #output a blank line, for some seperation between folders
    "`n"
}

Upvotes: 2

Related Questions