Audit NTFS Permissions PowerShell Script

Posted: 01/05/2011 in NTFS, PowerShell

This month I find myself in the need for a quick way to do a simple audit of NTFS permissions on a bunch of files servers. As always I wanted to use PowerShell Remoting (with the code executing on the local server) to accomplish this as enumerating permissions is a slow process at the best of times and over the wire this would have been painfully slow.

Now I know that you can use some of the *CACLS executables to do this but if you’ve ever used these tools you will know they seem to default to information overload. All I wanted to do is get the permissions of a path and then check for any inheritance breaks on all its child folders, and be able to export to CSV.

This is the code that I came up with:

function Get-PathPermissions {

param ( [Parameter(Mandatory=$true)] [System.String]${Path}	)

	begin {
	$root = Get-Item $Path
	($root | get-acl).Access | Add-Member -MemberType NoteProperty -Name "Path" -Value $($root.fullname).ToString() -PassThru
	process {
	$containers = Get-ChildItem -path $Path -recurse | ? {$_.psIscontainer -eq $true}
	if ($containers -eq $null) {break}
		foreach ($container in $containers)
		(Get-ACL $container.fullname).Access | ? { $_.IsInherited -eq $false } | Add-Member -MemberType NoteProperty -Name "Path" -Value $($container.fullname).ToString() -PassThru
Get-PathPermissions $args[0]

To use this code on a local machine simply execute the above function and call it, for example Get-PathPermissions D:\FileData. Now as previously mentioned I wanted to be able to audit a large bunch of File Servers and to do that I would first need to create a variable to hold my servers, for example $allServers, then save the above code to the executing server for example C:\Scripts\Get-PathPermissions.ps1. This finally allowed me to run the following command:

icm $allServers -FilePath C:\Scripts\Get-PathPermissions.ps1 -ArgumentList "E:\WallPaper" | Export-Csv C:\PermissionsAudit.csv –NoTypeInformation

The output (C:\PermissionsAudit.csv) can now be manipulated in Excel, for example:

As you can see this provides very simple and easy to read output that can ease auditing NTFS permissions in bulk.

Thanks for reading and I hope you find this useful.



  1. Jakub says:

    Very nice script. Is there any simple way to make it dive into subdirectories?

    • jfrmilner says:

      Jakub, Thank you. This script actually audits subdirectories by default but it will only list them if they differ from the directory you choose.
      For example:
      Get-PathPermissions E:\Admin | ft -a

      (this is just a sample of the results)

      Path FileSystemRights

      —- —————-
      E:\Admin FullControl
      E:\Admin FullControl
      E:\Admin FullControl
      E:\Admin FullControl
      E:\Admin FullControl
      E:\Admin\Applicat FullControl
      E:\Admin\Applicat Modify, Synchronize
      E:\Admin\Applicat ReadAndExecute, Synchronize
      E:\Admin\Applicat Write, ReadAndExecute, Synchronize
      E:\Admin\Applicat Read, Synchronize
      E:\Admin\Data FullControl
      E:\Admin\Data Modify, Synchronize
      E:\Admin\Data ReadAndExecute, Synchronize
      E:\Admin\Data Write, ReadAndExecute, Synchronize
      E:\Admin\Data Read, Synchronize

    • jp says:

      A terrific looking script. Is there a way to show the audit settings as well?

  2. Wesley says:

    I too find myself needing to audit the ACLs on a bunch of file servers. In my case I need to find the folders that I do not have access to (administrator). Is there a way to scan only folders and only save the names of the folders that the script cannot get ACLs from?

    • jfrmilner says:


      Thanks for your comment. I think you will be able to get what you need in a couple of lines of PowerShell:
      First try and list all the folders on one of your servers, for example
      Get-Childitem G:\Powershell -Recurse -ErrorVariable Access
      This will then capture any folders that it is unable to enumerate and places the errors in a variable named $Access, next you need to check this variable for permission errors and list just the path, for example
      $Access | Where-Object { $_.CategoryInfo -Match “PermissionDenied” } | Select-Object TargetObject

      This should list the problem folders your looking for.

      Hope this helps,

  3. David says:

    I need to do something similar to this, I need to audit all servers on my network that have the “Everyone” group listed. Is this possible?

    • jfrmilner says:


      The Everyone group will get enumerated as will all groups that have permissions set. If you only need the Everyone group you could modify the script to report on this only.



  4. John says:

    Every time I run this, I get the following errors:


    Get-Acl : The specified wildcard pattern is not valid: emp[loyee recognition
    At C:\Scripts\Get-PathPermissions.ps1:14 char:11
    + (Get-ACL <<<< $container.fullname).Access | ? { $_.IsInherited -eq $false } | Add-Member -MemberType NoteProperty
    -Name "Path" -Value $($container.fullname).ToString() -PassThru
    + CategoryInfo : NotSpecified: (:) [Get-Acl], WildcardPatternException
    + FullyQualifiedErrorId : RuntimeException,Microsoft.PowerShell.Commands.GetAclCommand


    Is there something that I am doing wrong?

    • jfrmilner says:


      The issue that you reported is due to the Get-Acl cmdlet interpreting the “[” as a wildcard. Unfortunately the Get-Acl cmdlet is lacking a LiteralPath switch but as a work around you can use the Get-Item cmdlet which is able to report the ACLs and has a LiteralPath switch, I have updated the code to utilize this cmdlet instead:

      function Get-PathPermissions2 {

      param ( [Parameter(Mandatory=$true)] [System.String]${Path} )

      begin {
      $root = Get-Item -LiteralPath $Path
      (Get-Item -LiteralPath $root).GetAccessControl().Access | Add-Member -MemberType NoteProperty -Name "Path" -Value $($root.fullname).ToString() -PassThru -Force
      process {
      $containers = Get-ChildItem -path $Path -recurse | ? {$_.psIscontainer -eq $true}
      if ($containers -eq $null) {break}
      foreach ($container in $containers)
      (Get-Item -LiteralPath $container.fullname).GetAccessControl().Access | ? { $_.IsInherited -eq $false } | Add-Member -MemberType NoteProperty -Name "Path" -Value $($container.fullname).ToString() -PassThru -Force

      Let me know how you get on.

      Update: It would seem that the Get-Acl cmdlet is getting the LiteralPath switch in v3!



      • I ran into this problem as well. I added a step that changed the [ to an asterisk! then changed the asterisk to an escaped ] (I think it was ‘[ in the code)

        Then did the same too steps for the reverse bracket.

        Write me and I can give you more specifics. I put it in a function and just call it before assign the value to the get ACL process.

  5. JHusting says:

    HI jfrmilner,
    Thanks for the script!!

    However, When using “./Get-PathPermissions.ps1 D:\data”

    It gather the permissions of all folders in the root of d:\data, but seems to not be able to gather anything about child object…

    This is the error that comes up for ALL child folders.

    The term ‘Value’ is not recognized as the name of a cmdlet, function, script fi
    le, or operable program. Check the spelling of the name, or if a path was inclu
    ded, verify that the path is correct and try again.
    At C:\pwr\Get-PathPermissions.ps1:16 char:6
    + Value <<<< $($container.fullname).ToString() -PassThru
    + CategoryInfo : ObjectNotFound: (Value:String) [], CommandNotFou
    + FullyQualifiedErrorId : CommandNotFoundException

    • jfrmilner says:


      I’m unable to replicate the error your getting but I suspect that in your effort to copy and paste the code into your script file you may have picked up some extra html formatting, try using the “copy to clipboard” or “view source” feature on the code block and try again.



  6. Jhusting says:

    You are right… sorry for wasting your time :(
    There was some space left after each line and didnt notice. Thanks alot!

  7. Starrett says:


    Thanks for posting this script it works great up to a point, See errors below. There seems to be a couple limits when I run this script as Domain Admin. A length issue and a permission issue.
    Is there a way to run the script as “System” to get arround the permission problem?
    Is there a Powershell or Windows tweak to extend the allowed path length?

    Get-ChildItem : The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters,
    and the directory name must be less than 248 characters.
    At C:\cmd\Get-PathPermissions.ps1:10 char:29
    + $containers = Get-ChildItem <<<< -path $Path -Recurse -Force | ? {$_.psIscontainer -eq $true}
    + CategoryInfo : ReadError: (F:\GroupData…ation work data:String) [Get-ChildItem], PathTooLongException
    + FullyQualifiedErrorId : DirIOError,Microsoft.PowerShell.Commands.GetChildItemCommand

    Get-ChildItem : Access to the path 'F:\GroupData\Accounting\Cost Accounting Archives' is denied.
    At C:\cmd\Get-PathPermissions.ps1:10 char:29
    + $containers = Get-ChildItem <<<< -path $Path -Recurse -Force | ? {$_.psIscontainer -eq $true}
    + CategoryInfo : PermissionDenied: (F:\GroupData…unting Archives:String) [Get-ChildItem], UnauthorizedAccessExcept
    + FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand

    • jfrmilner says:


      A couple if interesting issues there.
      1. The 248 characters issue is unfortunately unavoidable in all currently released versions of Windows, further details can be found here In the comments of the page a user reports that this restriction has been removed in Windows 8.
      2. I would recommend that at a minimum Administrators or a custom Admin group is given permissions over all files and folders on each volume instead, this would be best practice. If you insist on running as System then I have read that PsExec (Sysinternals) will be able to do this with the following command “psexec -i -s Powershell.exe”.

      Hope this helps


  8. bikedude101 says:

    what would be the best way to track the CHANGE in permissions, if I want to audit anything that’s changed? Want to stay away from gathering them into files, and comparing the conents each time I run.
    Thanks for your thoughts

    • jfrmilner says:


      If I needed to track the changes of permissions on a given directory I would enable Group Policy or Local Security Policy to audit access and then use the Get-EventLog cmdlet to parse the Security logs for Events 560, 562.

      To audit changes to file permission structure on NTFS, please follow the steps:

      1. Define the group policy “Audit object access” to audit the attemps “Success” or “Failure” for the target computer.
      a. Open the computer local group policy (gpedit.msc) or Create a GPO link it to the target computer OU. (gpmc.msc).
      b. Expand the group policy to [Computer Configuration\Windows Settings\Security Settings\Local Policies\Audit Policy\Audit object access]
      c. Double click “Audit object access”, tick on the option “Define these policy settings”.
      d. Select both “Success” and “Failure”, click OK.
      e. Run “gpudate /force” on the target computer if you choose the GPO deployment.
      2. Enbale the auditing changes on the target NTFS file of folder level.
      a. go to the file or folder properties-> security->Advanced->auditing tab
      b. Click on Add and Add Everyone
      c. Under apply to make sure This Folder, Sub Folders and Files is selected
      d. Click to check the check boxes for Change permissions both “successful” and “failed”.

      Hope this helps,


  9. Deepak says:

    I get an error when I execute the below command, please help
    PS G:\> .\Get-PathPermissions.ps1 G:\Deptinfo\Construction
    “Unable to find type [Parameter(Mandatory=$true)]: make sure that the assembly containing this type is loaded.
    At G:\Get-PathPermissions.ps1:2 char:37
    + param ( [Parameter(Mandatory=$true)] <<<< [System.String]${Path} )"

    • jfrmilner says:

      I’m unable to replicate the error your getting but I suspect that in your effort to copy and paste the code into your script file you may have picked up some extra html formatting, try using the “copy to clipboard” or “view source” feature on the code block and try again.



  10. jayrbarrios says:

    Hi, I have somewhat similar requirement, but im looking for a script that can compare the NewAcl from OldAcl and identify what permission(s) has change to a particular share.

    We’re basically want to investigate the security event 4670 on a file server with the following and generate a readable report to a csv:

    Who changed the permission
    Who changed the ownership
    To Whom was the permission was given
    To Whom was the ownership was given
    Who took ownership
    What permission was change
    Compare the new and old permission


    • jfrmilner says:


      If this information is in the Event log entry then I would suggest using a combination of the Get-EventLog cmdlet to find the and extract the data and then possibly the Select-String cmdlet to parse the results into the format you require.

      Good luck,


  11. Deepak says:


    You were aboslutely right, when I used the copy paste option the code -Name “Path” was being pasted as (-Name &path.) I corrected it manually and it worked perfectly fine.
    It is really frustrating for me as I do not have any scripting knowledge, due to which I was stuck for more than a week for such a small thing.
    Thank you very much for your script.


  12. Norm says:

    Thanks for this script! It’s pretty cool :)

    Just to clarify, when you say “this script actually audits subdirectories by default but it will only list them if they differ from the directory you choose,” do you mean that it only lists a subdirectory when its permissions differ from whatever has been defined in $Path?

    How would one modify the script to change the default to show permissions for everything?

  13. Angelo Papiccio says:

    Thank you for that script. It saved me hours of mucking around myself.

  14. Anthony says:

    Thanks for this script, it work properly but it work only in memory and it’s a important issue. In fact I have some important server to scan (more than 500GB) and the RAM consumption of Poweshell process is higher than 900MB. It’s cause several problems on the server as you imagine.

    Is not possible to write line per line on the csv file ?

    Thank you for your help.

  15. adam olson says:

    I have this script: E:>Get-ChildItem e:Shares\lashares -recurse | Get-Acl > C:\results.csv and am getting the error
    The specified wildcard pattern is not valid. Where do I put this $root = Get-Item -LiteralPath $Path
    (Get-Item -LiteralPath $root).GetAccessControl().Access | Add-Member -MemberType NoteProperty -Name “Path” -Value $($root.fullname).ToString() -PassThru -Force to get it to use literal path so that it bypasses files with a square bracket?

    • jfrmilner says:


      Download PowerShell 3.0 as the Get-ACL cmdlet has been updated with a LiteralPath switch, I think you’ll find this the simplest solution.



  16. Sergey says:

    Hi John, ths for wonderful script!
    As i understand this script analyze only folders not files?
    How can i modify it to analyze files permission to?

    • jfrmilner says:

      Sergey, the script was designed for folders only but you could try removing the where-object eq psIscontainer part and go from there.

      Good luck.


  17. Newbie says:

    Hi jfrmiller,

    Iam new to powershell and this is the first script i used.
    It works very nice, thanks for that. I used it several times to filter out the inheritance breaks, this is much easier then using Icacls for example.

    This is the powershell command i use now:
    C:\temp\inheritancecheck.ps1 “\\fileserver01\folder1” | Export-Csv c:\temp\filename.csv -NoTypeInformation

    I’am trying to get it easier to use on different file servers.
    I would like to import a file called folders.txt

    in that folders.txt there are for example 3 lines

    And i would like to create the output to:


    Do you know how to import this in your script? I tried several things but they all comes up with errors. Like i said iam a newbie to powershell.

    i would really appreciate your help.

    It already saved me lots of hours and hoping that i can get it more automated.

    Thanks for your script!

  18. Jamie says:

    Hi there. I’m rather new to powershell as well and I’m having trouble getting the script to function.

    The command syntax i’m using is:

    .\getacl4.ps1 “\\server-fs\D$\Data” | Export-Csv .\ntfs.csv -notypeinformation

    the error i’m receiving is:

    Missing statement body in foreach loop.
    At C:\users\syncronet\desktop\NTFS Permissions\GetAcl4.ps1:24 char:1
    + <<<< 13
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingForeachStatement

    the script i'm using is exactly as originally posted on this thread. "getacl4.ps1" is what i named the script.

    please help.

    So much appreciated.


  19. Jamie says:

    ignore that. i had some bad lines in the script. looks great guys thanks!

  20. Jamie says:

    This script is great, but I’d like to add a minor change. It might be very simple, but i’m new to powershell…

    I also want to report those directories which do inherit permission, so that one line appears to report the existence of the folder at the appropriate place in the tree, only to list the pathname and that it is inherited, without any permission details.

  21. Joe W says:

    Using Powershell ISE I find it throws a couple of errors although it does actually produce the desired output. The errors are:

    PS H:\> Get-PathPermissions x:\ |export-csv d:\test.csv
    Get-Acl : Cannot validate argument on parameter ‘Path’. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
    At line:4 char:43
    + $root = Get-Item $Path ($root | get-acl <<<< ).Access | Add-Member -MemberType NoteProperty -Name "Path" -Value $($root.fullname).ToString() -PassThru
    + CategoryInfo : InvalidData: (:) [Get-Acl], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.GetAclCommand

    You cannot call a method on a null-valued expression.
    At line:4 char:136
    + $root = Get-Item $Path ($root | get-acl).Access | Add-Member -MemberType NoteProperty -Name "Path" -Value $($root.fullname).ToString <<<< () -PassThru
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

  22. Lars Panzerbjørn says:

    Hey there :-)

    I just wanted to say thanks for a great script.
    I am now trying to change it to only audit the directories in the directory I specify. Interesting challenge ;-)


  23. Peter says:

    Hey jfrmiller,

    i found your script and it helps me alot. Unfortunately i don’t know how to differentiate a inheritance break und new rights of a user or an group. Both records in the csv file look the same.
    Maybe you have a idea to fix that problem.


  24. Ginger says:

    The script can be used to get permission first then add additional permission as needed?

  25. axel says:

    Would anyone know how to modify this script so that as well as listing groups it list group members and adds that to the spreadsheet as well?

  26. Tried everything but script not working on remote servers (working perfectly locally, btw).

    Everything fully patched in Lab environment (PS4 etc.), WinRM setup,…

    It runs Invoke-Command but discovers nothing!
    To be clear, on targeted machine it work like a charm (locally).

    I ran following:
    Invoke-Command -ComputerName $ComputerName -Credential $Credential -FilePath D:\!Scripts\!Functions\Get-PathPermissions.ps1 -ArgumentList C:\Test

    And no output given :(
    Any sugesstions?

  27. Reuben Hyman says:

    I’ve been trying to get something similar done on share locations, getting nowhere.
    My problems are I have path like \\server\folder .
    This folder contains subfolders with permissions, I need to export to Excel.
    So far I’ve found script but when ran I get limitations on Get-ADGrooupMember Cmdlet

    Get-ADGroupMember : The size limit for this request was exceeded
    At line:25 char:38

    cannot find and object with Identity:

    This script below: would be nice if someone could help.


    function Get-NTFSPermissions {

    param (
    foreach ($Share in $ShareName) {
    $ACLs = Get-Acl -Path $Share
    foreach ($ACL in $ACLs) {
    foreach ($AccessRight in $ACL.Access) {
    $ObjectGroup = New-Object -TypeName PSObject
    $ObjectGroup | Add-Member –MemberType NoteProperty –Name ‘Identity’ –Value $AccessRight.IdentityReference
    $ObjectGroup | Add-Member –MemberType NoteProperty –Name ‘DirectoryPath’ –Value $Share
    $ObjectGroup | Add-Member –MemberType NoteProperty –Name ‘SystemRights’ –Value $AccessRight.FileSystemRights
    $defaultgroup= $ObjectGroup | Where{$_.Identity -notlike “Admin Access” -and $_.Identity -notlike “Builtin\Administrators”}

    $Groups = $defaultGroup | Select-Object -ExpandProperty ‘Identity’
    foreach ($Group in $Groups) {
    $grp = $Group.tostring()
    $gp = $grp.Split(‘\’)[1]
    $Users = Get-ADGroupMember -Recursive -Identity $gp | select Name
    $defaultGroup | Add-Member -MemberType NoteProperty -Name ‘MembersoftheGroup’ -Value $Users

    • Newbie says:

      Hi Reuben Hyman,

      For my understanding, you want to get all identities from a share and want to add it into a security group?

      But what exactly is the problem? ” The size limit for this request was exceeded ” error ??

      or is the problem ” cannot find and object with Identity: “?

      first: The size limit for this request was exceeded
      – This is due to a limitation in AD web services, get-adgroupmember default limit is 5000 users, you are using -recursive i expect you have some nested groups in the security group you are using with get-adgroupmember. You can use Quest AD tools (powershell snappin) to workarround the error. Quest has an command: get-qadgroupmember -indirect which is not limited to 5000 users.

      The cannot find an object error:
      – Maybe it is because you are splitting the group name and therefore it cannot find that group? i expect your are splitting domain\groupname to groupname.

      Good luck with it.

      sorry for my english.

  28. adamkimbro says:

    Awesome script!!
    I have running on a network with multiple file servers at various locations. I am having issues with some directories just stopping on script is there away to have it skip over permissions issue and keep running?

  29. Cyril says:

    Awesome script.

    I have a text file with a list of shares – UNC format \\server\share: SHARES.TXT
    I run the script with:
    Get-Content .\Shares.txt | foreach { .\Get-PathPermissions.ps1 “$_” } | Export-Csv .\Audit.Csv -NoTypeInformation

    Great result indeed. Years ago, we had to purchase specific software to manage this kind of inventory on a big scale.

  30. Jim says:

    Thanks for the script. One thing though is it doesn’t seem to handle access denied errors. I’m experimenting with expanding it so I can at least have an entry in the csv that lists the folder that’s inaccessible.

  31. Kirk says:

    Looks like just what I need however when trying to utilize the code shown in the current article here at the top. I would assume since the original article was done in 2011 that this code should be v2.0 & higher compliant? I am putting the function into my $Profile on a 2003R2 server but when the profile script runs I get the following error:

    Get-PathPermissions : Cannot bind argument to parameter ‘Path’ because it is an empty string.
    At line:19 char:22
    + Get-PathPermissions $args[0]
    + ~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Get-PathPermissions], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Get-PathPermissions

    • jfrmilner says:

      Kirk, The line “Get-PathPermissions $args[0]” executes the function which I expect you don’t want to do so remove that line from your profile. I recommend running “Get-Help about_Automatic_Variables” to view the help section regarding the use of $args to further your understanding of the error.

  32. Kirk says:

    Thanks that took care of it, I hadn’t realized initially that that wasn’t really part of the function.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s