Exchange Retention Project Review

So this project started out as many projects at work do. The boss comes to you and says, “hey I have this new email use policy written up, now we need to implement the technology to meet it”. I’ll leave out many of the company specific details, but let’s say that new policy has the following requirements for example:

  • There are two different groups of users within the Exchange environment which require unique storage limitations. For example, “standard users” should receive a storage warning at 250Mb and be prevented from sending email when their mailbox reaches 500Mb. Whereas “management users” should receive a warning at 750Mb and be prevented from sending email when their mailbox reaches 1Gb.
  • When the IT department is informed of a litigation hold, mailboxes should be excluded from this retention policy regardless of being a member of the standard user or management user groups. This is the default behavior of enabling litigation hold, but it would be nice if helpdesk could make a group membership change and this happen in the background.
  • These retention policies should follow the user regardless of the mail database to which the user mailbox is stored in.
  • During implementation of these mail retention policies, it should be considered that many mailboxes will be over these limits. There should be some leniency in place so that business is not halted during rollout.
  • All mail should only be retained for 1 yr. Deleted items should only be retained for 14 days.
  • Finally, any user that is not subject to email retention policies should have the policy automatically enabled.

So that is a lot. As an IT professional that does a bit of everything, initially I wasn’t sure how to accomplish all of these goals. I mean, it could be manually done on a per mailbox bases, but that doesn’t scale. It’s also very difficult to ensure without constantly auditing that these settings are being applied to all mailboxes consistently. So I opted to script this in PowerShell. I set out to create a daily task that would run, collect users from different groups, then apply storage settings to meet the written policy.

I’ll preface the rest of this post by saying that this code was written for Exchange 2010. I imagine that much of the code will also work for later versions, but I am sure modifications will need to be made. I spent the better part of a day writing it, so I figured I would post..

I currently have this script running on a “support” server with Windows 2012 R2, Exchange 2010 management tools, and PowerShell Active Directory module installed. I am using PowerShell version “4”. I won’t get into the details of how this task is scheduled, basically just using the Windows task scheduler which calls PowerShell.exe with the “-command” switch and a path out to the primary script below.

Prerequisit list:
1. Exchange Management Tools
2. Active Directory PowerShell Module
3. Active Directory Security Groups to facilitate policy filtering

Exchange-Standard Users: Group used to apply standard send limits and warnings.
Exchange-Management Users: Group used to apply management send limits and warnings.
Exchange-EnableLitigationHold: Group used to apply litigation hold to required users.

Step 1: Create the retention policies. Run the following PowerShell commands from your Exchange Management Shell:

#Create retention tags
New-RetentionPolicyTag -Name 'Mail Policy - All Mail Baseline' -RetentionAction DeleteAndAllowRecovery -AgeLimitForRetention 365.00:00:00 -Type ALL -Comment 'Email older than 12 months will be automatically purged from the system.' -RetentionEnabled $True
New-RetentionPolicyTag -Name 'Mail Policy - DeletedItems' -RetentionAction DeleteAndAllowRecovery -AgeLimitForRetention 14.00:00:00 -Type DeletedItems -Comment 'Deleted items will be retained for 14 days.' -RetentionEnabled $True
New-RetentionPolicyTag -Name 'Mail Policy - SyncIssues' -RetentionAction DeleteAndAllowRecovery -AgeLimitForRetention 14.00:00:00 -Type SyncIssues -Comment 'SyncIssues items will be retained for 14 days.' -RetentionEnabled $True
New-RetentionPolicyTag -Name 'Mail Policy - JunkEmail' -RetentionAction DeleteAndAllowRecovery -AgeLimitForRetention 14.00:00:00 -Type JunkEmail -Comment 'JunkEmail items will be retained for 14 days.' -RetentionEnabled $True
New-RetentionPolicyTag -Name 'Mail Policy - RSS' -RetentionAction DeleteAndAllowRecovery -AgeLimitForRetention 14.00:00:00 -Type RssSubscriptions -Comment 'RSS items will be retained for 14 days.' -RetentionEnabled $True
#Disable retention policy from applying to Contacts or Notes.
New-RetentionPolicyTag -Name 'Mail Policy - Contacts' -Type 'Contacts' -Comment 'Contacts cleared from mailboxes never.' -AgeLimitForRetention $null -RetentionAction 'DeleteAndAllowRecovery' -RetentionEnabled $false
New-RetentionPolicyTag -Name 'Mail Policy - Notes' -Type 'Notes' -Comment 'Notes cleared from mailboxes never.' -AgeLimitForRetention $null -RetentionAction 'DeleteAndAllowRecovery' -RetentionEnabled $false

#Add tags to mail policy
New-RetentionPolicy -Name 'Standard Retention Policy' -RetentionPolicyTagLinks 'Mail Policy - All Mail Baseline','Mail Policy - DeletedItems','Mail Policy - SyncIssues','Mail Policy - JunkEmail','Mail Policy - Contacts','Mail Policy - Notes','Mail Policy - RSS'

Step 2: Create master script which launches subsequent tasks. This is the script that should be scheduled to run at your desired frequency.

<#
Primary script designed to run:
QuotaAndLimits.ps1 - Applies standard retention policy to all users and sets base warning and send limits.
OverLimitProcessing.ps1 - Adjusts sending limit for those mailboxes still out of compliance.
#>

#Enable AD shell commands
Import-Module ActiveDirectory

#Import Exchange Commands
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://ExchHostname.domain.local/PowerShell/ -Authentication Kerberos
Import-PSSession $Session

Set-Location $PSScriptRoot

#start PageLoad.ps1
invoke-expression .\QuotaAndLimits.ps1

#start ICMPTest.ps1
invoke-expression .\OverLimitProcessing.ps1

Exit

Step 3: QuotaAndLimits.ps1

This script will search a particular OU for mailboxes and enable the standard retention policy for any found. It then applies limits based on group membership. If a mailbox is found for a user who is not a member of either Standard or Manager storage class, the script adds the user to standard and applies the related limits.

Additionally this script searches for litigation hold group membership and enables lit hold. Or disables if the user has been removed from the group.


#Set retention policy on applicable mailboxes.  This command was filtered to exclude certain OUs, you will need to adjust to meet your needs.
get-mailbox | where {$_.OrganizationalUnit -notmatch "domain.local/Users/Service Accounts" -and $_.Name -notmatch "DiscoverySearchMailbox" -and $_.Name -notmatch "SearchResults" } | Set-Mailbox -RetentionPolicy 'Standard Retention Policy'

#Set user variable used in foreach loop below.  Should capture all mailboxes actively set to current retention policy.
$RetentionEnabledUsers = get-mailbox | where { $_.RetentionPolicy -match "Standard Retention Policy" }

	#Capture AD security group members and write to variable.
	$ExchManager = Get-ADGroupMember -Identity "Exchange-Management Users" -Recursive | Select -ExpandProperty Name
	$ExchGeneral = Get-ADGroupMember -Identity "Exchange-Standard Users" -Recursive | Select -ExpandProperty Name
	$ExchLitHold = Get-ADGroupMember -Identity "Exchange-EnableLitigationHold" -Recursive | Select -ExpandProperty Name

#Being a loop on each mailbox found with current retention policy.
Foreach($R in $RetentionEnabledUsers) {

	#Sanitize name for console output.  Write to variable.
	$NameForLog = $R.name

		#If user found in manager security group, set appropriate warning and set limits for this class of user.
		#Also set new variable "$ExchMangaerMember" for later use.
		If($ExchManager -match $R.name) {
			Write-Host "$NameForLog exists in the Exchange-Management Users group"
			$ExchManagerMember = "1"
			set-mailbox -identity $R.SamAccountName -IssueWarningQuota 750MB -ProhibitSendQuota 1024MB -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true

		}Else{
			Write-Host "$NameForLog not exists in the Exchange-Management Users group"
			$ExchManagerMember = "0"
	
		}
		
		#If user found in standard user security group, set appropriate warning and set limits for this class of user.
		#Also set new variable "$ExchGeneralMember" for later use.
		If($ExchGeneral -match $R.name) {
			Write-Host "$NameForLog exists in the Exchange-Standard Users group"
			$ExchGeneralMember = "1"
			set-mailbox -identity $R.SamAccountName -IssueWarningQuota 250MB -ProhibitSendQuota 500MB -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true
	
		}Else{
			Write-Host "$NameForLog not exists in the Exchange-Standard Users group"
			$ExchGeneralMember = "0"
		
		}
	
	#If retention enabled user is not a member of either group, add to general.
	If($ExchManagerMember -eq "0" -and $ExchGeneralMember -eq "0") {
		Add-ADGroupMember -identity 'Exchange-Standard Users' -Members $R.SamAccountName -PassThru
		$ExchGeneralMember = "1"
	}
	
	#If user is a member of "Exchange-EnableLitigationHold", set variable.
	If($ExchLitHold -match $R.name) {
		$ExchLitHoldMember = "1"
	
	}Else{
		$ExchLitHoldMember = "0"
	
	}

#Last task to set manager limits for users who may have been missed.  Those users who were not members of either manager or standard user groups.
if($ExchGeneralMember -eq "1"){
	set-mailbox -identity $R.SamAccountName -IssueWarningQuota 250MB -ProhibitSendQuota 500MB -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true

}
#Last task to set manager limits for users who may have been missed.  Those users who were not members of either manager or standard user groups.
if($ExchManagerMember -eq "1"){
	set-mailbox -identity $R.SamAccountName -IssueWarningQuota 750MB -ProhibitSendQuota 1024MB -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true

}

#Enable litigation hold for those users found in the appropriate litigation hold security group.
if($ExchLitHoldMember -eq "1"){
	set-mailbox -identity $R.SamAccountName -LitigationHoldEnabled $true

}Else{
	set-mailbox -identity $R.SamAccountName -LitigationHoldEnabled $false
}
	
}
EXIT

Step 4: OverLimitProcessing.ps1

Because it is a requirement to being implementing this new written policy without halting production, it was necessary to build an additional layer. Enter OverLimitProcessing.ps1. The purpose of this script is to identify any user mailbox that currently is overlimit or out of compliance, measure the mailbox size, and add 30Mb to the send limit. The hope being that the end user will begin seeing the warning and clearing their mailbox out. As the script runs daily it will continue to lower the limit until the mailbox is within compliance.

This one was challenging to write, but works well in our environment. Originally I rolled it out with only 10 Mb as the daily send limit, but our helpdesk was getting quite a few calls with end users chewing through this limit right away.

#Collect AD group members for standard and manager user classes.
$ExchManager2 = Get-ADGroupMember -Identity "Exchange Retention - Director and Executive"
$ExchGeneral2 = Get-ADGroupMember -Identity "Exchange Retention - General User"

Foreach($ExchMan in $ExchManager2) {
	#Null variable to keep work clean.
	$LimitInfoMan = $null
	$ExchManRetentionCheck = $null
	
#check to see if retention policy is even applied to mailbox, if not skip processing....
$ExchManRetentionCheck = get-mailbox $ExchMan.SamAccountName
if($ExchManRetentionCheck.RetentionPolicy -eq $null){
#Retention policy not detected.

}Else{
#Retention policy detected.  Continue processing over limit changes.

	
	#Default SizeInGB value - for KB or otherwise.
	$SizeInGB = "2"

	#get size information for users in group.
	$LimitInfoMan = Get-mailboxstatistics $ExchMan.SamAccountName

	#Capture initial value of size.  Exclude byte and MB/GB.
	$LimitInfoManValueOnly = $LimitInfoMan.TotalItemSize | Select-String -pattern "\d*\.?\d*" |%{$_.Matches}|%{$_.Value}
	#change value from string to "Double".  Int didn't allow for decimal place.
	$LimitInfoManValueOnlyDouble = [Double]$LimitInfoManValueOnly
	#Sanitize DisplayName for log output and echo
	$DisplayName = $LimitInfoMan.DisplayName

		#Detect if value is in GB or MB
		if ($LimitInfoMan.TotalItemSize -match "MB"){ $SizeInGB = "0" }
		if ($LimitInfoMan.TotalItemSize -match "GB"){ $SizeInGB = "1" }
	
		#Take action on any MBX detected to be over limit.
		if ($SizeInGB -eq "1"){
			if($LimitInfoManValueOnlyDouble -eq "1" -or $LimitInfoManValueOnlyDouble -gt "1"){
		
			#Process exception math
			$SendLimitException = $LimitInfoManValueOnlyDouble*1000+30
		
			#set exception limits
			set-mailbox -identity $ExchMan.SamAccountName -IssueWarningQuota 750MB -ProhibitSendQuota $SendLimitException"MB" -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true
		
		}
	}

	if ($SizeInGB -eq "0"){
		if($LimitInfoManValueOnlyDouble -gt "999"){
		#Process exception math
		$SendLimitException = $LimitInfoManValueOnlyDouble+30
		
		#set exception limits
		set-mailbox -identity $ExchMan.SamAccountName -IssueWarningQuota 750MB -ProhibitSendQuota $SendLimitException"MB" -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true
		
		}

	}

}#Endif Retention Check
	
}#End Foreach


Foreach($ExchGen in $ExchGeneral2) {
#Null variable to keep work clean.
$LimitInfoGen = $null

#Default SizeInGB value - for KB or otherwise.
$SizeInGB = "2"

#get size information for users in group.
$LimitInfoGen = Get-mailboxstatistics $ExchGen.SamAccountName

#Capture initial value of size.  Exclude byte and MB/GB.
$LimitInfoGenValueOnly = $LimitInfoGen.TotalItemSize | Select-String -pattern "\d*\.?\d*" |%{$_.Matches}|%{$_.Value}
#change value from string to "Double".  Int didn't allow for decimal place.
$LimitInfoGenValueOnlyDouble = [Double]$LimitInfoGenValueOnly
#Sanitize DisplayName for log output and echo
$DisplayName = $LimitInfoGen.DisplayName

	#Detect if value is in GB or MB
	if ($LimitInfoGen.TotalItemSize -match "MB"){ $SizeInGB = "0" }
	if ($LimitInfoGen.TotalItemSize -match "GB"){ $SizeInGB = "1" }

	#Take action on any MBX detected to be over limit.
	if ($SizeInGB -eq "1"){
		if($LimitInfoGenValueOnlyDouble -eq "1" -or $LimitInfoGenValueOnlyDouble -gt "1"){
		
		#Process exception math
		$SendLimitException = $LimitInfoGenValueOnlyDouble*1000+30
		
		#set exception limits
		set-mailbox -identity $ExchGen.SamAccountName -IssueWarningQuota 250MB -ProhibitSendQuota $SendLimitException"MB" -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true
		
		}
	}

	if ($SizeInGB -eq "0"){
		if($LimitInfoGenValueOnlyDouble -gt "500"){
		#Process exception math
		$SendLimitException = $LimitInfoGenValueOnlyDouble+30
		
		#set exception limits
		set-mailbox -identity $ExchGen.SamAccountName -IssueWarningQuota 250MB -ProhibitSendQuota $SendLimitException"MB" -ProhibitSendReceiveQuota unlimited -UseDatabaseQuotaDefaults $false -UseDatabaseRetentionDefaults $true
		
		}

	}

}

EXIT

Leave a Reply