Monitoring Exchange 2010 for Spammers

When using Exchange as your outside facing transport servers in either a dedicated Edge role under 2010 or within a multi-role setup finding out when you have a spammer from within historically has been done via blacklist notifications. What if we can catch the spammers in the act? What if we can stop the spam midstream? As a side benefit, you’ll get notification if mail is backing up for other reasons as well…ie random email providers being offline or if you end up having routing issues.

$servername = Get-Content env:computername
$mail_sender = "$"
$mail_server = ""
$mail_recipient = ""
$mailreport_subject = "Script: $servername Message Queues"
#At what level do you want to be emailed?
$maxinqueue = 40
$body = ""

Add-pssnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction SilentlyContinue

function SendEmailReport

    $msg = New-Object System.Net.Mail.MailMessage $mail_sender, $mail_recipient, $mailreport_subject, $body
    $client = New-Object System.Net.Mail.SmtpClient $mail_server
    $client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials

$i = 0
while($i -lt 29)
	$mymessages = get-message -resultsize unlimited	
	#$mysenders  = $mymessages | select-object fromaddress
	if($mymessages.count -gt $maxinqueue)
		$body = "Warning the current queue on $servername has exceeded the queue count of $maxinqueue and is currently at " + $mymessages.count
		$body += "`r`n"
		$body += $mymessages | out-string

		$body = ""
	$mymessages = $null
	write-host $i
	Sleep 60

The $maxinqueue variable is the real trick, at what level of messages in the queue is normal for your organization?

Then all that needs to be done is configuration of a simple scheduled task say run every 30 minutes, the scripting logic is configured to run in a loop to cover at the per minute within a 30 minute window.

, ,
July 11, 2013 at 9:04 am Comments (0)

Large exchange distribution automation

Exchange distribution groups are a very useful method for delivering email to a large number of clients, however every design has it’s limits. I needed to use a distribution list for a rotating number of users with a total count of close to 20,000 members. When looking at distribution groups with more than a few thousand entries causes scalability limits. Naturally I’d rather not have to manually load lists every night.

if(Get-Module -Name ActiveDirectory){}
else{Import-Module ActiveDirectory}

Write-Host "Loading employees"
$myusers = Get-ADUser -filter "*" -SearchBase "OU=Employees,DC=liquidobject,DC=com" -properties description | Select-Object samaccountname
Write-Host "Successfully loaded" $myusers.count "employee accounts."

$alpha = "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"
foreach($i in $alpha)
    $mygroup = "EmployeeSub_$i"
    $myoldgroupmembers = Get-AdGroupMember -identity $mygroup | Select-Object SamAccountName
    Write-Host "Group:" $mygroup "has" $myoldgroupmembers.count "members"
    $mycurrentusers = $myusers | where {$_.samaccountname -like "$i*"}
    Write-Host "We currently have" $mycurrentusers.count "which should be in this group"
    $mydiff =  Compare-Object -ReferenceObject $myoldgroupmembers -DifferenceObject $mycurrentusers -property samaccountname
    foreach($i in $mydiff)
        if($i.SideIndicator -eq "=>"){Add-AdGroupMember -identity $mygroup -members $i.samaccountname}
        else {Remove-AdGroupMember -identity $mygroup -members $i.samaccountname -confirm:$False}

The above provides a differential solution by splitting the single very large group into a series of 26 smaller, more manageable groups. Then we can wrap the 26 groups with a query-based distribution group for simplified delivery to clients using

new-DynamicDistributionGroup All_Employees -OrganizationalUnit "OU=My OU,DC=liquidobject,DC=com" -RecipientFilter {RecipientContainer -eq "OU=EmployeeGroups,My OU,DC=liquidobject,DC=com"}
, , ,
March 21, 2013 at 7:58 pm Comments (0)

Exchange 2010 Holiday Calendar Loading

Within an organization I had need to bulk load behind the scenes a number of calendar items on all employee’s accounts. This script had multiple sources with the original idea coming from

To get this working requires a few things.
1) Exchange 2010 with SP1 (currently running under SP2)
2) The Exchange EMS shell installed
3) The Exchange Web Services API to be installed (
4) Elevated permissions within the Exchange environment.

Beyond your normal administration rights is the requirement for the ability to Impersonate all users within the organization. Because of the level of access required to make these changes please verify with the organization that there are no legal issues with attempting this.

#Path to your holiday file
$inputfile = "D:\Data\HolidayCalendar2012-2013.csv"
#Path to the EWS DLL file
$Webservices = "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
#Where are the users
$baseOU = "OU=My Employees,DC=liquidobject,DC=com"

#Need to update each year
$myyears = "2012-2013"
$myuniquebody = "My Unique Calendar Automation Message" + $myyears
#The above body entry was the easiest way for users to see that the items were loaded by the IT department and also provides the one place where we can go back and modify/delete calendar entries later.

#If we need to run the once per year everyone run, set this to true
$YearlyRun = $false

#Testing mode options
$testmode = $false
$testuser = "Testuser99"

####################### End Options ########################

if(!(Get-Module | where {$_.Name -eq "ActiveDirectory"})){Import-Module ActiveDirectory}
if(!(Get-PSSnapin -Name "Microsoft.Exchange.Management.PowerShell.E2010" -ErrorAction SilentlyContinue)){Add-pssnapin Microsoft.Exchange.Management.PowerShell.E2010}
if(!(Get-PSSnapin -Name "" -ErrorAction SilentlyContinue)){Add-pssnapin}

if(!(Test-Path $Webservices))
    Write-Host "`n`nExchange Web Services API is required and is not installed`n`n"
if(!(Test-Path $inputfile))
    Write-Host "`n`nInput file of: $inputfile is missing, cannot proceed without this file.`n`n"
Write-Host "`nEWS Web Services - Loading"
Add-Type -Path $Webservices
Write-Host "EWS Web Services API - Loaded`n"

function New-CalendarItem {
        [Parameter(Position=1, Mandatory=$true)]$CalendarUser,
        [Parameter(Position=2, Mandatory=$true)]$Subject,
        [Parameter(Position=3, Mandatory=$true)]$Date
    $sid = (Get-ADUser -Identity $CalendarUser).SID
    $user = [ADSI]"LDAP://<SID=$sid>"
    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList ([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
    $Impersonate = (Get-Mailbox -Identity $calendaruser).PrimarySMTPAddress
    $ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList ([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress),$Impersonate
    $service.ImpersonatedUserId = $ImpersonatedUserId
    $appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $service
    $appointment.Subject = $Subject
    $startdate = Get-Date($Date)
    $appointment.Start = $startdate
    $enddate = (Get-Date($Date)).adddays(1)
    $appointment.End = $enddate
    $appointment.body = $myuniquebody 
    $appointment.LegacyFreeBusyStatus = "Free"
    $appointment.IsAllDayEvent = $true
    $appointment.IsReminderSet = $False
#    Write-Host "New Entry: " $CalendarUser " - " $Subject " - " $Date
    $appointment = $null
    sleep 0.3

$mydata = Import-Csv $inputfile
Write-Host "Preparing to process" $mydata.count "calendar entries`n"

#Everyone, we need this once per year
    $myusers = Get-ADuser -Filter {(mail -like "*") -and (ObjectClass -eq "user") -and (Enabled -eq $true)} -SearchBase $baseOU -Properties created | Select-Object samaccountname, created
    #Normally grab just the last 7 days of users for a weekly run.
    $mydays = (Get-Date).Adddays(-7)
    $myusers = Get-ADuser -Filter {(mail -like "*") -and (ObjectClass -eq "user") -and (Created -gt $mydays) -and (Enabled -eq $true)} -SearchBase $baseOU -Properties created | Select-Object samaccountname, created

#sorting for easy troubleshooting
$myusers = $myusers | Sort-Object samaccountname
#For testing lock to a single user
    $myusers2 = @{"samAccountName"=$testuser}

$today = Get-Date
$mycount = $myusers.count
$currentpos = 1
foreach($i in $myusers)
    Write-Host "Loading $currentpos of $mycount for:" $i.samaccountname
    foreach($entry in $mydata)
        if($today -lt $
            #Write-Host $ " - " $entry.Subject
            New-CalendarItem -CalendarUser $i.samaccountname -Subject $entry.Subject -date $
            sleep 0.2
            if($YearlyRun) #only load past dates on the yearly run
                New-CalendarItem -CalendarUser $i.samaccountname -Subject $entry.Subject -date $
                sleep 0.2
        sleep 0.1
    sleep 1.5

All items created are all-day events with availability as as free with no reminders to annoy the staff. This is designed for an annual run and then adjustment of the “$YearlyRun” variable for weekly loads on all new employees.

The CSV file is in the following form:
New Years Day, 2013/1/1
April Fools, 2013/4/1

Be careful on the characters used in the Subject entries as extra ” or ‘ characters can cause lots of headaches.

, ,
December 27, 2012 at 4:14 pm Comments (0)

Current Exchange CAS user counts

Below you’ll find a slight modification to a post from Mike Pfeiffer on querying Exchange 2010 CAS servers to obtain the current count of OWA and RPC clients.

function Get-CASActiveUsers {
      [Parameter(Position=0, ValueFromPipelineByPropertyName=$true, Mandatory=$true)]

  process {
    $Name | %{
      $RPC = Get-Counter "\MSExchange RpcClientAccess\User Count" -ComputerName $_
      $OWA = Get-Counter "\MSExchange OWA\Current Unique Users" -ComputerName $_
      New-Object PSObject -Property @{
        Server = $_
        "RPC Client Access" = $RPC.CounterSamples[0].CookedValue
        "Outlook Web App" = $OWA.CounterSamples[0].CookedValue

Get-CASActiveUsers CAS-1,CAS-2,CAS-3
, , ,
December 10, 2012 at 3:49 pm Comments (0)

Hiding Distribution Group memberships

Under Exchange 2010 the need arose to create some distribution groups which the membership was hidden. The most published method is to actually use two groups. The first is a non-mail enabled group and the second is a dynamic distribution group. While these do have there place in many organizations I wanted to maintain the same functionality without needing the second group.

To keep my life simple here is the PowerShell method to this problem.

Lets define some variables

$listname = "allemployees"
$orgunit = " Groups"
$managedby = "MyDummyAdmin"
#Build the Group
New-DistributionGroup -Name $listname -SamAccountName $listname -OrganizationalUnit $orgunit -Type "Distribution" -ManagedBy $managedby -MaxReceiveSize "5120 KB"
sleep 4
#Restrict commandline to view membership via net group command
Add-ADPermission -Identity $listname -User "Normal_Employee_Group" -Deny -AccessRights ReadProperty -Properties Member
#Restrict Outlook & OWA Access to view membership
set-ADGroup -identity $listname -Replace @{HideDLMembership=$true}

This method has been used in the past via ADSI Edit but I was looking for a native PowerShell approach for a process to automate group creation.

The only drawback is the HideDLMembership is organization wide, so regular managers cannot see the group memberships. Administrators can still see membership via the EMS, EMC, or via the old net group method.

, , , ,
August 12, 2012 at 9:00 am Comments (0)

Messagetracking log compilation

If you ever needed to quickly all external messages sent to your organization but have the problem of multiple transport servers, the below script should help give some insight into the messages recently received.

$myminutes = 1
$myservers = "cashub-1","cashub-2","cashub-3"
$mydomain = "testdomain.local"

$mystart = (Get-Date).addminutes(-$myminutes)
foreach($myserver in $myservers)
		Write-host "------------------- start " $myserver "-------------------"
		Get-MessageTrackingLog -Start $mystart -server $myserver | where {($_.Sender -notlike "*$mydomain") -and ($_.Source -eq "SMTP")}
		Write-host "-------------------   end " $myserver "-------------------`n"
, ,
July 30, 2012 at 3:54 am Comments (0)

« Older Posts