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

Comments (6)

  1. AllenRich says:

    Very interesting article, I read this article, it good explain for auditing file server permissions through power-shell and check the permissions of my file server folder. I also tried this file access auditing (http://www.lepide.com/file-server-audit/)
    tool that assist to see who made and what changes in files and track all modification like add, delete, edit etc. and keep track of the access or modifications to critical files with the report.

  2. David says:

    Hi,

    Is it possible to just output folders and not files as well? I can't get the syntax right on this line:

    Get-ChildItem -LiteralPath $StartPath -Recurse |

    Thanks

  3. David says:

    Scratch the above – figured it out!

       Get-ChildItem -LiteralPath $StartPath -Recurse | ?{ $_.PSIsContainer } |

  4. David says:

    Hi again,

    I've found when the folders or Access Masks column contain a comma Excel interprets this as separate into columns. To disable go to Control Panel > Region and Language > Additional Settings > List separator – replace the comma for anything, reopen CSV then save as XLSX

    Roman – When the Trustee lists CREATOR OWNER the Access Mask reports 268435456 – is there a way to convert this too?

    Other than that – great script!

    Thanks

  5. srhopkin says:

    Hi,
    I would interested to know, how you could audit single folder, rather than a drive ?
    Any pointers would be appreciated.

  6. alan says:

    I get an error:

    Select-Object : A positional parameter cannot be found that accepts argument ‘Expand’.

    I’m using PowerShell V2.0 on Windows 2008 R2 SP1

    Is there something I need in terms of "expanded capabilities of PowerShell V2 which had to be installed on some servers"?

    Thanks for any help… and I appreciate Daid’s inclusdion re: PSIsContainer…

    ‘Regards, Alan

Skip to main content