Checking For Protected ACLs In Active Directory

Exchange Server setup grants permissions to various groups at various places in the domain and configuration contexts of Active Directory. Since Exchange relies on these permissions in order for everything to work properly, from time to time we see cases where something is not working, and we eventually track it down to the fact that an object has been set to NOT inherit permissions from its parent. This is called a protected ACL, because it is protected from inheriting permissions from the parent. Here’s how this looks in Active Directory Users & Computers:

protectedacl

You can imagine what fun it is to go hunting around, clicking through multiple dialog boxes to try and find an object where inheritance has been unchecked in the GUI.

Fortunately, Powershell makes it easy to evaluate whether an ACL is protected by exposing the ObjectSecurity.AreAccessRulesProtected member on a DirectoryEntry object. This makes it relatively simple to write a script that scans the entire Active Directory for objects with protected ACLs, and I wrote just such a script a couple of months back.

Be aware that it’s normal for the ACLs on certain objects to be protected, such as certain containers in the domain’s System container, as well as any user objects affected by AdminSDHolder. Don’t panic when you see that inheritance is disabled on your administrator accounts, or on the various built-in groups, or the policies in the System container, etc. That’s totally normal. From an Exchange perspective, in the domain context we’d be looking for normal users or OUs that contain users. In the configuration context we’d be looking for anything under the Microsoft Exchange container.

In any case, here is the script. Read the top for usage details.

#################################################################################
#
# The sample scripts are not supported under any Microsoft standard support
# program or service. The sample scripts are provided AS IS without warranty
# of any kind. Microsoft further disclaims all implied warranties including, without
# limitation, any implied warranties of merchantability or of fitness for a particular
# purpose. The entire risk arising out of the use or performance of the sample scripts
# and documentation remains with you. In no event shall Microsoft, its authors, or
# anyone else involved in the creation, production, or delivery of the scripts be liable
# for any damages whatsoever (including, without limitation, damages for loss of business
# profits, business interruption, loss of business information, or other pecuniary loss)
# arising out of the use of or inability to use the sample scripts or documentation,
# even if Microsoft has been advised of the possibility of such damages
#
#################################################################################
#
# Check-Inheritance
#
# The purpose of this script is to check for Active Directory objects that are
# set to NOT inherit permissions from their parent. It takes four parameters,
# all of which are optional:
#
# $BaseDN - This is the root container where we start our search. If not specified
# we use the root of the current domain. A DC can be specified or not.
# $Recurse - This determines whether we search only the root, or whether we
# recursively go through its child containers. $false by default.
# $ContainersOnly - This determines if we look only at containers, or if we also
# inspect every individual leaf object for inheritance. $false by default.
# $Verbose - This determines whether we log every single object we look at,
# or whether we log only problem objects. $false by default.
#
# Examples:
#
# .\Check-Inheritance -BaseDN "LDAP://CN=Users,DC=contoso,DC=com" -Recurse $true -ContainersOnly $false -Verbose $false
# .\Check-Inheritance "LDAP://DC1/CN=Users,DC=contoso,DC=com" $true $false $false
# .\Check-Inheritance "LDAP://CN=Users,DC=contoso,DC=com"
# .\Check-Inheritance
#
# Output looks like this:
#
# Inheritance disabled: CN=Someobject,DC=contoso,DC=com
# Inheritance disabled: CN=Someotherobject,DC=contoso,DC=com
#
# If verbose is turned on, it will also report the DN of every object it looks at:
#
# CN=Object1,DC=contoso,DC=com
# CN=Object2,DC=contoso,DC=com
# Inheritance disabled: CN=Object3,DC=contoso,DC=com
# CN=Object4,DC=contoso,DC=com

param([string]$BaseDN, [bool]$Recurse, [bool]$ContainersOnly, [bool]$Verbose)

function DoContainer($base)
{
$finder = new-object System.DirectoryServices.DirectorySearcher($base, $filter, $propertyList, $scope)
$finder.PageSize = 100
$results = $finder.FindAll()
if ($results.Count -gt 0)
{
foreach ($result in $results)
{
$entry = $result.GetDirectoryEntry()
if ($entry.ObjectSecurity.AreAccessRulesProtected)
{
("Inheritance disabled: " + $entry.distinguishedName)
}
elseif ($Verbose)
{
$entry.distinguishedName
}
if ($Recurse)
{
DoContainer $entry
}
}
}
}

if ($BaseDN -eq "")
{
$dcRoot = [ADSI]"LDAP://RootDSE"
$BaseDN = ("LDAP://" + $dcRoot.dnsHostName + "/" + $dcRoot.DefaultNamingContext)
}

$filter = "(objectClass=*)"
if ($ContainersOnly)
{
$filter = "(|(objectClass=organizationalUnit)(objectClass=container))"
}

$propertyList = @("distinguishedName")
$scope = [System.DirectoryServices.SearchScope]::OneLevel

("Base container: " + $BaseDN)
("Recurse: " + $Recurse)
("ContainersOnly: " + $ContainersOnly)
("Verbose: " + $Verbose)
"Here we go..."
""
$baseEntry = [ADSI]$BaseDN
DoContainer $baseEntry

"Done!"