Tuesday, February 15, 2011

Search for Large Distribution Lists without Sending Restrictions

If you've worked in a large organization, you might have encountered the misdirected e-mail which finds itself being delivered to an unsecured distribution list. A well meaning employee uses the Global Address List, fat fingers the selection of the recipient and sends an e-mail not suited for a large audience. Even worse, a malicious departing employee takes advantage of the lack of security to say their not-so-fond farewell. This results storm of employees hitting reply to all (why can't Outlook have an 'Are you sure?' prompt for this button?) and writing "take me off this distribution list", "me too" and my personal favorite, "quit hitting reply to all" (usually in all caps, the font bolded and with multiple exclamation points). There are many reasons that this sort of event happens. No defined policy for the creation of distribution lists and mail-enabled security groups and the sending restrictions when the member list exceeds a certain size, human error in creation, organic growth of a small list over the years or the nuisance of nesting of groups (I'll tackle this problem later).

In the code sample below, I have a procedure to search the entire forest of the security context executing the script and return the group objects that are mail-enabled and do not have the authOrig and dLMemSubmitPerms attributes set. These two attributes are used for restricting who can send to a distribution list/mail-enabled security group within a forest. The authOrig attribute is for the storage of user object distinguished names and dLMemSubmitPerms is for the distinguished names of group objects. The code only looks at the first level of the members of the distribution list/mail-enabled security group. It does not take account the membership of a nested group. As I mentioned earlier, I will tackle that in a later post. The script will generate a report of the groups that have 25 members or more and are open to anyone to send. You can increase or decrease that value by modifying the threshold constant variable. I have also included and resolved the managedBy attribute to assist in identifying an employee who may have an understanding who should have the right to send.

Another attribute of interest and future blog post is msExchRequiredAuthToSendTo. This attribute controls the ability of external senders to deliver messages to distribution lists. If you have a good reason to have large, unsecured mail-enabled groups, you might want to restrict them from Internet based mailers -- especially spammers. This attribute is one method to protect those lists.
Function Get-LocalDomainController($objectDomain) {
 return ([System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite()).Servers | Where-Object { $_.Domain.Name -eq $objectDomain } | ForEach-Object { $_.Name } | Select-Object -first 1
Function Get-ObjectADDomain($distinguishedName) {
 return ((($distinguishedName -replace "(.*?)DC=(.*)",'$2') -replace "DC=","") -replace ",",".")
Function Get-ActiveDirectoryObject($distinguishedName) {
 return [ADSI]("LDAP://" + (Get-LocalDomainController (Get-ObjectADDomain $distinguishedName)) + "/" + ($distinguishedName -replace "/","\/"))
Set-Variable -name forestRootDn -option Constant -value ([ADSI]("LDAP://" + (([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()).name) + "/rootDSE")).defaultNamingContext
Set-Variable -name threshold -option Constant -value 25
$groupsInNonCompliance = @()
$objectConnection = New-Object -comObject "ADODB.Connection"
$objectCommand = New-Object -comObject "ADODB.Command"
$objectCommand.ActiveConnection = $objectConnection

$ldapBase = "GC://$forestRootDn"
$ldapAttr = "distinguishedName"
$ldapScope = "subtree"

$ldapFilter = "(&(objectClass=group)(objectCategory=group)(!dLMemSubmitPerms=*)(!authOrig=*)(mail=*))"
$ldapQuery= "<$ldapBase>;$ldapFilter;$ldapAttr;$ldapScope"
$objectCommand.CommandText = $ldapQuery

$objectRecordSet = $objectCommand.Execute()
if(!$objectRecordSet.EOF) {
 while(!$objectRecordSet.EOF) {
  $groupObject = Get-ActiveDirectoryObject $objectRecordSet.Fields.Item('distinguishedName').Value
  if($groupObject.member.Count -ge $threshold) {
   $groupInformation = New-Object -typeName PSObject
   Add-Member -inputObject $groupInformation -type NoteProperty -name "domain" -value ((Get-ObjectADDomain $groupObject.distinguishedName).Split(".")[0]).ToUpper()
   Add-Member -inputObject $groupInformation -type NoteProperty -name "sAMAccountName" -value ($groupObject.sAMAccountName).ToString()
   Add-Member -inputObject $groupInformation -type NoteProperty -name "displayName" -value ($groupObject.displayName).ToString()
   Add-Member -inputObject $groupInformation -type NoteProperty -name "eMail" -value ($groupObject.mail).ToString()
   if($groupObject.managedBy) {
    $managedByObject = Get-ActiveDirectoryObject $groupObject.managedBy
    Add-Member -inputObject $groupInformation -type NoteProperty -name "managedBy" -value (((Get-ObjectADDomain $managedByObject.distinguishedName).Split(".")[0]).ToUpper() + "\" + ($managedByObject.sAMAccountName).ToString())
   } else {
    Add-Member -inputObject $groupInformation -type NoteProperty -name "managedBy" -value "N/A"
   Add-Member -inputObject $groupInformation -type NoteProperty -name "memberCount" -value ($groupObject.member.count).ToString()
   $groupsInNonCompliance += $groupInformation

$groupsInNonCompliance | Export-Csv -path "Groups In Non-Compliance.csv" -noTypeInformation

No comments:

Post a Comment