Public/Get-BrokenInheritance.ps1

function Get-BrokenInheritance {
    <#
    .SYNOPSIS
        Finds folders where NTFS permission inheritance has been disabled.
 
    .DESCRIPTION
        Scans the specified paths and reports on folders where access rule inheritance has
        been turned off (AreAccessRulesProtected = True). Broken inheritance is not necessarily
        wrong -- it is commonly used on departmental root folders -- but every instance should
        be documented and justified. Undocumented broken inheritance is a frequent compliance
        finding and a common vector for permission drift.
 
    .PARAMETER Path
        One or more file system paths to scan.
 
    .PARAMETER MaxDepth
        Maximum folder recursion depth. Defaults to 3.
 
    .EXAMPLE
        Get-BrokenInheritance -Path 'D:\Shares\Finance'
 
    .EXAMPLE
        Get-BrokenInheritance -Path '\\server\data' -MaxDepth 5
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,

        [Parameter()]
        [ValidateRange(1, 20)]
        [int]$MaxDepth = 3
    )

    begin {
        function Get-FoldersToDepth {
            param(
                [string]$RootPath,
                [int]$Depth
            )

            $folders = @([PSCustomObject]@{ FullName = $RootPath; Depth = 0 })

            if ($Depth -ge 1) {
                $children = Get-ChildItem -Path $RootPath -Directory -Recurse -Depth ($Depth - 1) -ErrorAction SilentlyContinue
                foreach ($child in $children) {
                    $relativePath = $child.FullName.Substring($RootPath.Length).TrimStart('\', '/')
                    $currentDepth = ($relativePath -split '[\\/]').Count
                    $folders += [PSCustomObject]@{ FullName = $child.FullName; Depth = $currentDepth }
                }
            }

            $folders
        }

        $results = [System.Collections.Generic.List[PSObject]]::new()
    }

    process {
        foreach ($scanPath in $Path) {
            if (-not (Test-Path -Path $scanPath)) {
                Write-Warning "Path not found: $scanPath"
                continue
            }

            Write-Verbose "Scanning for broken inheritance: $scanPath (MaxDepth: $MaxDepth)"

            $folders = Get-FoldersToDepth -RootPath $scanPath -Depth $MaxDepth

            foreach ($folder in $folders) {
                try {
                    $acl = Get-Acl -Path $folder.FullName -ErrorAction Stop
                }
                catch {
                    Write-Warning "Cannot read ACL on $($folder.FullName): $_"
                    continue
                }

                $inheritanceEnabled = -not $acl.AreAccessRulesProtected
                $aceCount           = $acl.Access.Count
                $uniqueIdentities   = ($acl.Access | Select-Object -ExpandProperty IdentityReference -Unique).Count

                $finding = if (-not $inheritanceEnabled) {
                    "INHERITANCE DISABLED - $aceCount explicit ACEs, $uniqueIdentities unique identities"
                }
                else {
                    'OK'
                }

                $result = [PSCustomObject]@{
                    Path                = $folder.FullName
                    InheritanceEnabled  = $inheritanceEnabled
                    ACECount            = $aceCount
                    UniqueIdentities    = $uniqueIdentities
                    Finding             = $finding
                }

                $results.Add($result)
            }
        }
    }

    end {
        $results
    }
}