How to monitor NTFS permission changes in PowerShell: step by step
This tutorial shows how to monitor NTFS permission changes in PowerShell to detect when the ACLs (Access Control Lists) of files or folders are changed. It is useful for auditing, security and for keeping control over who has access to sensitive data.
Prerequisites
- Windows 10/11 or Windows Server with PowerShell 5.1 or PowerShell 7+
- Read permissions on the folders to monitor
- Preferably run PowerShell elevated to access all ACLs
- Text editor (e.g., Visual Studio Code) to save the script
Step 1: Why monitor ACLs and the approach
Changes to NTFS permissions do not always create a clear event in the system. The approach here is to periodically read the ACLs of a folder/file, generate a representative hash and compare it with the previous snapshot. When the hash changes, log the difference and send a simple notification.
Step 2: Function to get an ACL 'fingerprint'
We will create a function that reads a folder's ACL and creates a consistent fingerprint. This allows fast comparisons between versions.
function Get-AclFingerprint {
param(
[Parameter(Mandatory)] [string] $Path
)
# Obtém a ACL e cria uma string ordenada para hashing
$acl = Get-Acl -Path $Path
$entries = $acl.Access | ForEach-Object {
"{0}|{1}|{2}|{3}|{4}" -f $_.IdentityReference, $_.FileSystemRights, $_.AccessControlType, $_.InheritanceFlags, $_.PropagationFlags
}
$entries = $entries | Sort-Object
$joined = $entries -join ";"
# MD5 é suficiente para fingerprint (não para segurança criptográfica)
$md5 = [System.Security.Cryptography.MD5]::Create()
$bytes = [System.Text.Encoding]::UTF8.GetBytes($joined)
$hash = $md5.ComputeHash($bytes)
return ([System.BitConverter]::ToString($hash)).Replace("-","")
}
Step 3: Capture initial snapshot and save to file
Save the initial fingerprint to a JSON file for future comparisons. You can monitor multiple folders by saving an object per path.
$paths = @('C:\Dados\Projeto', 'C:\Logs')
$snapshot = @{}
foreach ($p in $paths) {
if (Test-Path $p) {
$snapshot[$p] = Get-AclFingerprint -Path $p
}
}
$snapshot | ConvertTo-Json | Out-File -FilePath 'C:\MonitorACL\acl_snapshot.json' -Encoding UTF8
Step 4: Monitoring script and change detection
Script that loads the snapshot, recalculates fingerprints and, when a change is detected, writes to a log and saves the new snapshot. It also shows how to get the difference in ACL entries to contextualize the change.
# Requer a função Get-AclFingerprint definida acima
$snapshotPath = 'C:\MonitorACL\acl_snapshot.json'
$logPath = 'C:\MonitorACL\acl_changes.log'
$paths = @('C:\Dados\Projeto', 'C:\Logs')
# Carrega snapshot existente
if (Test-Path $snapshotPath) {
$oldSnapshot = Get-Content $snapshotPath -Raw | ConvertFrom-Json | ConvertTo-Hashtable
} else {
$oldSnapshot = @{}
}
foreach ($p in $paths) {
if (-not (Test-Path $p)) { continue }
$newHash = Get-AclFingerprint -Path $p
$oldHash = $oldSnapshot[$p]
if ($oldHash -ne $null -and $newHash -ne $oldHash) {
$time = (Get-Date).ToString('s')
$msg = "[$time] ACL altered for $p (old:$oldHash new:$newHash)"
$msg | Out-File -FilePath $logPath -Append -Encoding UTF8
# Opcional: obter e registar entradas ACL actuais para análise
$acl = Get-Acl -Path $p
$entries = $acl.Access | ForEach-Object { "{0} {1} {2}" -f $_.IdentityReference, $_.FileSystemRights, $_.AccessControlType }
($time + ' - Entries:') | Out-File -FilePath $logPath -Append -Encoding UTF8
$entries | Out-File -FilePath $logPath -Append -Encoding UTF8
}
# Actualiza snapshot em memória
$oldSnapshot[$p] = $newHash
}
# Grava snapshot actualizado
$oldSnapshot | ConvertTo-Json | Out-File -FilePath $snapshotPath -Encoding UTF8
Step 5: Schedule periodic execution (quick example)
For continuous monitoring, you can run the script in a loop with a delay or use Task Scheduler. Example of a simple loop that runs every 5 minutes (useful for testing).
while ($true) {
& 'C:\MonitorACL\Check-AclChanges.ps1'
Start-Sleep -Seconds 300
}
Check the result
Open the defined log file (e.g., C:\MonitorACL\acl_changes.log) to see timestamped entries when changes occurred. Also check acl_snapshot.json to confirm that the hashes were updated. If no record appears despite changes, verify permissions, paths and whether the script has access to Get-Acl for those items.
Conclusion
With this simple method you can monitor NTFS permission changes using PowerShell, log events and save snapshots for auditing. Next steps: integrate with sending email or Teams when a change occurs, and use Event Forwarding to centralize logs. Tip: test first on non-critical folders to validate false positives and adjust the check frequency.