PowerShell NTFS Permission Audit: Find Broken Inheritance

How NTFS Permission Sprawl Happens
Every “quick exception” someone makes — breaking inheritance on a project folder, adding an explicit ACE for a contractor, giving a service account broad access just this once — accumulates silently. File servers that have been in production for several years routinely contain hundreds of folders where inheritance was broken and explicit permissions were never cleaned up. No one audits them until a security incident or compliance review forces the issue. PowerShell’s Get-Acl and ACE introspection properties let you scan an entire share, identify every broken inheritance point, flag overly permissive entries, and export a risk-ranked report in minutes.
Quick Answer
Use Get-Acl on each folder returned by Get-ChildItem -Recurse, check .Access.IsInherited to find explicit ACEs, and check .AreAccessRulesProtected to identify folders where inheritance is broken at the container level.
Getting ACLs with Get-Acl and Inspecting ACE Properties
Each object returned by Get-Acl has an Access property — a collection of FileSystemAccessRule objects. Key properties on each rule are IdentityReference, FileSystemRights, AccessControlType, and IsInherited. The IsInherited property distinguishes ACEs that came from a parent folder from ones that were explicitly set on this object.
$acl = Get-Acl -Path 'C:\FileShare\ProjectData'
$acl.Access | Select-Object IdentityReference,
FileSystemRights,
AccessControlType,
IsInherited |
Format-Table -AutoSize
IdentityReference FileSystemRights AccessControlType IsInherited
----------------- ---------------- ----------------- -----------
BUILTIN\Administrators FullControl Allow True
DOMAIN\ProjectTeam Modify Allow True
DOMAIN\contractor.jane FullControl Allow False
NT AUTHORITY\Authenticated Users Modify Allow False
The two IsInherited = False rows are explicit ACEs — they were set directly on this folder and will survive if the parent ACL changes.
Detecting Inheritance Protection with AreAccessRulesProtected
Broken inheritance means the folder no longer receives ACE changes from its parent. The AreAccessRulesProtected property on the ACL object is a Boolean that is $true whenever inheritance has been disabled for that folder.
function Test-InheritanceBroken {
param([string]$FolderPath)
try {
$acl = Get-Acl -Path $FolderPath -ErrorAction Stop
[PSCustomObject]@{
Path = $FolderPath
InheritanceBroken = $acl.AreAccessRulesProtected
ExplicitAceCount = ($acl.Access | Where-Object { -not $_.IsInherited }).Count
TotalAceCount = $acl.Access.Count
}
}
catch {
Write-Warning "Access denied reading ACL: $FolderPath"
$null
}
}
Test-InheritanceBroken -FolderPath 'C:\FileShare\ProjectData'
Finding Explicit ACEs That Override Inheritance
Filtering on IsInherited -eq $false isolates every ACE that was placed directly on the object. These are the entries worth reviewing — they represent deliberate (or forgotten) exceptions to the standard permission model.
function Get-ExplicitAces {
param([string]$FolderPath)
try {
$acl = Get-Acl -Path $FolderPath -ErrorAction Stop
$acl.Access |
Where-Object { -not $_.IsInherited } |
Select-Object @{N='Path';E={$FolderPath}},
IdentityReference,
FileSystemRights,
AccessControlType
}
catch { $null }
}
Get-ExplicitAces -FolderPath 'C:\FileShare\ProjectData'
Flagging Overly Permissive ACEs for Everyone or Authenticated Users
ACEs granting Modify or FullControl to Everyone, NT AUTHORITY\Authenticated Users, or BUILTIN\Users are the highest risk findings. These identities cover essentially all domain users, making the permission functionally public within the organization.
$riskyIdentities = @(
'Everyone',
'NT AUTHORITY\Authenticated Users',
'BUILTIN\Users'
)
$riskyRights = @('FullControl', 'Modify', 'Write')
function Test-OverlyPermissive {
param([System.Security.AccessControl.FileSystemSecurity]$Acl, [string]$Path)
foreach ($ace in $Acl.Access) {
$identity = $ace.IdentityReference.Value
$rights = $ace.FileSystemRights.ToString()
if ($identity -in $riskyIdentities -and
($riskyRights | Where-Object { $rights -match $_ })) {
[PSCustomObject]@{
Path = $Path
Identity = $identity
Rights = $rights
IsInherited = $ace.IsInherited
RiskLevel = 'High'
}
}
}
}
Recursive Scan Across Deep Directory Trees Efficiently
Combine all the checks above into a single recursive scan. Use -ErrorAction SilentlyContinue on Get-ChildItem to skip inaccessible folders rather than aborting the entire scan.
$scanRoot = 'C:\FileShare'
$findings = [System.Collections.Generic.List[PSCustomObject]]::new()
$folders = Get-ChildItem -Path $scanRoot -Recurse -Directory -ErrorAction SilentlyContinue
foreach ($folder in $folders) {
try {
$acl = Get-Acl -Path $folder.FullName -ErrorAction Stop
if ($acl.AreAccessRulesProtected) {
$findings.Add([PSCustomObject]@{
Path = $folder.FullName
FindingType= 'BrokenInheritance'
Detail = "Inheritance disabled; $($acl.Access.Count) ACEs present"
RiskLevel = 'Medium'
})
}
foreach ($result in (Test-OverlyPermissive -Acl $acl -Path $folder.FullName)) {
$findings.Add($result)
}
}
catch { Write-Warning "Skipped (access denied): $($folder.FullName)" }
}
Write-Host "Scan complete. $($findings.Count) findings across $($folders.Count) folders"
Exporting Findings to CSV with Risk Rating
Sort findings by risk level and path, then export to CSV for review or import into a ticketing system.
Common Errors
- Get-Acl throws access denied on folders where the account lacks read permissions. Even a local administrator may hit this on folders with broken inheritance that removed admin access. Wrap every
Get-Aclcall in atry/catchand log inaccessible paths separately — they are often the most interesting findings. - Inherited ACEs appear in the ACL list — filter with the IsInherited property.
$acl.Accessreturns both inherited and explicit ACEs. Without filtering onIsInherited -eq $false, your “explicit ACE” report will be flooded with inherited entries that are perfectly normal and expected.
Related Cmdlets / See Also
Wrapping Up
An NTFS permission audit built on Get-Acl, AreAccessRulesProtected, and IsInherited filtering turns a multi-day manual review into an automated scan that completes in minutes. Run it quarterly, compare findings between runs, and close the gap between your intended permission model and reality before the next compliance review.


