PowerShell Script to disable inheritance on Windows
#Requires -Version 5.1
<#
.SYNOPSIS
Disables NTFS inheritance on folders located at a specified depth ("level")
beneath a list of SMB share root paths supplied in a CSV file.
.DESCRIPTION
For each share root listed in the CSV, the script finds every folder that
sits EXACTLY N levels below the root and disables inheritance on it.
Level 0 = the share root folder itself
Level 1 = the immediate sub-folders of the root
Level 2 = the sub-folders of those, etc.
When you disable inheritance you must choose what happens to the permissions
that were being inherited. This mirrors the Windows "Block Inheritance" dialog:
A) Convert inherited permissions into explicit permissions on this object.
-> Current effective permissions are preserved as explicit ACEs.
-> SetAccessRuleProtection($true, $true)
B) Remove all inherited permissions from this object.
-> Inherited ACEs are dropped; only explicit ACEs already present remain.
-> SetAccessRuleProtection($true, $false)
-> WARNING: a folder left with no explicit ACEs can become inaccessible.
.PARAMETER CsvPath
Path to the CSV file. Prompted for if omitted.
.PARAMETER PathColumn
Name of the CSV column holding each share's root path. Auto-detected from a
list of common names, otherwise prompted for.
.PARAMETER Level
The depth at which to disable inheritance. Prompted for if omitted.
.PARAMETER Mode
'A' or 'B' as described above. Prompted for if omitted.
.PARAMETER LogPath
Where the run log is written. Defaults to a timestamped file in the current dir.
.PARAMETER WhatIfPreview
Dry run. Reports what WOULD change without modifying any ACLs. Recommended for
the first run, especially with Mode B.
.NOTES
* Run in an ELEVATED PowerShell session and as an account with rights to read
and write the ACLs (ownership / SeRestorePrivilege may be required).
* Operates against the Windows PowerShell 5.1 engine.
* Paths in the CSV may be local (D:\Shares\Foo) or UNC (\\server\share).
.EXAMPLE
.\Disable-SmbInheritance.ps1
Fully interactive: prompts for CSV, level and A/B.
.EXAMPLE
.\Disable-SmbInheritance.ps1 -CsvPath .\shares.csv -PathColumn Path -Level 1 -Mode A -WhatIfPreview
Dry run against level-1 folders, converting inherited perms to explicit.
#>
[CmdletBinding()]
param(
[string]$CsvPath,
[string]$PathColumn,
[Nullable[int]]$Level,
[ValidateSet('A', 'B')]
[string]$Mode,
[string]$LogPath = ".\Disable-Inheritance_$(Get-Date -Format 'yyyyMMdd_HHmmss').log",
[Nullable[bool]]$ReplaceChildPermissions,
[switch]$WhatIfPreview
)
# ------------------------------------------------------------------ helpers ---
function Write-Log {
param(
[string]$Message,
[ValidateSet('INFO', 'OK', 'WARN', 'ERROR', 'WHATIF')]
[string]$Severity = 'INFO'
)
$line = '{0} [{1,-6}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Severity, $Message
switch ($Severity) {
'ERROR' { Write-Host $line -ForegroundColor Red }
'WARN' { Write-Host $line -ForegroundColor Yellow }
'OK' { Write-Host $line -ForegroundColor Green }
'WHATIF' { Write-Host $line -ForegroundColor Cyan }
default { Write-Host $line }
}
Add-Content -LiteralPath $LogPath -Value $line
}
function Get-FoldersAtLevel {
<#
Returns the directory objects that sit EXACTLY $Level levels below $RootPath.
Level 0 returns the root itself.
#>
param(
[Parameter(Mandatory)] [string]$RootPath,
[Parameter(Mandatory)] [int]$Level
)
$current = @(Get-Item -LiteralPath $RootPath -ErrorAction Stop)
for ($depth = 1; $depth -le $Level; $depth++) {
$next = @()
foreach ($dir in $current) {
$next += Get-ChildItem -LiteralPath $dir.FullName -Directory -ErrorAction SilentlyContinue
}
$current = $next
if ($current.Count -eq 0) { break }
}
return $current
}
function Set-ChildObjectsInheritable {
<#
Replicates the Advanced Security dialog option:
"Replace all child object permission entries with inheritable
permission entries from this object."
For every descendant (sub-folders AND files) of $ParentPath:
* every EXPLICIT (non-inherited) ACE is removed
* inheritance is (re-)enabled
so the child keeps ONLY the inheritable permissions flowing down
from $ParentPath. This wipes any independent / protected ACLs the
children were holding.
#>
param(
[Parameter(Mandatory)] [string]$ParentPath,
[switch]$Preview
)
$ok = 0; $err = 0
$descendants = Get-ChildItem -LiteralPath $ParentPath -Recurse -Force -ErrorAction SilentlyContinue
foreach ($item in $descendants) {
$ip = $item.FullName
try {
if ($Preview) { $ok++; continue }
$cAcl = Get-Acl -LiteralPath $ip
# remove every explicit (non-inherited) ACE - this clears protected/independent ACLs
$explicit = @($cAcl.Access | Where-Object { -not $_.IsInherited })
foreach ($ace in $explicit) { [void]$cAcl.RemoveAccessRuleSpecific($ace) }
# enable inheritance so the child picks up the parent's inheritable ACEs
$cAcl.SetAccessRuleProtection($false, $false)
Set-Acl -LiteralPath $ip -AclObject $cAcl -ErrorAction Stop
$ok++
}
catch {
Write-Log " child reset FAILED on '$ip' : $($_.Exception.Message)" 'ERROR'
$err++
}
}
return [pscustomobject]@{ Ok = $ok; Failed = $err }
}
# -------------------------------------------------------------- gather input ---
# 1) CSV file
if (-not $CsvPath) {
$CsvPath = Read-Host 'Enter the full path to the CSV file listing the SMB shares'
}
if (-not (Test-Path -LiteralPath $CsvPath)) { throw "CSV file not found: $CsvPath" }
$rows = @(Import-Csv -LiteralPath $CsvPath)
if ($rows.Count -eq 0) { throw "CSV file is empty: $CsvPath" }
# 2) Which column holds the path
if (-not $PathColumn) {
$columns = $rows[0].PSObject.Properties.Name
$candidates = @('Path', 'SharePath', 'SmbSharePath', 'LocalPath', 'UNCPath',
'FullName', 'RootPath', 'Folder', 'Directory', 'Share')
$PathColumn = $candidates | Where-Object { $columns -contains $_ } | Select-Object -First 1
if (-not $PathColumn) {
Write-Host "Columns found in CSV: $($columns -join ', ')"
$PathColumn = Read-Host 'Which column contains the share root path?'
}
}
if (($rows[0].PSObject.Properties.Name) -notcontains $PathColumn) {
throw "Column '$PathColumn' does not exist in the CSV."
}
# 3) Level
if ($null -eq $Level) {
$parsed = 0
do {
$entry = Read-Host 'Disable inheritance at which level? (0 = share root, 1 = sub-folders, 2 = sub-sub-folders, ...)'
$valid = [int]::TryParse($entry, [ref]$parsed) -and $parsed -ge 0
if (-not $valid) { Write-Host 'Please enter a non-negative whole number.' -ForegroundColor Yellow }
} until ($valid)
$Level = $parsed
}
# 4) A / B choice
if (-not $Mode) {
Write-Host ''
Write-Host 'When inheritance is disabled, choose what to do with the inherited permissions:'
Write-Host ' A) Convert inherited permissions into explicit permissions on this object.'
Write-Host ' B) Remove all inherited permissions from this object.'
do { $Mode = (Read-Host 'Enter A or B').Trim().ToUpper() } until ($Mode -in @('A', 'B'))
}
# A -> preserve (convert to explicit); B -> do not preserve (remove inherited)
$preserveInheritance = ($Mode -eq 'A')
# 5) Replace all child object permission entries (True / False)
if ($null -eq $ReplaceChildPermissions) {
Write-Host ''
Write-Host 'Replace all child object permission entries with inheritable permission entries from this object?'
Write-Host (" True = wipe explicit permissions on EVERY sub-folder/file below the level-{0}" -f $Level)
Write-Host ' folder and force them all to inherit from it.'
Write-Host ' False = leave child objects untouched.'
do { $rcp = (Read-Host 'Enter True or False').Trim() } until ($rcp -match '^(true|false|t|f|y|n|yes|no)$')
$ReplaceChildPermissions = ($rcp -match '^(true|t|y|yes)$')
}
# ----------------------------------------------------------------- confirm ----
Write-Host ''
Write-Host '------------------------------ Summary ------------------------------'
Write-Host (" CSV file : {0}" -f $CsvPath)
Write-Host (" Column : {0} ({1} share rows)" -f $PathColumn, $rows.Count)
Write-Host (" Level : {0}" -f $Level)
Write-Host (" Mode : {0} -> {1}" -f $Mode, $(if ($preserveInheritance) {'convert inherited to explicit'} else {'REMOVE inherited permissions'}))
Write-Host (" Replace child entries : {0}" -f $ReplaceChildPermissions)
if ($ReplaceChildPermissions) {
Write-Host ' ^ WARNING: this wipes ALL explicit/independent permissions on every' -ForegroundColor Yellow
Write-Host ' sub-folder and file and makes them inherit from the target folder.' -ForegroundColor Yellow
}
Write-Host (" Log file : {0}" -f $LogPath)
if ($WhatIfPreview) { Write-Host ' *** DRY RUN (WhatIfPreview) - no changes will be made ***' -ForegroundColor Cyan }
Write-Host '---------------------------------------------------------------------'
if (-not $WhatIfPreview) {
$go = Read-Host 'Proceed with these changes? (Y/N)'
if ($go -notmatch '^(y|yes)$') { Write-Host 'Aborted by user.'; return }
}
# ------------------------------------------------------------------ run -------
$applied = 0; $failed = 0; $skipped = 0
$childApplied = 0; $childFailed = 0
Write-Log ("Run started. CSV='{0}' Column='{1}' Level={2} Mode={3} Preserve={4} ReplaceChildren={5} DryRun={6}" -f `
$CsvPath, $PathColumn, $Level, $Mode, $preserveInheritance, [bool]$ReplaceChildPermissions, [bool]$WhatIfPreview)
foreach ($row in $rows) {
$root = ([string]$row.$PathColumn).Trim()
if ([string]::IsNullOrWhiteSpace($root)) { continue }
if (-not (Test-Path -LiteralPath $root)) {
Write-Log "Share path not found, skipping: $root" 'WARN'
$skipped++
continue
}
try {
$targets = @(Get-FoldersAtLevel -RootPath $root -Level $Level)
} catch {
Write-Log "Could not enumerate '$root' : $($_.Exception.Message)" 'ERROR'
$failed++
continue
}
if ($targets.Count -eq 0) {
Write-Log "No folders at level $Level under: $root" 'WARN'
$skipped++
continue
}
foreach ($folder in $targets) {
$fp = $folder.FullName
try {
$acl = Get-Acl -LiteralPath $fp
# isProtected = $true (disable inheritance); preserveInheritance per A/B choice
$acl.SetAccessRuleProtection($true, $preserveInheritance)
if ($WhatIfPreview) {
Write-Log "Would disable inheritance (preserve=$preserveInheritance) on: $fp" 'WHATIF'
if ($ReplaceChildPermissions) {
$res = Set-ChildObjectsInheritable -ParentPath $fp -Preview
$childApplied += $res.Ok
Write-Log " -> would reset $($res.Ok) child object(s) to inherit from this folder" 'WHATIF'
}
}
else {
Set-Acl -LiteralPath $fp -AclObject $acl -ErrorAction Stop
Write-Log "Disabled inheritance (preserve=$preserveInheritance) on: $fp" 'OK'
$applied++
# Replace child permissions AFTER the target ACL is written, so children
# inherit the freshly-updated permission set from this folder.
if ($ReplaceChildPermissions) {
$res = Set-ChildObjectsInheritable -ParentPath $fp
$childApplied += $res.Ok
$childFailed += $res.Failed
Write-Log " -> reset $($res.Ok) child object(s) to inherit (failed: $($res.Failed))" 'OK'
}
}
}
catch {
Write-Log "Failed on '$fp' : $($_.Exception.Message)" 'ERROR'
$failed++
}
}
}
# ----------------------------------------------------------------- summary ----
Write-Host ''
Write-Log ("Run complete. Target folders Applied={0} Failed={1} Skipped={2}. Child objects reset={3} (failed {4}).{5}" -f `
$applied, $failed, $skipped, $childApplied, $childFailed, $(if ($WhatIfPreview) {' (dry run - nothing changed)'} else {''}))
Write-Host ("Log written to: {0}" -f (Resolve-Path -LiteralPath $LogPath))