Audit File Server Permissions Using PowerShell

A customer recently asked me to help refine a VBScript which they use to enumerate permissions on all their file servers. The team periodically queries the entire file system and imports the results into a database for historical auditing. Their existing VBScript did provide the required information using nested loops to walk the file system and WMI calls to “Win32_LogicalFileSecuritySettings” to enumerate permissions. However, the script took up to 3 days to run on large volumes, and server resources fluctuated greatly depending on folder depth and often crashed. 

My first instinct was to parse through the 1,600 lines of their current script and look for ways to streamline and improve it. However, I instead decided to re-write the script from scratch using PowerShell…

First, let’s define a few variables. I set “ErrorActionPreference” so that the script would continue if it encountered any exit errors:

    $ErrorActionPreference = "Continue"

I also define the local computer name so that we can include it in the results:

    $strComputer = $env:ComputerName

Next, I enumerate all the drive letters and store them in a collection variable:

    $colDrives = Get-PSDrive -PSProvider Filesystem

Now, let’s begin doing some work. For each drive letter I need to output the permissions of every file and folder on the volume. The easiest way to walk the file system is to specify the starting root directory and use “Get-ChildItem” with the “Recurse” parameter. I also use the “LiteralPath” parameter in case file or folder names include special characters, such as square brackets or ampersands:

    ForEach ($DriveLetter in $colDrives) {
$StartPath = "$DriveLetter`:\"
Get-ChildItem -LiteralPath $StartPath –Recurse }

Next, we can use “Get-Acl” and expand the “Access” column to return all the data related to the ACE:

    Get-Acl | Select * -Expand Access

But, wait… I’ve discovered a problem! Get-Acl crashes if I try to enumerate permissions on a file or folder which contains special characters. Unfortunately, Get-Acl does not support the LiteralPath parameter, which is a well-documented shortcoming. So, we will need to “escape” any special characters in the path name.

I found several examples of various subroutines and functions which parse the path name and escapes special characters. But, I couldn’t help thinking these methods simply added overhead to the script. Therefore, I changed my approach and instead used Get-Item (which does support the LiteralPath parameter) and the GetAccessControl method which returns the same information as Get-Acl:

    ForEach {
$FullPath = Get-Item -LiteralPath (Get-Item -LiteralPath $_.PSPath)
(Get-Item -LiteralPath $FullPath).GetAccessControl() }

Now, with all our data exposed, we can begin to select the columns and transform them into the required export format. My customer had specific guidelines for how the data should be labeled and formatted. But, this can obviously be changed to suit other requirements.

In the first column, we’ll write the server name:

    Select @{N='Server Name';E={$strComputer}}

Then we’ll write the full path of the file or folder:

    @{N='Full Path';E={$FullPath}}

If the object is a directory, write “D”. Otherwise, write “F”:

    @{N='Type';E={If($FullPath.PSIsContainer -eq $True) {'D'} Else {'F'}}}

Write the owner:

    @{N='Owner';E={$_.Owner}}

Write each of the accounts associated with the ACE:

    @{N='Trustee';E={$_.IdentityReference}}

For each ACE, write whether the permissions are inherited:

    @{N='Inherited';E={$_.IsInherited}}

Write the inheritance flags:

    @{N='Inheritance Flags';E={$_.InheritanceFlags}}

Write the propagation flags:

    @{N='Ace Flags';E={$_.PropagationFlags}}

Write the access control type:

    @{N='Ace Type';E={$_.AccessControlType}}

Write the permissions:

    @{N='Access Masks';E={$_.FileSystemRights}}

And finally, we’ll stream the results into an export file whose name includes the server and drive letter. My customer also asked that each column be separated by “|” as a delimiter:

    Export-CSV -NoTypeInformation -Delimiter "|" -Path "$strComputer`_$DriveLetter.csv"

That's it. The resulting script is very fast and very efficient. What used to take 3 days now runs in just over 1 hour and utilizes minimal resources. The only drawback is that the script depends on expanded capabilities of PowerShell v2 which had to be installed on some of the servers. RjZ

AuditFilePermissions.zip