On first execution, the script back up folders Access Control List and on the second execution it generates report which shows the folder Access Control List modifications, if any. The report is in human readable format. Please note that script DOES NOT show who have made the modification.

 

PowerShell
Edit|Remove
<#   
.Synopsis   
.Description   
    Back up the folder called "Access Control List" to an XML file. Upon the next execution, compare the folders back up and the current ACL    
    and show ACL discrepancy report. If ACL discrepancy exists, recover folders ACL from back.   
       
.Example   
    .\Track-NTFSChanges.ps1 -Path E:\ -BackupACL   
    <Back up all folders and subfolders ACL under E: drive into XML>   
   
.Example   
    .\Track-NTFSChanges.ps1 -Path E:\ -DiscrepancyReport   
    <Compare folder current and back up ACL permissions and generate folder ACL discrepancy report>   
   
.Example   
    .\Track-NTFSChanges.ps1 E:\ -FixACL   
    <Recoveer folder ACL from back up if it was modified since last back up.>   
.Notes   
    Author: Artak Gabrielyan   
    Date:   January 292016   
    Version: 1   
.Link   
    https://www.linkedin.com/in/artakgabrielyan   
    https://www.upwork.com/freelancers/~010927360345e1d2a2   
#>   
   
Param(   
    [parameter(Mandatory=$True,    
               helpmessage='Recursive folder path to backup Access Control List')]   
                [String]$Path,   
    [parameter(Mandatory=$False,    
               helpmessage='Switch to compare folder current access control list  with old ACL.')]   
                [Switch]$DiscrepancyReport,   
    [parameter(Mandatory=$False,   
                helpmessage='switch to compare folder current access control list with old ACL and recover old ACL')]   
                [Switch]$FixACL,   
    [parameter(Mandatory=$False,   
                helpmessage='switch to perform folder Access Control List backup.')]   
                [Switch]$BackupACL   
)   
   
Begin{   
       
    $BackupDirectory = "$((Get-Location).Path)\"   
    # Does log folder exist   
    $logDirectory = $LogFile -replace (($logfile -split "\\")[-1]) , ""   
   
    If (!(Test-Path $logDirectory))   
    {   
        Write-Host "`n$(get-date -f 'yyyy-MM-dd HH:mm:ss') Error: '$($logfile)' log directory doesn't exist.`n" -ForegroundColor Red   
        Break   
    }   
   
    $LogMsg = "`n$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') ### Script is started. ###`n"    
    Write-Host $LogMsg -foregroundcolor cyan   
       
   
    If (!($DiscrepancyReport) -and !($FixACL) -and !($BackupACL))   
    {   
        $LogMsg = "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') At least one inpute parameter should be submitted!"    
        Write-Host $LogMsg -foregroundcolor Red   
           
        Return   
    }   
       
    If (!(Test-Path $Path -PathType 'Container'))   
    {   
        Throw  "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') '$($Path)' folder doesn't exist or it is not a folder!"    
        Return   
    }   
       
    [Array]$Global:Folders = (Get-Item $Path).FullName   
    $Folders += (Get-ChildItem $Path -Directory).FullName   
}   
Process{   
   
    ## Check file paths existance ##################   
    If (!(Test-Path $BackupDirectory ))   
    {   
        $LogMsg = "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') error '$($BackupDirectory )' folder does not exist!"    
        Write-Host $LogMsg -foregroundcolor red   
           
        Break   
    }   
   
    # Compare ACL of each folder, subfolder and if there is necessity recover accessed from last ACL back   
    Function Compare-ACL{   
    Param(   
    [Parameter(Mandatory=$false)]   
    [Switch]$FixACL   
    )   
        # Take acl from xml file, the last screenshoted acl   
        $XMLpath = Get-ChildItem $BackupDirectory -filter Track-NTFSChanges*.xml | sort creationtime -descending | select -first 1   
   
        # Snapshot file doesn't exist   
        If (!($xmlpath))   
        {   
            $LogMsg = "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') The ACL permission back up file is found in current directory"     
            Write-Host $LogMsg -ForegroundColor Red   
               
            Return   
        }   
   
        $Xml = Import-Clixml $xmlpath.fullname   
        $LogMsg = "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') The latest ACL permission backup\snapshot file is $($BackupDirectory )$($xmlpath)."    
        Write-Host $LogMsg -foregroundcolor cyan   
           
           
        $historyACL= @()   
   
        # Take actual ACL and compare with historical, old acl ###############        
        $Folders| foreach-object{   
               
            $FolderPath = $_   
            Write-Host "`n*** $FolderPath"  -f Cyan   
            $CurrentACL = @()   
            $HistoryACL = @()   
   
            If (!(Test-Path $FolderPath))   
            {   
                $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss')  error: '$($FolderPath)' folder doesn't exist."    
                Write-Host $LogMsg -foregroundcolor red   
                   
                Break    
            }   
   
            # Get folder current ACL   
            $OriginalACL = (Get-Acl -path $FolderPath).Access   
               
            # Replace ACL showed as number with named ACL   
            $OriginalACL | Foreach {   
                Switch($_.filesystemrights)    
                {   
                    '268435456'  {$Rights = 'FullControl'}    
                    '-536805376'  {$Rights = 'Modify'}    
                    '-1610612736'  {$Rights = 'ReadAndExecute'}   
                    Default {$Rights = $_}    
                }   
   
                $Permission = $_.identityreference.value, $Rights, $_.inheritanceflags, "none", $_.accesscontroltype   
                $Accessrule = new-object system.security.accesscontrol.filesystemaccessrule($Permission)   
                $CurrentACL += $accessrule   
            }   
               
            # Get ACL from XML file, historical   
            $Xml  | ? {$_.direction -eq $FolderPath } | foreach-object{   
                $Permission = $_.identityreference.value, $_.filesystemrights, $_.inheritanceflags.value, "none", $_.accesscontroltype.value   
                $AccessRule = new-object system.security.accesscontrol.filesystemaccessrule $Permission   
                $HistoryACL += $AccessRule   
            }   
   
            # Compare old and current ACLs and show difference   
            $DiffACL = Compare-Object $historyACL $currentACL -property identityreference, accesscontroltype,filesystemrights    
   
            If (!($DiffACL))    
            {   
                $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss') The '$($FolderPath)' folder ACL is not modified!"    
                write-host $LogMsg -foregroundcolor cyan   
                   
                Return   
            }    
               
            #### Show in table ACL discrepancies   
            $DiscrepACL = @()   
   
            # Select unique users and for each user show old and current ACL permissions   
            $DiffACL | Select -ExpandProperty IdentityReference -Unique | ForEach-Object {   
                $ResultACL = @()   
                $UserName = $_   
                Write-Host $UserName -ForegroundColor Cyan   
   
                # Filter each user historical and current access   
                $HistACL = $HistoryACL | Where {$_.IdentityReference.value -eq $UserName }  | sort FileSystemRights, AccessControlType, IsInherited, InheritanceFlags   
                $CurrentACL = $CurrentACL | Where {$_.IdentityReference.value -eq $UserName } | sort FileSystemRights, AccessControlType, IsInherited, InheritanceFlags   
                   
                # If same user has multiple layer (inherited) and different ACL permissions for same folder   
                If ($HistACL.count -ge $CurrentACL.count)    
                {   
                    # drow ACL discrepancies table   
                    For($i =0; $i -le $HistACL.count-1; $i++)   
                    {   
                        'FileSystemRights','AccessControlType''IdentityReference''IsInherited','InheritanceFlags','PropagationFlags'  | ForEach{   
                                   
                            If ($HistACL)   
                            {   
                                $oValue = ($HistACL[$i] | select -ExpandProperty $_ )   
                            }   
                            Else {$oValue = ''}   
   
                            if ($CurrentACL)   
                            {   
                                $cValue = ($CurrentACL[$i] | select -ExpandProperty $_)   
                            }   
                            Else {$cValue =''}   
   
                            $ACLitem = New-Object PSObject   
                            $ACLitem | Add-Member -type NoteProperty -Name "NAME" -Value "$_"   
                            $ACLitem | Add-Member -type NoteProperty -Name "Old" -Value $oValue   
                            $ACLitem | Add-Member -type NoteProperty -Name "Current" -Value $cValue   
   
                            $ResultACL += $ACLitem   
                               
                        }   
                               
                        $ACLitem = New-Object PSObject   
                        $ACLitem | Add-Member -type NoteProperty -Name "NAME" -Value $null   
                        $ACLitem | Add-Member -type NoteProperty -Name "Old" -Value $null   
                        $ACLitem | Add-Member -type NoteProperty -Name "Current" -Value $null   
   
                        $ResultACL += $ACLitem   
                    }   
                       
                    # display ACL discrepancies table   
                    $ResultACL   
                }   
   
                # If same user has one layer (not inherited) ACL permissions for same folder   
                Else   
                {   
                    # Drow ACL discrepancies table   
                    For($i =0; $i -le $CurrentACL.count-1; $i++)   
                    {   
                        'FileSystemRights','AccessControlType''IdentityReference''IsInherited','InheritanceFlags','PropagationFlags'  | ForEach{   
                            if ($HistACL)    
                            {   
                                $oValue = ($HistACL[$i] | select -ExpandProperty $_ )   
                            }   
                            Else {$oValue = ''}   
   
                            if ($CurrentACL)   
                            {   
                                $cValue = ($CurrentACL[$i] | select -ExpandProperty $_)   
                            }   
                            Else {$cValue =''}   
   
                            $ACLitem = New-Object PSObject   
                            $ACLitem | Add-Member -type NoteProperty -Name "NAME" -Value "$_"   
                            $ACLitem | Add-Member -type NoteProperty -Name "Old" -Value $oValue   
                            $ACLitem | Add-Member -type NoteProperty -Name "Current" -Value $cValue   
   
                            $ResultACL += $ACLitem   
                               
                        }   
   
                        $ACLitem = New-Object PSObject   
                        $ACLitem | Add-Member -type NoteProperty -Name "NAME" -Value $null   
                        $ACLitem | Add-Member -type NoteProperty -Name "Old" -Value $null   
                        $ACLitem | Add-Member -type NoteProperty -Name "Current" -Value $null   
   
                        $ResultACL += $ACLitem   
                    }   
                       
                    # display ACL discrepancies table   
                    $ResultACL   
                }   
   
                If (($CurrentACL -ne $null) -and ($FixACL -eq $true))   
                {   
                    Fix-ACL $FolderPath $CurrentACL 'remove'   
                }   
                   
                If (($HistACL -ne $null) -and ($FixACL -eq $true))   
                {   
                    Fix-ACL $FolderPath $HistACL 'recover'   
                }   
            }   
        }   
    }   
   
    # Remove unnecessary ACL or add missing ACL to folder   
    Function Fix-ACL   
    {   
    Param(   
    [parameter(Mandatory=$True)]   
                [Array]$FolderPath,   
    [parameter(Mandatory=$True)]   
                [Array]$ACL,   
    [parameter(Mandatory=$True) ]   
                [String]$Action)   
   
        Write-Host "Remove ACL" -f Cyan   
        $ACL   
   
        # Remove Permission ####   
        If ($Action -eq 'remove')   
        {   
            $ACL| Foreach{   
                $Permission = $_.identityreference.value, $_.filesystemrights, $_.inheritanceflags, `   
                                "none", $_.accesscontroltype   
   
                $AccessRule = New-Object system.security.accesscontrol.filesystemaccessrule $Permission   
                $itemACL = Get-Acl -path $FolderPath   
                $itemACL.removeaccessruleall($AccessRule)   
                Set-Acl $FolderPath $itemACL   
            }   
   
            $LogMsg ="$(get-date -f 'yyyy-MM-dd HH:mm:ss') FixACL: The modified ACL is removed!"    
            Write-Host $LogMsg -foregroundcolor cyan   
               
        }   
   
        # recover\set permission #####   
        Elseif ($Action -eq 'recover')   
        {   
            $ACL | Foreach{   
                $Permission = $_.identityreference.value, $_.filesystemrights, $_.inheritanceflags, "none", $_.accesscontroltype   
                $accessrule = new-object system.security.accesscontrol.filesystemaccessrule $Permission   
                $itemACL = Get-Acl -path $FolderPath   
                $itemACL.AddAccessRule($accessrule)   
                set-acl $FolderPath $itemacl   
            }   
            $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss') fixacl: acl recovered, from backup!"    
            Write-Host $LogMsg -foregroundcolor cyan   
               
        }   
    }   
   
    # Snapshot ACL for folders and keep them in xml file as back up   
    Function Backup-ACL{   
        $fullACL = @()   
   
        ## Back up ACL and export to xml ###############      
        $Folders | sort -unique | foreach-object{   
            $FolderPath = $_   
            $FoldersACL = @()   
            $ACL = @()   
            If (!(Test-Path $FolderPath))   
            {   
                $LogMsg = "$(Get-Date -f 'yyyy-MM-dd HH:mm:ss') Error: '$($FolderPath)' folder doesn't exist."    
                Write-Host $LogMsg -ForegroundColor red   
                   
                Return   
            }   
   
            $currentACL  = (Get-Acl -Path $FolderPath).access   
            $currentACL | ForEach-Object{   
   
                # Replace permission showed as number with name, string    
                Switch($_.filesystemrights)    
                    {    
                        '268435456'  {$Rights = 'FullControl'}    
                        '-536805376'  {$Rights = 'Modify'}    
                        '-1610612736'  {$Rights= 'ReadAndExecute'}   
                        Default {$Rights = $_}    
                    }   
   
                $ACL = New-Object psobject -property @{   
                       
                    direction = $FolderPath   
                    filesystemrights = $Rights   
                    accesscontroltype = $_.accesscontroltype   
                    identityreference = $_.identityreference   
                    isinherited       = $_.isinherited   
                    inheritanceflags  = $_.inheritanceflags   
                    propagationflags  = $_.propagationflags   
                }   
                   
                $FoldersACL += $acl    
            }   
            $fullACL += $FoldersACL   
        }   
        if ($fullACL)   
        {   
            # Back up ACL into xml and keep as back up   
            $clixmlpath = "$($BackupDirectory )Track-NTFSChanges-$(get-date -f 'yyyymmddhhmmss').xml"   
            $fullACL | export-clixml $clixmlpath    
   
            $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss') The folders ACLs back up is successfully permformed to '$($clixmlpath)'. "   
            write-host $LogMsg -foregroundcolor cyan   
        }   
        else   
        {   
            $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss') error: the ACL back up is not permformed, please check files path. "    
            write-host $LogMsg -foregroundcolor red   
        }   
    }   
   
    # Retrive users if    
    Function Retrive-User{   
        Param(   
            [parameter(Mandatory=$false)]   
            [string]$Users   
        )   
       
        If ($Users -like "*\*")   
        {   
            $Arr = $Users -split "\\"""   
            $Domain = $Arr[0]   
            $Account = $Arr[1]    
               
            # Does USER exist            
            if (Get-WmiObject win32_useraccount -Filter "name = '$($Account)' AND domain = '$($Domain)'")   
            {   
                Return 1   
            }   
            # Does GROUP exist   
            ElseIf (Get-WmiObject win32_group -Filter "name = '$($Account)' AND domain = '$($Domain)'")   
            {   
                Return 1   
            }   
            Else    
            {   
                # User is not found   
                $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss') '$($Users)' is not found and"    
                Write-Host $LogMsg -foregroundcolor Red   
                Return 0   
            }   
        }   
        ElseIf ($Users -eq 'Everyone')   
        {   
            Return 1       
        }   
        ElseIf ($Users -eq 'Authenticated Users')   
        {   
            Return 1       
        }   
        Else   
        {   
            $LogMsg = "$(get-date -f 'yyyy-MM-dd HH:mm:ss') $($Users) is not found."    
            Write-Host $LogMsg -foregroundcolor Red   
               
            Return 0   
        }   
    }   
}   
End{   
    # Create folder structure based from csv file   
    if ($CreateFolders)   
    {   
        Backup-ACL   
    }   
   
    # compare csv folders acl with old, back up acl and recovery acl.   
    if ($FixACL)    
    {   
        Compare-ACL -FixACL   
    }   
    elseif ($DiscrepancyReport) # compare csv folders acl with old, back up acl and just report   
    {   
        Compare-ACL   
    }   
   
    # Backup folder ACL    
    if ($BackupACL -and !($CreateFolders))   
    {   
        Backup-ACL   
    }   
   
    $LogMsg = "`n$(get-date -f 'yyyy-MM-dd HH:mm:ss') ### Script is finished. ###"     
    Write-Host $LogMsg -foregroundcolor cyan   
 }
 

If there is necessity it could recover old permissions.

 

 .\Track-NTFSChanges.ps1 -Path E:\ -BackupACL

 

 .\Track-NTFSChanges.ps1 -Path E:\ -DiscrepancyReport

 

 .\Track-NTFSChanges.ps1 -path E:\ -FixACL