Alisdair Craik

Alisdair Craik

10 October 2013

Recording NTFS permissions with PowerShell

I was talking to someone this morning about custom file system auditing to overcome a very specific problem and, inspired by that, this is a quick blog post to demonstrate how you can record file system rights using PowerShell.

The Export-NTFSPermissions function below will record the NTFS access rules in your file system and export them to CSV, allowing you to build up a picture of the entire set of permissions that have been assigned within a drive or folder. PowerShell being PowerShell, you can then re-import the CSV to query, manipulate or even re-apply the data whenever you need to.

Now, obviously how often you have assigned specific permissions is going to affect the size of the CSV. If you've assigned permissions sparingly, the CSV should end up quite small and you can specify the folder path nearer the root of your drive. If you have a large number explicit permissions, each CSV is going to be larger and you need to run the command against folders further down the tree. I'll leave it up to you how to split the load, but bear in mind we're logging specific access rules here, not inherited rights. If you want the full picture of rights assigned within F:\MyBigFolder\ you're also going to have to take into account rights assigned at F:\. In short, you always need to go back to the root at some point to get the full picture.

That said, here are the functions you need:

Function Export-NTFSPermissions
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Folder,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$AuditFolder = $env:USERPROFILE
    )
    
    Process
    {
        foreach($root in $Folder)
        {
            $SafeRootName = $root.Replace([System.IO.Path]::VolumeSeparatorChar,'_').Replace([System.IO.Path]::DirectorySeparatorChar,'_')
            $AuditFilePath = [System.IO.Path]::Combine($AuditFolder, $SafeRootName)
            $AuditFilePath = [System.IO.Path]::ChangeExtension($AuditFilePath, ".csv")

            $rootPermissions = Get-AccessRules $root
            $rootPermissions | Export-Csv -Path $AuditFilePath -Force

            Write-Verbose ("Processing '{0}'..." -f $root)
            Write-Verbose ("Recording to '{0}'..." -f $AuditFilePath)
            Get-ChildItem $root -Recurse | %{Get-AccessRules $_.FullName | Export-Csv -Path $AuditFilePath -Append}
        }
    }
}


Function Get-AccessRules
{
    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Path
    )

    Begin
    {
        $RecordErrorsPath = [System.IO.Path]::Combine($env:TEMP, 'AuditErrors.csv')
    }


    Process
    {
        try
        {
            $acl = Get-Item $Path | Get-ACL
            $rules = $acl.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])

            if($rules -eq $null){ return }

            $rules | % {$_ | Add-Member -Name Path -Value $Path -MemberType NoteProperty}
            return $rules            
        }
        catch
        {            
            Write-Warning ("Error processing '{0}'. Path logged to '{1}'." -f $Path, $RecordErrorsPath)
            $Path | Out-File -Path $RecordErrorsPath -Append
        }
    }
}

Dealing with awkward data - SIDs and Access Masks

You'll notice from looking at the output CSV files that generally file system rights and accounts are enumerated in a friendly format. However, you may see two additional views of your data depending on the state of your file system.

SIDs

In the Identity Reference column, you may see a SID instead of a user account or group. This happens when your computer was unable to resolve the SID to a known account. The object with that SID still has rights to that location in your file system, but the computer cannot determine the account itself. To attempt to resolve a SID to an account you can use our own Resolve-SID cmdlet or you can create a SID object and use the translate method:

$sid = new-object System.Security.Principal.SecurityIdentifier $SIDString
$sid.Translate([System.Security.Principal.NTAccount])

Chances are though you're going to get an error here unless you resolve the underlying cause of the problem. However, if you know that the SID refers to an account in another domain in which your computer isn't sufficiently trusted to perform the translation, this might be useful for you to resolve the SID in the origin domain, at least allowing you to view which account or group the SID refers to.

Access Masks

In the File System Rights column you may see a number rather than a nice enumeration of file rights in readable format. This happens because .Net doesn't have an in-built representation for this particular combination of rights and instead displays the integer representation of the flags. It's possible to resolve this number to human readable format, although once you do so you'll be unable to re-import the access rules in to PowerShell. Anyway, resolving access masks is a whole other horror show that deserves a blog post of it's own, so for now I'll leave you with the MSDN article on Access Masks.

<< Back To The Blog