Dargslan.NtfsAclAudit.psm1
|
<# .SYNOPSIS Audit NTFS ACLs on a directory tree: Everyone / Authenticated Users excessive rights, broken inheritance, orphaned SIDs. .DESCRIPTION Part of the Dargslan Windows Admin Tools collection. Free Cheat Sheet: https://dargslan.com/cheat-sheets/ntfs-acl-permission-audit-2026 Full Guide: https://dargslan.com/blog/ntfs-acl-permission-audit-powershell-2026 More tools: https://dargslan.com .LINK https://dargslan.com .LINK https://github.com/Dargslan/powershell-admin-scripts #> $script:Banner = @" +----------------------------------------------------------+ | Dargslan NTFS ACL Audit | https://dargslan.com - Free cheat sheets & eBooks | +----------------------------------------------------------+ "@ function Get-DargslanNtfsPermissions { <# .SYNOPSIS Recursive ACL inventory of a path (top N levels). #> [CmdletBinding()] param([Parameter(Mandatory)][string]$Path,[int]$Depth = 2) $root = Get-Item -LiteralPath $Path $items = @($root) + @(Get-ChildItem -LiteralPath $Path -Directory -Recurse -Depth $Depth -ErrorAction SilentlyContinue) foreach ($i in $items) { try { $acl = Get-Acl -LiteralPath $i.FullName -ErrorAction Stop } catch { continue } foreach ($ace in $acl.Access) { [pscustomobject]@{ Path = $i.FullName Identity = $ace.IdentityReference.Value Rights = $ace.FileSystemRights AccessType = $ace.AccessControlType IsInherited = $ace.IsInherited Owner = $acl.Owner } } } } function Get-DargslanEveryoneAcls { <# .SYNOPSIS Find ACEs granting Everyone / Authenticated Users excessive rights. #> [CmdletBinding()] param([Parameter(Mandatory)][string]$Path,[int]$Depth = 3) Get-DargslanNtfsPermissions -Path $Path -Depth $Depth | Where-Object { $_.AccessType -eq 'Allow' -and $_.Identity -match 'Everyone|Authenticated Users|BUILTIN\\\\Users|INTERACTIVE' -and $_.Rights -match 'FullControl|Modify|Write' } } function Get-DargslanBrokenAclInheritance { <# .SYNOPSIS Find folders with inheritance disabled (likely a one-off override). #> [CmdletBinding()] param([Parameter(Mandatory)][string]$Path,[int]$Depth = 3) Get-ChildItem -LiteralPath $Path -Directory -Recurse -Depth $Depth -ErrorAction SilentlyContinue | ForEach-Object { try { $acl = Get-Acl -LiteralPath $_.FullName -ErrorAction Stop if ($acl.AreAccessRulesProtected) { [pscustomobject]@{ Path = $_.FullName Owner = $acl.Owner AceCount = $acl.Access.Count } } } catch {} } } function Get-DargslanOrphanSidAcls { <# .SYNOPSIS Find ACEs whose IdentityReference still resolves to a raw SID. #> [CmdletBinding()] param([Parameter(Mandatory)][string]$Path,[int]$Depth = 3) Get-DargslanNtfsPermissions -Path $Path -Depth $Depth | Where-Object Identity -match '^S-1-5-' | Select Path, Identity, Rights, AccessType, IsInherited } function Get-DargslanNtfsAclAuditReport { <# .SYNOPSIS Combined NTFS audit with PASS / WARN / FAIL verdict. #> [CmdletBinding()] param([Parameter(Mandatory)][string]$Path,[int]$Depth = 3) $ev = @(Get-DargslanEveryoneAcls -Path $Path -Depth $Depth) $br = @(Get-DargslanBrokenAclInheritance -Path $Path -Depth $Depth) $orph = @(Get-DargslanOrphanSidAcls -Path $Path -Depth $Depth) $score = 0 if ($ev.Count -eq 0) { $score++ } if ($br.Count -le 5) { $score++ } if ($orph.Count -eq 0) { $score++ } $verdict = if ($score -eq 3) { 'PASS' } elseif ($score -ge 1) { 'WARN' } else { 'FAIL' } [pscustomobject]@{ Path = $Path EveryoneFindings = $ev BrokenInherit = $br OrphanSids = $orph EveryoneCount = $ev.Count BrokenCount = $br.Count OrphanCount = $orph.Count Score = $score Verdict = $verdict TimeStamp = (Get-Date).ToString('s') } } function Export-DargslanNtfsAclAuditReport { <# .SYNOPSIS Export the NTFS audit to HTML and JSON. #> [CmdletBinding()] param([Parameter(Mandatory)][string]$Path,[int]$Depth = 3,[string]$OutDir = (Join-Path $env:TEMP 'DargslanNtfsAudit')) if (-not (Test-Path $OutDir)) { New-Item -Type Directory -Path $OutDir | Out-Null } $r = Get-DargslanNtfsAclAuditReport -Path $Path -Depth $Depth $base = 'ntfs-' + ([System.IO.Path]::GetInvalidFileNameChars() | ForEach-Object { $Path = $Path.Replace($_, '_') } ; $Path -replace '[\\\\:]','_') $json = Join-Path $OutDir ($base + '.json') $html = Join-Path $OutDir ($base + '.html') $r | ConvertTo-Json -Depth 6 | Set-Content $json -Encoding UTF8 $body = "<h1>NTFS ACL Audit - $($r.Path)</h1>" $body += "<p>Verdict: <b>$($r.Verdict)</b> ($($r.Score)/3)</p>" $body += '<h2>Everyone / Auth Users excessive</h2>' + ($r.EveryoneFindings | ConvertTo-Html -Fragment) $body += '<h2>Broken inheritance</h2>' + ($r.BrokenInherit | ConvertTo-Html -Fragment) $body += '<h2>Orphan SIDs</h2>' + ($r.OrphanSids | ConvertTo-Html -Fragment) ConvertTo-Html -Body $body -Title 'NTFS ACL Audit' | Set-Content $html -Encoding UTF8 [pscustomobject]@{ Json = $json; Html = $html; Verdict = $r.Verdict } } |