Powershell – Get ACL Details for Shared Folders

I’ve been having a lot of fun using Powershell more and more since I first discovered it almost 6 years ago now. One thing I try to do in my daily job is challenge myself to use it whenever the opportunity arises. If time isn’t a critical constraint, I find this the best way to learn new methods and good coding habits even when there may already be a tool available that does what you need.

Recently a scenario came up where I was asked to provide the details of every user account that had access to a group of shared folders in a Windows File Server with some limitations. Those being:

  • Only permissions at the top level of the share need be considered
  • Only include Active Directory users and groups (I.E. no locals or builtins)
  • Do not recursively search the contents of groups (groups within groups)

This actually reduces the scope of work considerably but it is still a tedious task when relying solely on the inbuilt Windows Server Administration tools. Powershell it is then.

At a high level you can break the task down to the following

  1. Locate the shared folder
  2. Extract the Access Control List (ACL) and all Access Control Entries (ACEs) within
  3. Filter out undesired ACEs
  4. Gather information from Active Directory on the user and group objects for each ACE
  5. Present the information in a user friendly manner

Extracting the ACL is very easy using the native get-acl cmdlet. For the Active Directory objects, I’ll be using the Quest AD cmdlets rather than the Microsoft AD DS cmdlets. The only reason for this is the environment I wrote the script for is Windows 2003 R2 AD and does not have a Domain Controller running Active Directory Web Services available. Still, I don’t think it would take much to port the Quest cmdlets over to the MS ones.

Here is the script I came up with in the end.

<#
.SYNOPSIS
Get top level NTFS permissions for a file system object and return details of Active Directory users and groups.

.PARAMETER
No parameters required

.DESCRIPTION
Using a combination of native and QuestAD cmdlets, this script will loop through an array of Windows File System objects (local or remote folders) extracting the ACL from each.
It will then look up Active Directory for applicable users and groups and return details for these accounts.
E.g. samaccountname, displayname, whether the object was directly applied to the ACL or from group membership etc.
Unresolved SIDs and builtin objects (Administrators, everyone etc) are discarded early on.
Nested group members (I.E. groups that are members of other groups) are not expanded on. This problem is currently firmly in the too hard basket.
QuestAD CMDlets are used simply because the environment I wrote it for was Windows 2003 AD. It should be very easy to port over to the native Microsoft AD CMDlets.
#>

# Build an array of the objects you want to check here. Can be local paths, UNCs etc. Input method can be directly in the script as below, or imported from an external source.
$Paths = "\\server1\share1", "\\server2\share2"

# Create and initialise a new array for the output
$Output = @()

# Loop through each object in the array
foreach ($path in $paths)
	{
	# Write some progress to the console so you know the script is working
	write-host "Processing $path"

	# Get the ACL for your first object. Ignore any unresolved SIDs, BUILTINs and other common identifiers.
	$ACLs = get-acl -path $path | select -expand access | where-object {$_.IdentityReference -notlike "*S-1-5-21-*"} | where-object {$_.IdentityReference -notlike "*BUILTIN*"} | where-object {$_.IdentityReference -notlike "*NT AUTHORITY*"} | where-object {$_.IdentityReference -notlike "Everyone"} | select IdentityReference,FileSystemRights

	# Loop through each entry returned by the get-acl command
	foreach ($ACL in $ACLs)
		{
		# Convert the IdentityReference property to a string so it can be used with the next cmdlet
		$strObject = ($ACL.IdentityReference).ToString()

		# Find out what type of object it is. Result should only ever be a user or group
		$ACLobjectType = get-qadobject $strObject | select -expand type

		# If it is a user, lets build a new object with the details we want
		if ($ACLobjectType -eq "user")
			{
			# Create a new System.Object
			$objResults = New-Object System.Object

			# Get AD user object information using Quest AD CMDlet
			$user = get-qaduser $strObject | select samaccountname,displayname

			# Populate the results object with data
			$objResults | Add-Member -MemberType NoteProperty -Name Name -Value $user.samaccountname
			$objResults | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $user.displayname
			$objResults | Add-Member -MemberType NoteProperty -Name "Object Type" -Value "User"
			$objResults | Add-Member -MemberType NoteProperty -Name "Membership Comes From" -Value "Direct"
			$objResults | Add-Member -MemberType NoteProperty -Name "Group Name" -Value "N/A"
			$objResults | Add-Member -MemberType NoteProperty -Name "Group Description" -Value "N/A"
			$objResults | Add-Member -MemberType NoteProperty -Name "Group Notes" -Value "N/A"
			$objResults | Add-Member -MemberType NoteProperty -Name "Permission" -Value $ACL.FileSystemRights
			$objResults | Add-Member -MemberType NoteProperty -Name "Path" -Value $path

			# Add the object data to the $Output array
			$Output += $objResults

			# Otherwise, the object is going to be a group
			} else {

			# So, get a list of the members of each group
			$groupmembers = get-qadgroupmember $strObject | select samaccountname,displayname,type

			# Also get some details for the group that can help identify what it is used for
			$groupdeets = get-qadgroup $strObject | select description,notes

			# Loop through each group member and build an object similar to the one used for directly applied users above
			foreach ($member in $groupmembers)
				{

				$objResults = New-Object System.Object
				$objResults | Add-Member -MemberType NoteProperty -Name Name -Value $member.samaccountname
				$objResults | Add-Member -MemberType NoteProperty -Name "Display Name" -Value $member.displayname
				$objResults | Add-Member -MemberType NoteProperty -Name "Object Type" -Value $member.type
				$objResults | Add-Member -MemberType NoteProperty -Name "Membership Comes From" -Value "Group Member"
				$objResults | Add-Member -MemberType NoteProperty -Name "Group Name" -Value $strObject
				$objResults | Add-Member -MemberType NoteProperty -Name "Group Description" -Value $groupdeets.description
				$objResults | Add-Member -MemberType NoteProperty -Name "Group Notes" -Value $groupdeets.Notes
				$objResults | Add-Member -MemberType NoteProperty -Name "Permission" -Value $ACL.FileSystemRights
				$objResults | Add-Member -MemberType NoteProperty -Name "Path" -Value $path

				# Add the object data to the $Output array
				$Output += $objResults
				}
			}
		}
	}
# Send the output array to a csv file
$output | export-csv .\ACLUsersAndGroups.csv -notype

# Also, print it to the console because why not
$output

Fairly straight forward in the end but it has proven to be very useful and adaptable to multiple scenarios.