I have run into scenarios where I need to ensure file or registry settings are set and stay set to a certain level. An example is STIG* vulnerability ID V-220717.
*Security Technical Implementation Guide (STIG) – Based on Department of Defense (DoD) policy and security controls. Implementation guide geared to a specific product and version. Contains all requirements that have been flagged as applicable for the product which have been selected on a DoD baseline.
In the example, it is required that the permissions for “C:\” are set to:
Type – “Allow” for all
Inherited from – “None” for all
Principal – Access – Applies to
Administrators – Full control – This folder, subfolders and files
SYSTEM – Full control – This folder, subfolders and files
Users – Read & execute – This folder, subfolders and files
Authenticated Users – Modify – Subfolders and files only
Authenticated Users – Create folders / append data – This folder only
Having to verify this on one or two systems manually is no big deal but manual checks are not scalable beyond a few systems. Let’s see if we can figure out how to check the permissions.
Let’s start by looking at the current ACL to see what we have to work with from a PowerShell standpoint. When we run a Get-ACL, we get results, but what type of object is that? Let’s run a .GetType() to see what we’re working with.

We can see it is a System.Security.AccessControl.FileSystemSecurity and also notice the real info is held in the access property. Let’s expand that to see what we have.

Now we could write up some overly complicated way to parse that and compare against what we want, but I suspect that would end up being a lot of manual changes each time we want to use for something new. Looking at what Get-ACL returned we know that the ACL can be a PowerShell object, so we should be able to build an ACL of what we want, then compare. In the image above we see that each entry has five attributes: IdentityReference, AccessControlType, InheritanceFlags, PropagationFlags, and FileSystemRights. From that we can assume that we can create a new rule with those data points. Let’s translate one of the entries to the values we’d need.
Type – “Allow” for all
Inherited from – “None” for all
Administrators – Full control – This folder, subfolders and files
Let’s start by just breaking that into our five points.
IdentityReference – Administrators
AccessControlType – Allow
InheritanceFlags – None
PropagationFlags – This folder, subfolders and files
FileSystemRights – Full control
Now we go a step further and convert those into the format needed for the object.
Administrators becomes BUILTIN\Administrators.
We can see AccessControlType decodes to a 0 for allow.
InheritanceFlags becomes 3 (bitwise 1 + 2) for sub containers and objects.
PropagationFlags is 0 for none.
When we get to FileSystemRights, we hit the tough one. At least it could be tough if you have to get really granular with advanced options. Luckily for us, in this example we need a simple Full Control and the FileSystemRights page gives us the common bitwise values to include Full Control which is 2032127.
Taking all the extra text out of that we are left with:
IdentityReference – BUILTIN\Administrators
AccessControlType – 0
InheritanceFlags – 3
PropagationFlags – 0
FileSystemRights – 2032127
or…
“BUILTIN\Administrators”,”0″,”3″,”0″,”2032127″
Now let’s do the same for all the other required entries…

“BUILTIN\Administrators”,”0″,”3″,”0″,”2032127″
“NT AUTHORITY\SYSTEM”,”0″,”3″,”0″,”2032127″
“BUILTIN\Users”,”0″,”3″,”0″,”1179817″
“NT AUTHORITY\Authenticated Users”,”0″,”0″,”0″,”4″
“NT AUTHORITY\Authenticated Users”,”0″,”3″,”2″,”-536805376″
Did you notice the CSV like format? Let’s go with it and use a little PowerShell to turn those values into an actual ACL object.
$CSV = @'
"IdentityReference","AccessControlType","InheritanceFlags","PropagationFlags","FileSystemRights"
"BUILTIN\Administrators","0","3","0","2032127"
"NT AUTHORITY\SYSTEM","0","3","0","2032127"
"BUILTIN\Users","0","3","0","1179817"
"NT AUTHORITY\Authenticated Users","0","0","0","4"
"NT AUTHORITY\Authenticated Users","0","3","2","-536805376"
'@
$perms = ConvertFrom-Csv $CSV
$tempACL = New-Object System.Security.AccessControl.DirectorySecurity
ForEach($perm in $perms){
$newAce = $tempACL.AccessRuleFactory(
(New-Object System.Security.Principal.NTAccount($perm.IdentityReference.split('\')[0], $perm.IdentityReference.split('\')[1])),
$perm.FileSystemRights,
$FALSE,
$perm.InheritanceFlags,
$perm.PropagationFlags,
$perm.AccessControlType
)
$tempACL.AddAccessRule($newAce)
}
If we check on the built object we can see it matches what we set in the CSV format.

Now that we have seen what is currently set and have an object of what we expect/want to be set, we need to compare them. Enter a handy little PS function.
Function Match-ACL {
<#
.SYNOPSIS
Compare two ACL's ACE(s). Will return True if the Access Rules match and will return false if the Access rules do not match.
Note: Ignores Inherited permissions.
.DESCRIPTION
Checks if two ACLs are matching by finding identical ACE(s) in the Current and Desired non-inherited ACL(s).
Returns False if all Desired ACE(s) match Current ACE(s) but there is not the same amount of ACE(s) in each.
.EXAMPLE
Acl-Match -CurrentACL (Get-ACL C:\temp) -DesiredACL (Get-ACL C:\test)
.EXAMPLE
It is also possible to create a System Security object in powershell to compare to:
$DesiredACL = New-Object System.Security.AccessControl.DirectorySecurity
#Create the ACE
$ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
'Contoso\Domain Users',
'Modify',
'ContainerInherit, ObjectInherit', #ThisFolderSubfoldersAndFiles
'None',
'Allow'
)
#Add the ACE to the ACL
$DesiredACL.AddAccessRule($ace)
$ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
"Contoso\User1",
'FullControl',
'ContainerInherit, ObjectInherit', #ThisFolderSubfoldersAndFiles
'None',
'Allow'
)
$DesiredACL.AddAccessRule($ace)
Acl-Match -CurrentACL (Get-ACL C:\temp) -DesiredACL $DesiredACL
.NOTES
ToDo:
Output object similar to Compare-Object
#>
param(
[System.Security.AccessControl.FileSystemSecurity]$DesiredACL,
[System.Security.AccessControl.FileSystemSecurity]$CurrentACL
)
$DesiredRules = $DesiredACL.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
$CurrentRules = $CurrentACL.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
$Matches = @()
Foreach($DesiredRule in $DesiredRules){
$Match = $CurrentRules | Where-object {
($DesiredRule.FileSystemRights -eq $_.FileSystemRights) -and
($DesiredRule.AccessControlType -eq $_.AccessControlType) -and
($DesiredRule.IdentityReference -eq $_.IdentityReference) -and
($DesiredRule.InheritanceFlags -eq $_.InheritanceFlags ) -and
($DesiredRule.PropagationFlags -eq $_.PropagationFlags )
}
If($Match){
$Matches += $Match
}
Else{
Return $False
}
}
If($Matches.Count -ne $CurrentRules.Count){
Return $False
}
Return $True
}
Using the function we can see that the ACL does not match what we want.

Before we look at what to do with this information, let’s put all the code together for checking the ACL in this example.
Function Match-ACL {
<#
.SYNOPSIS
Compare two ACL's ACE(s). Will return True if the Access Rules match and will return false if the Access rules do not match.
Note: Ignores Inherited permissions.
.DESCRIPTION
Checks if two ACLs are matching by finding identical ACE(s) in the Current and Desired non-inherited ACL(s).
Returns False if all Desired ACE(s) match Current ACE(s) but there is not the same amount of ACE(s) in each.
.EXAMPLE
Acl-Match -CurrentACL (Get-ACL C:\temp) -DesiredACL (Get-ACL C:\test)
.EXAMPLE
It is also possible to create a System Security object in powershell to compare to:
$DesiredACL = New-Object System.Security.AccessControl.DirectorySecurity
#Create the ACE
$ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
'Contoso\Domain Users',
'Modify',
'ContainerInherit, ObjectInherit', #ThisFolderSubfoldersAndFiles
'None',
'Allow'
)
#Add the ACE to the ACL
$DesiredACL.AddAccessRule($ace)
$ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
"Contoso\User1",
'FullControl',
'ContainerInherit, ObjectInherit', #ThisFolderSubfoldersAndFiles
'None',
'Allow'
)
$DesiredACL.AddAccessRule($ace)
Acl-Match -CurrentACL (Get-ACL C:\temp) -DesiredACL $DesiredACL
.NOTES
ToDo:
Output object similar to Compare-Object
#>
param(
[System.Security.AccessControl.FileSystemSecurity]$DesiredACL,
[System.Security.AccessControl.FileSystemSecurity]$CurrentACL
)
$DesiredRules = $DesiredACL.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
$CurrentRules = $CurrentACL.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
$Matches = @()
Foreach($DesiredRule in $DesiredRules){
$Match = $CurrentRules | Where-object {
($DesiredRule.FileSystemRights -eq $_.FileSystemRights) -and
($DesiredRule.AccessControlType -eq $_.AccessControlType) -and
($DesiredRule.IdentityReference -eq $_.IdentityReference) -and
($DesiredRule.InheritanceFlags -eq $_.InheritanceFlags ) -and
($DesiredRule.PropagationFlags -eq $_.PropagationFlags )
}
If($Match){
$Matches += $Match
}
Else{
Return $False
}
}
If($Matches.Count -ne $CurrentRules.Count){
Return $False
}
Return $True
}
$CSV = @'
"IdentityReference","AccessControlType","InheritanceFlags","PropagationFlags","FileSystemRights"
"BUILTIN\Administrators","0","3","0","2032127"
"NT AUTHORITY\SYSTEM","0","3","0","2032127"
"BUILTIN\Users","0","3","0","1179817"
"NT AUTHORITY\Authenticated Users","0","0","0","4"
"NT AUTHORITY\Authenticated Users","0","3","2","-536805376"
'@
$perms = ConvertFrom-Csv $CSV
$tempACL = New-Object System.Security.AccessControl.DirectorySecurity
ForEach($perm in $perms){
$newAce = $tempACL.AccessRuleFactory(
(New-Object System.Security.Principal.NTAccount($perm.IdentityReference.split('\')[0], $perm.IdentityReference.split('\')[1])),
$perm.FileSystemRights,
$FALSE,
$perm.InheritanceFlags,
$perm.PropagationFlags,
$perm.AccessControlType
)
$tempACL.AddAccessRule($newAce)
}
Match-ACL -CurrentACL (get-acl -Path "C:\") -DesiredACL $tempACL
If the ACL doesn’t match, setting it to what we want uses the same code to build up the desired ACL object, then we use Set-ACL to apply it.
$CSV = @'
"IdentityReference","AccessControlType","InheritanceFlags","PropagationFlags","FileSystemRights"
"BUILTIN\Administrators","0","3","0","2032127"
"NT AUTHORITY\SYSTEM","0","3","0","2032127"
"BUILTIN\Users","0","3","0","1179817"
"NT AUTHORITY\Authenticated Users","0","0","0","4"
"NT AUTHORITY\Authenticated Users","0","3","2","-536805376"
'@
$perms = ConvertFrom-Csv $CSV
$tempACL = New-Object System.Security.AccessControl.DirectorySecurity
ForEach($perm in $perms){
$newAce = $tempACL.AccessRuleFactory(
(New-Object System.Security.Principal.NTAccount($perm.IdentityReference.split('\')[0], $perm.IdentityReference.split('\')[1])),
$perm.FileSystemRights,
$FALSE,
$perm.InheritanceFlags,
$perm.PropagationFlags,
$perm.AccessControlType
)
$tempACL.AddAccessRule($newAce)
}
Set-ACL -Path "C:\" -AclObject $tempACL
Now we can build a CI from the two scripts

We create a setting for each ACL we need to check, each with its own compliance rule based on a Boolean result with a required “true” result.

We populate the discovery and remediation scripts with the code from above.

You may notice the comment I left in the description that remediation is not enabled. We can verify this by looking at the compliance rule.

So why did we create a remediation script if leaving it disabled? I create these examples to give to customers and while some of the CIs in the baseline are low risk to enforce/remediate, I want to make sure a customer must take (hopefully) thoughtful action to enable what could be a high impact change. In this case that would mean not just deploying the baseline in a remediate status, but also entering this specific CI to verify the settings and enable remediation.
The same general process can be used for registry ACLs too. You just need some minor tweaks. Below is the discovery script for a registry example.
Function Match-RegACL {
<#
.SYNOPSIS
Compare two ACL's ACE(s). Will return True if the Access Rules match and will return false if the Access rules do not match.
Note: Ignores Inherited permissions.
.DESCRIPTION
Checks if two ACLs are matching by finding identical ACE(s) in the Current and Desired non-inherited ACL(s).
Returns False if all Desired ACE(s) match Current ACE(s) but there is not the same amount of ACE(s) in each.
.EXAMPLE
Acl-Match -CurrentACL (Get-ACL C:\temp) -DesiredACL (Get-ACL C:\test)
.EXAMPLE
It is also possible to create a System Security object in powershell to compare to:
$DesiredACL = New-Object System.Security.AccessControl.DirectorySecurity
#Create the ACE
$ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
'Contoso\Domain Users',
'Modify',
'ContainerInherit, ObjectInherit', #ThisFolderSubfoldersAndFiles
'None',
'Allow'
)
#Add the ACE to the ACL
$DesiredACL.AddAccessRule($ace)
$ace = New-Object System.Security.AccessControl.FileSystemAccessRule(
"Contoso\User1",
'FullControl',
'ContainerInherit, ObjectInherit', #ThisFolderSubfoldersAndFiles
'None',
'Allow'
)
$DesiredACL.AddAccessRule($ace)
Acl-Match -CurrentACL (Get-ACL C:\temp) -DesiredACL $DesiredACL
.NOTES
ToDo:
Output object similar to Compare-Object
#>
param(
[System.Security.AccessControl.RegistrySecurity]$DesiredACL,
[System.Security.AccessControl.RegistrySecurity]$CurrentACL
)
$DesiredRules = $DesiredACL.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
$CurrentRules = $CurrentACL.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
$Matches = @()
Foreach($DesiredRule in $DesiredRules){
$Match = $CurrentRules | Where-object {
($DesiredRule.FileSystemRights -eq $_.FileSystemRights) -and
($DesiredRule.AccessControlType -eq $_.AccessControlType) -and
($DesiredRule.IdentityReference -eq $_.IdentityReference) -and
($DesiredRule.InheritanceFlags -eq $_.InheritanceFlags ) -and
($DesiredRule.PropagationFlags -eq $_.PropagationFlags )
}
If($Match){
$Matches += $Match
}
Else{
Return $False
}
}
If($Matches.Count -ne $CurrentRules.Count){
Return $False
}
Return $True
}
$CSV = @'
"IdentityReference","AccessControlType","InheritanceFlags","PropagationFlags","FileSystemRights"
"BUILTIN\Administrators","0","1","0","393216"
"NT AUTHORITY\SYSTEM","0","1","0","983103"
'@
$perms = ConvertFrom-Csv $CSV
$tempACL = New-Object System.Security.AccessControl.RegistrySecurity
ForEach($perm in $perms){
$newAce = $tempACL.AccessRuleFactory(
(New-Object System.Security.Principal.NTAccount($perm.IdentityReference.split('\')[0], $perm.IdentityReference.split('\')[1])),
$perm.FileSystemRights,
$FALSE,
$perm.InheritanceFlags,
$perm.PropagationFlags,
$perm.AccessControlType
)
$tempACL.AddAccessRule($newAce)
}
Match-RegACL -CurrentACL (get-acl -Path HKLM:\SECURITY) -DesiredACL $tempACL