Managing OU Delegation
Let’s start by looking at how we can keep an eye on delegation of Organizational Units (OUs). The ability to add objects to an OU, or to modify those objects within the OU, is often controlled at the OU level. These rights can be conveyed using a number of different methods, including the “Delegation of Control” wizard in AD Users and Computers, modifying the Access Control List (ACL) of the OU directly, or using a command-line utility like dsacls.exe. Interestingly, there are no direct cmdlets in the Active Directory PowerShell module that Microsoft provides that allow you to get and set ACLs on AD objects. However, that won’t stop us. What we can do, is take advantage of the AD “PS Provider” that comes with the PowerShell module, along with some existing cmdlets called Get-ACL and Set-ACL, which are ordinarily used for getting and setting file system security. They work equally well on AD as PS Provider, so let’s see how that works. Let’s say I want to get the current permissions on my Marketing OU. How can I do that? Type the following into PowerShell to get into the AD PS Provider:
This changes our default drive to our current AD domain. Now that we are “in AD” we can use Get-ACL and Set-ACL to manage security on OUs. Let’s say we want to find out the current ACL on the Marketing OU. We can type:
(Get-Acl "OU=Marketing, DC=cpandl,DC=com").Access
This returns the full Access Control List for the Marketing OU. What we’re doing here is calling the Get-ACL cmdlet on the distinguished name of the Marketing OU, and asking it to return just the “Access” property, which is what holds the security descriptor for that OU. But the output is a bit messy, and what we really want to see is the security principal for a given Access Control Entry (ACE) and it’s permission, so we can modify the statement slightly above to pare down the listing, like this:
(Get-Acl "OU=Marketing, DC=cpandl,DC=com").Access | format-list identityreference, accesscontroltype ,activedirectoryrights
In this example above, we’re using the pipeline to send the output to the format-list, and selecting only the identityReference, AccessControlType and ActiveDirectoryRights properties for each ACE, which returns a much more useful output, as shown in Figure 1:
Figure 1: Viewing an OU's ACL
Now let’s say I wanted to capture the current delegation for my Marketing OU, and save it, so that I can compare it against a later version to see if any permissions have drifted over time. The easiest way to do this is to capture the output in Figure 1 to a CSV file that I can store persistently to the disk. So, in the following statement, I use the Export-CSV cmdlet to save off the current permissions to a CSV file:
(Get-Acl "OU=Marketing, DC=cpandl,DC=com").Access | select identityreference, accesscontroltype,activedirectoryrights | export-csv -Path c:\data\MarketingOUPerms.csv
This command is similar to the previous one, except that instead of piping the ACL output to a format-list cmdlet, we pipe it instead to a Select-Object (alias select) cmdlet to select the three columns we want, and then pipe that output to the export-csv cmdlet to store the current ACL in a file. Once we have the baseline stored in the file, we can do a quick set of commands on a periodic basis to check for inconsistencies. For example, you could run the following commands in a PowerShell script using the Windows Task Scheduler to get a periodic report of the differences. So, let’s see how that would work. First, we need to get the current ACL on the OU, and store it in the variable. This looks pretty similar to above:
$newACL = (Get-Acl "OU=Marketing, DC=cpandl,DC=com").Access | select identityreference, accesscontroltype,activedirectoryrights
Now that we’ve stored the current ACL in the variable $newACL, we want to retrieve our baseline ACL from the CSV file we stored earlier and store it in , and use the Compare-Object PowerShell cmdlet to find the differences between the baseline and the newest version of the ACL list, as shown here:
$baselinePerms = import-csv -Path C:\data\marketingOUPerms.csv
Compare-Object -ReferenceObject $baselinePerms -DifferenceObject $newACL -Property "IdentityReference","AccessControlType","ActiveDirectoryRights" | fl
Note that in the first statement, we are retrieving the baseline permissions from the CSV file and storing them in a variable called $baselinePerms. In the second statement, we call Compare-Object and pass both $baselinePerms and $newACL to the cmdlet. We also use the –Property parameter to specify the property names we wish to compare, between these two objects. Finally, we pipe the output to the format-list (alias fl) cmdlet for readability. The output looks like this:
Figure 2: Viewing Differences in an OU's ACL
What this shows is that the group called “CPANDL\Office 2003 Users” has been granted the additional rights of “CreateChild, Self and WriteProperty” on the OU.
Detecting Group Membership Drift
The same method we used above can be used to check any changes or drift in critical AD group memberships. The Get-ADGroupMember AD cmdlet can return the list of members in a given group (either direct members, or recursive members using the –Recursive parameter). Once you have this list, you can again store it to a “baseline” file on the disk, using the Export-CSV cmdlet and then use the Compare-Object cmdlet in a script on a scheduled basis to check the group’s current membership. To add some additional capabilities to our scenario, let’s send an email message whenever we detect a group membership change. The following script code accomplishes this:
- $currentGroup = Get-ADGroupMember "Marketing Users"
- $baselineGroup = import-csv -Path C:\data\groupbaseline.csv
- $diff = Compare-Object -ReferenceObject $baselinegroup -DifferenceObject $currentGroup -Property "DistinguishedName"
- if ($diff -ne $null)
- foreach ($item in $diff)
- $user = $item.DistinguishedName
- if ($item.SideIndicator -eq "=>")
- $mailbody = "$user was added to the Marketing Users group"
$mailbody = "$user was removed from the Marketing Users group"
- send-mailmessage -from "firstname.lastname@example.org" -to "email@example.com" -subject "Critical Group Change Detected" -body $mailbody -smtpServer "mailhost.cpandl.com" -port 587 -UseSSL -credential $cred
This script starts by assuming that I’ve already used Export-CSV to export the Marketing Users group’s membership to a CSV file called c:\data\groupbaseline.csv. In Line 1, we start by getting the current group membership and assigning it to $currentGroup. Line 2 imports the baseline into a variable called $baselineGroup. Line 3 uses the Compare-Object cmdlet to compare the DistinguishedName property on any differences found between the two lists. In line 4, if the $diff object that we assigned the differences to in Line 3 is not null (meaning we found some differences) then we use a foreach loop in line 6 to iterate through the differences. For each difference, we retrieve the DN of the object (Line 8). Then in line 9, we look at the “SideIndicator” property on the difference object to determine if the user/group was added or removed. Based on that, we form the body of the mail message indicating that a member was added or removed, in Lines 9-15. Finally, in Line 16, we use the Send-MailMessage cmdlet to send the message. Note that I’ll need to provide a valid SMTP server host name, and port if applicable. In addition, the –credential parameter is what allows you to pass credentials to the SMTP Server, if your server requires authentication prior to being able to send mail. Note that in the script above, I’m passing a variable to that parameter, but the variable isn’t defined in the script. However, you can pre-populate that variable using the following syntax:
$cred = get-credential
Once this script runs, an email message is sent every time a change is detected in group membership (assuming the change occurs before the script runs!)
Using Powershell to control, audit, and report on when objects in AD drift is very useful and powerful. The ability to schedule these Powershell commands to allow you to know when things are not as they should be is something that is easy and convenient. There will be more from me on Powershell and how to leverage it to control Active Directory.