Powershell Replace all child object permission entries with inheritable permission entries from this object

Powershell Replace all child object permission entries with inheritable permission entries from this object

How could would it be if in Powershell there’d be an option to Replace all child object permission entries with inheritable permission entries from this object, like we have in the GUI when applying permissions?

Well, there isn’t :), at least not for now (20181003), but I found a workaround that seems to be working pretty good.

When would we need this?

This is really handy to reset NTFS permissions from a certain level. Imagine this folder structure:

-Folder1 (inheritance disabled, John in Read Only and Albert in Read/Write)
–SubFolder1 (inherited from Folder1)
—-File1.txt (inherited from SubFolder1 plus Mark in Read Only)
—-File2.txt (inherited from SubFolder1)
–SubFolder2 (inherited from Folder1 plus Simon in Read/Write)
—-Sub-SubFolder1 (inherited from SubFolder2 plus Michael in Read Only)
—-Sub-SubFolder2 (inheritnace disabled, John in Read and Write)
–File1.txt (inherited from Folder1 – This is located under Folder1)

I hope you don’t actually have such a permission mess, as this would be bad, however this is just to generalize the example. Let’s also say that Folder1’s permissions are ok and we want to make sure they get replicated under every item recursively. So we want to remove Mark from accessing File1.txt, Simon from SubFolder2, Michael from Sub-SubFolder1 and also, we want to re-enable the inheritance from Sub-SubFolder2 (removing John as well). This is something you can force through with the GUI by flagging Replace all child object permission entries with inheritable permission entries from this object in the Advanced Security Settings.

So, this is fairly simple, but it’s really not cool when you’re trying to automate a permission setup process.

The logic behind the workaround

Let’s first talk about the example we have above, so we’re happy with Folder 1, how do I go and make sure every sub-folder/file will inherit the same permissions and will also remove any addition and reset the inheritance from the parent?

icacls <item> /Reset /T /C

Icacls is the partial answer. We cannot tell it straight to go and do what we want, but we can trigger it from powershell. If we were to run icacls Folder1 /Reset /T /C, this will go and reset permissions on Folder1 too. We don’t want to do that! I’ve added a strikethrough on the command on purpose, to avoid you copying it and making a mistake 🙂
The specific command above will reset (/Reset) permissions recursively (/T) and ignore any error (/C).

An easy way would be to grab a list of folders and files directly under Folder 1 (so, not recursively) and then run icacls for each one of them. Something like this:

Get-ChildItem .\Folder1 | % {icacls $_.FullName /Reset /T /C}

IMPORTANT: The above command will work, but on a large folder with a ton of files and folders, you’ll end up having a problem with files that are directly located under Folder1: in fact, because of the /T switch to go recursively through every file, icacls will try to locate files named the same way and take actions against them. This is true for our Folder1\File1.txt in the example above. The /T command will make it search and take action against every file named File1.txt under Folder1.

Of course, if you don’t have any file under the parent folder, than you won’t have that problem, but, we want a solution that is capable of working in scenarios where there are files under the parent folder and that will make the least of the effort in order to save time on large folders with millions of files.

So here’s when I thought of splitting the operations in two cores, one for the files located under the parent folder and one for the folders.

  1. We need to run through every file under the parent folder and reset permissions for these, without the /T option.
  2. We need to run through every folder under the parent folder and reset permissions for these, with the /T option.

The script that will reset the permissions based on a parent folder

Now that I’ve presented you with some more info, I can show you the basic script I built, which of course you can take and edit to make more robust (this is just an example).

So this script will only require the $Path of the parent folder and it’ll go and reset permissions for every sub-file and sub-folder recursively. To do that it’ll:

  • Grab a list of $SubFiles right under $Path and, if there’s any, start a job ($Job) to go and reset permissions for every of them with icacls, without using the /T option.
    • I leveraged start-job to be safe in cases where I could have a parent folder ($Path) with a lot of files located right under it. This will start a job and it’ll work in the background. Normally though, we don’t have many files, so a process to go through each of them should be enough. (You may switch to using a process instead, see how I handled the subfolders below).
  • Grab a list of $SubFolders and, if there’s any, start an icacl process (and store each process info under $Processes) for each of them.
    • Why a process and not a job this time? Because folders are containers and will contain a lot more files and subfolders. This means that if I was to start a job for every direct sub-folder, then the job would trigger icacls taking more memory than it should. Instead we start right away an icacl process and let it go on its own.
  • If there was any $SubFile, wait for the job ($Job) to finish.
  • If there was any $SubFolder, wait for every single process (in $Processes) to finish.
  • If there was any fatal error in launching the job/processes, it’d be captured in the Try/Catch.

Here it is:

Try  {

  $Path = "\\myshare\Folder1"

  #Start the job that will reset permissions for each file, don't even start if there are no direct sub-files
  $SubFiles = Get-ChildItem $Path -File
  If ($SubFiles)  {
    $Job = Start-Job -ScriptBlock {$args[0] | %{icacls $_.FullName /Reset /C}} -ArgumentList $SubFiles
  }
  
  #Now go through each $Path's direct folder (if there's any) and start a process to reset the permissions, for each folder.
  $Processes = @()
  $SubFolders = Get-ChildItem $Path -Directory
  If ($SubFolders)  {
    Foreach ($SubFolder in $SubFolders)  {
      #Start a process rather than a job, icacls should take way less memory than Powershell+icacls
      $Processes += Start-Process icacls -WindowStyle Hidden -ArgumentList """$($SubFolder.FullName)"" /Reset /T /C" -PassThru
    }
  }

  #Now that all processes/jobs have been started, let's wait for them (first check if there was any subfile/subfolder)
  #Wait for $Job
  If ($SubFiles)  {
    Wait-Job $Job -ErrorAction SilentlyContinue | Out-Null
    Remove-Job $Job
  }
  #Wait for all the processes to end, if there's any still active
  If ($SubFolders)  {
    Wait-Process -Id $Processes.Id -ErrorAction SilentlyContinue
  }
  
  Write-Host "The script has completed resetting permissions under $($Path)."
}
Catch  {
  $ErrorMessage = $_.Exception.Message
  Throw "There was an error during the script: $($ErrorMessage)"
}

Of course, use this with caution and test it in a dev environment before using it in production. I won’t be responsible for any issue that you may get from misusing this.

IT Droplets

IT Droplets