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.

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

No parameters required

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

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

Cisco UCS Manager Powershell Backup Script

I love Cisco UCS. I’ve been fortunate enough to be involved with the design and setup of a number of UCS clusters over the last few years. As with anything else in this line of work, the need to have backups is critical. Fortunately UCS manager includes a built in methods for backing up via the UCS Manager GUI and CLI. Unfortunately. there is no built in way of scheduling these backups.

From the Horse’s mouth:

Scheduled Backups

You cannot schedule a backup operation. You can, however, create a backup operation in advance and leave the admin state disabled until you are ready to run the backup. Cisco UCS Manager does not run the backup operation, save, or export the configuration file until you set the admin state of the backup operation to enabled.


A quick Google search will reveal a number of options for automating this but I wasn’t able to find anything satisfactory that used the Cisco UCS Powertool Powershell cmdlets. After a few hours working with almost non existent documentation and a helpful co-worker we managed to come up with a working solution which you can find below. I do intend to update it especially in regards to the error checking, but for now it is perfectly workable.

You can also find this on my Github repo.

Backup Cisco UCS Manager cluster

None yet

Connect securely to a UCS Manager cluster and perform all four backup types. Backup files are saved via http to a customisable location. Results will then be emailed for successfull and failed backups.


# Things still todo:
# Make email body more meaningful
# Change subject of email if one or more of the jobs has failed
# Add param support for all of the variables

# Hostname or IP for your SMTP server
$SMTPServer = x.x.x.x
# Email recipients go here
$mailto = me@email.com
# Sender addresses
$mailfrom = UCS@email.com

# Hostname or IP for UCS Cluster
$UCSHostName = x.x.x.x
# Username for account with rights to backup UCS
$UCSUser = user

# These paths are used by the backup jobs to store each of the four types in their own folder with a unique name. Customise as required.
$BackupPath = .\UCS\Backups\
$fullstatePath =  "$BackupPath\full-state\$UCSHostName-{0:yyyMMdd-HHmm}.xml" -f (get-date)
$configallPath =  "$BackupPath\config-all\$UCSHostName-{0:yyyMMdd-HHmm}.xml" -f (get-date)
$configlogicalPath =  "$BackupPath\config-logical\$UCSHostName-{0:yyyMMdd-HHmm}.xml" -f (get-date)
$configsystemPath =  "$BackupPath\config-system\$UCSHostName-{0:yyyMMdd-HHmm}.xml" -f (get-date)

# You should store this in a txt file to run the script non interatively. See http://blogs.technet.com/b/robcost/archive/2008/05/01/powershell-tip-storing-and-using-password-credentials.aspx
$pwd = read-host -AsSecureString | ConvertFrom-SecureString
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UCSUser,$pwd

$result = @()
# Function to test for the presence of a backup file matching the name generated above. If it is present it will return the name, size and creation date in the email body.
function Backup_Check($path){
		$test = test-path $path
		if ($test){
			$jobresults = get-childitem $path | select name,length,creationtime
		} else{
			$jobresults = $path + "backup has failed. File is not present"

		return $jobresults

# Connect to UCS Manager and execute the four backup jobs
connect-ucs $UCSHostName -credential $cred
backup-ucs -type full-state -pathpattern $fullstatePath
backup-ucs -type config-all -pathpattern $configallPath
backup-ucs -type config-logical -pathpattern $configlogicalPath
backup-ucs -type config-system -pathpattern $configsystemPath

# Run the Backup Check function and save the results in the array $result
$result += Backup_Check($fullstatePath)
$result += Backup_Check($configallPath)
$result += Backup_Check($configlogicalPath)
$result += Backup_Check($configsystemPath)

# Convert $result to a string so it can be used as the body of send-mail message
$body = $result | out-string

# Email results of backups
send-mailmessage -to $mailto -Subject 'UCS Backup Report' -from $mailfrom -smtpserver $SMTPServer -body $body