PowerShell to Find Where Your Active Directory Groups Are Used On File Shares

Happy St. Patrick’s Day!  Enjoy some PowerShell limericks here.  Download today’s script from the TechNet Script Gallery.

Where are my AD groups used?

Today's post gives you a script to crawl your file shares and document the AD users and groups referenced in NTFS permissions.  I’m sure others have published similar scripts, but I want to approach it from the angle of Active Directory group cleanup. Using this output together with the script from my last post will give you plenty of insight to go after stale groups.

Leprechaun

Finish this familiar quote, “I can’t delete that group, because ______________ .”  Multiple choice:

  • “I have no idea where it is used.”
  • “The last admin told me to never delete that group.”
  • “That is how the leprechauns get access.”
  • All of the above.

What would we do without file shares?  Well, actually, we would use SharePoint or OneDrive. The truth is file shares have been around for decades, and in most cases mission critical data resides there.  But who can access that data? That is the big question, and many of us cannot give a complete answer.

By the way, if you would like a security report for SharePoint group usage, my peer, Brian Jackett, has a script for that.  (That sentence had more commas than a CSV file.)

The Solution

Our solution today involves two scripts:

  • Get Access Control Entries.  This script scans file server paths provided by an input text file.  The text file simply lists the root UNC path to every share you want to scan. It exports a CSV report of all explicitly defined (not inherited) permissions at the folder level recursively down a file share path.
  • Merge CSV NTFS Scans.  This script combines all of the individual CSV permission reports into a single file for importing into a database.

In my SID history series I included a function to scan file shares for SID history and migrate the NTFS ACL entries to the new SID.  Basically I retooled that code to simply report all access and ignore SID history. This time, however, I used the Access property instead of the SDDL property.  I recommend that you read this particular post for more background information.

The Code

This script is really not that complicated, so it will be a good one to study if you’re learning PowerShell.  The main cmdlet is Get-ACL.  Everything else is loops, error checking, and progress bars.

First, populate paths.txt with local drive paths and/or UNC paths for the root of each share to scan. For each path in the file you will get two CSV output files:

  • ACEs – An exhaustive list of every user or group explicitly assigned permissions at the folder level all the way down the tree.
  • Errors – Here you will find the folder paths with error messages encountered during the scan.  Popular errors include Access Denied and Path Too Long.

Be sure to review the error log for each share scanned.  You may need to run another scan with different credentials.

 #Requires -Version 3.0            
            
Function Get-ACE {            
Param (            
        [parameter(Mandatory=$true)]            
        [string]            
        [ValidateScript({Test-Path -Path $_})]            
        $Path            
)            
            
    $ErrorLog = @()            
            
    Write-Progress -Activity "Collecting folders" -Status $Path `
        -PercentComplete 0            
    $folders = @()            
    $folders += Get-Item $Path | Select-Object -ExpandProperty FullName            
 $subfolders = Get-Childitem $Path -Recurse -ErrorVariable +ErrorLog `
        -ErrorAction SilentlyContinue |             
        Where-Object {$_.PSIsContainer -eq $true} |             
        Select-Object -ExpandProperty FullName            
    Write-Progress -Activity "Collecting folders" -Status $Path `
        -PercentComplete 100            
            
    # We don't want to add a null object to the list if there are no subfolders            
    If ($subfolders) {$folders += $subfolders}            
    $i = 0            
    $FolderCount = $folders.count            
            
    ForEach ($folder in $folders) {            
            
        Write-Progress -Activity "Scanning folders" -CurrentOperation $folder `
            -Status $Path -PercentComplete ($i/$FolderCount*100)            
        $i++            
            
        # Get-ACL cannot report some errors out to the ErrorVariable.            
        # Therefore we have to capture this error using other means.            
        Try {            
            $acl = Get-ACL -LiteralPath $folder -ErrorAction Continue            
        }            
        Catch {            
            $ErrorLog += New-Object PSObject `
                -Property @{CategoryInfo=$_.CategoryInfo;TargetObject=$folder}            
        }            
            
        $acl.access |             
            Where-Object {$_.IsInherited -eq $false} |            
            Select-Object `
                @{name='Root';expression={$path}}, `
                @{name='Path';expression={$folder}}, `
                IdentityReference, FileSystemRights, IsInherited, `
                InheritanceFlags, PropagationFlags            
            
    }            
                
    $ErrorLog |            
        Select-Object CategoryInfo, TargetObject |            
        Export-Csv ".\Errors_$($Path.Replace('\','_').Replace(':','_')).csv" `
            -NoTypeInformation            
            
}            
            
# Call the function for each path in the text file            
Get-Content .\paths.txt |             
    ForEach-Object {            
        If (Test-Path -Path $_) {            
            Get-ACE -Path $_ |            
                Export-CSV `
                    -Path ".\ACEs_$($_.Replace('\','_').Replace(':','_')).csv" `
                    -NoTypeInformation            
        } Else {            
            Write-Warning "Invalid path: $_"            
        }            
    }            

 

Disclaimers

  • This will likely take hours or days to run depending on the size of your shares.
  • You must run this script from PowerShell v3 or later.
  • Paths longer than 260 characters will error.
  • You must run the script with permissions to read all of the folders down the file share tree.
  • In order to keep the script as efficient as possible we do not scan individual file permissions.
  • This script does not look at the share permissions, only NTFS. In my field experience most places use Everyone/FullControl on their share roots and manage permissions with NTFS.

 

Roll ‘em Up

I included a bonus script that will merge all of the CSV output. This is rather short and sweet. It just saves you the time of doing it yourself. The result is a file called NTFSScan.CSV containing all of the CSV output rolled into one file.

The Next Level

Now that you have this rich group data in CSV format you can pull it all into a database for analysis. In the past I have used Microsoft Access for a quick proof-of-concept.  I pulled in the group report, group duplication report, and the merged NTFS permission CSV output. (You could even pull in the AD organizational unit permission report.) I imported these from CSV to new Access tables.  Then I created some queries that relate the data and report on things like:

  • Perfect match group memberships at 100%
  • Group counts by category and scope
  • Empty groups not updated in 12 months
  • Groups not used in NTFS permissions
  • Pivot table (cross tab) report of groups used on each server
  • Summary of groups used in NTFS permissions
  • Etc.

These reports will give you insight into the use of groups in your environment. You can also see where users are assigned permissions directly instead of using groups.

Group Cleanup

There are many factors that go into group cleanup. Just because a group has not been updated in over one year does not always mean it is stale, especially for some of the built-in AD groups. Groups are used in so many places across the enterprise that it is nearly impossible to say that one is not in use at all. However, when combined with usage data like we collected with today’s script, we can get a far more accurate list of which groups are potentially stale. Go here for a list of other group cleanup posts.

Pro Tip: Instead of deleting a global group right away try this: change the group type to Distribution group. That will effectively remove it as a security group. That may be enough of a fail safe that you can flip it back to Global group should the need arise. If no one calls in the next 30 days, then there is a possibility you could completely delete it.

Pro Tip: When it comes time to clean up your groups make sure you have the AD Recycle Bin turned on and a full backup of your Active Directory.

With proper caution and investigation you should now have a good start on stale group cleanup. Happy hunting!

Download the full script from the TechNet Script Gallery.