Monday, January 26, 2009

From Address Rewriting in a Transport Agent for a Exchange 2007 Hub Server

The ability to rewrite the from address of emails can be exceedingly useful if you are working with consolidating or migrating email systems especially if you have two (or more) companies merging or you're trying to consolidate disparate email system across orgs. Exchange 2007 has a great address rewriting agent that runs on a Edge server which is explained in If you aren't implementing an Edge server as part of your Exchange org then unfortunately you cant use this agent on a Hub server. One solution to this is that you write your own agent to do this which is what this post is about. Now that link I just pointed to has some very important information about the fields you should and shouldn't rewrite in an address rewriting Agent and the reason you shouldn't rewrite particular fields. The Return-Path is probably the one of most note and maybe the one people will tend to want to rewrite because of the visibility of the original address in the header and possible SPAM detection issues where the return-path will be different to the From address etc. But it is probably best to leave it as someone has probably done a lot more testing then you at this and it best not to rewrite it the longer story I'm sure is a lot more complicated.

Envelope From (MAIL FROM)

This field can be known more commonly as one of the P1 headers and is available in a Transport event as the mailitem.FromAddress property. To rewrite this address you need to use the RoutingAddress type e.g

e.MailItem.FromAddress = new RoutingAddress("localPart", "newFromDomain");

Body From and Sender

These fields are part of the P2 headers and are exposed via the EmailMessageClass accessable via the MailItem.Message property. Eg to rewrite these properties

e.MailItem.Message.From.SmtpAddress = localPart + "@" + newFromDomain;
e.MailItem.Message.Sender.SmtpAddress = localPart + "@" + newFromDomain;

Body Reply-To

Because there can be more then one address within the Reply-To property you need some code that will loop through the recipients collection and re-map address's as nessasary. e.g

foreach (EmailRecipient rpReplyTo in e.MailItem.Message.ReplyTo) {
if (rpReplyTo.SmtpAddress != null)
String rtSourceAddress = rpReplyTo.SmtpAddress.ToString();
String[] rtSourceArray = rtSourceAddress.Split('@');
if (rtSourceArray[1].ToString().ToLower() == oldFromDomain) {
rpReplyTo.SmtpAddress = rtSourceArray[0].ToString() + "@" + newFromDomain;

Disposition-Notification-To and Return-Receipt-To

These two properties represent Read Recipient address's the reason there are two is a little complicated and has a little to do with history but the one that relates to the RFC and is the standard property is Disposition-Notification-To which is what is exposed by the EmailMessageClass and can be rewritten with something like.

EmailRecipient dnDispNotice = e.MailItem.Message.DispositionNotificationTo;
String dnSourceAddress = dnDispNotice.SmtpAddress.ToString();
String[] dnSourceArray = dnSourceAddress.Split('@');
if (dnSourceArray[1].ToString().ToLower() == oldFromDomain)
dnDispNotice.SmtpAddress = dnSourceArray[0].ToString() + "@" + newFromDomain;

The second header Return-Receipt-To which is non standard is a little trickier this isn't exposed as a property by the EmailMessageClass but can be accessed as an AddressHeader when looping through the MIME headers. Rewriting this field is difficult in a Transport agent and the only method I've found that works is to use the MIME writer. To simplify my transport agent i took what might prove to be the wrong approach of just deleting this header. I took the view point that its a redundant header anyway and any compliant client should support the standard DispositionNotificationTo header. To delete the header you can use code like.

HeaderList hlHeaderlist = e.MailItem.Message.RootPart.Headers;
AddressHeader mhRrecp = (AddressHeader)hlHeaderlist.FindFirst("Return-Receipt-To");

If you are looking for some code to read an AddressHeader Email value which you wont be able to do by just looping through the headerlist like normal Text headers you need to use some code that looks like.

Stream messageStream = e.MailItem.GetMimeReadStream();
MimeReader mrMimeReader = new MimeReader(e.MailItem.GetMimeReadStream());
while (mrMimeReader.HeaderReader.ReadNextHeader()) {
if (mrMimeReader.HeaderReader.Name.ToString() == "Return-Receipt-To") {
MimeAddressReader reRecp = mrMimeReader.HeaderReader.AddressReader;


Im nore sure if this is the best method but the documentation for the MIMEReader class is very limited.

Thats it while its not 100% hopefully it will help if your trying to write your own address rewriting Transport Agent.

Saturday, January 24, 2009

Turning the Reading / Preview pane on and off in OWA in Exchange 2007 with EWS and Powershell

The Reading pane (or preview pane) is one the features of the premium version of Outlook Web Access on 2007. Due to Security or bandwidth (or other) worries you may want to turn this off on particular folders for your users (although they will still be able to still turn this back on using OWA). There is no global way of turnin it off so you need to modify the property that controls this on each folder you want to be affected.

The property that controls the Preview pane is this doesn't get set by default so in the absence of this property the preview pane is on. If the property does exist it will be one of three values

0 - Preview Pane is off
1 - Preview Pane set to rights
2 - Preview Pane set to bottom

To set this property you can use any of the Exchange API's on 2007 the best API to use is EWS in C# if you wanted to set this property you would need to use the following property definition in a FolderUpdate Operation

PathToExtendedFieldType ppainprop = new PathToExtendedFieldType();
ppainprop.PropertyName = "";
ppainprop.PropertyType = MapiPropertyTypeType.String;
ppainprop.DistinguishedPropertySetIdSpecified = true;
ppainprop.DistinguishedPropertySetId = DistinguishedPropertySetType.PublicStrings;

I've put together some methods in my EWS Powershell library to allow some easy PSH code to query and update this value on mailbox folders. I've created two seperate versions of a sample script to do this the first just sets this property on the inbox folder while the second loops through every mailbox folder and sets this property on every folder in a mailbox.

To use this script you need my ewsutil library which contains the EWS code you can download this from more information on using the library and connection and authentication options this is documented in another post.

I've posted both versions of the script here the version that loops through all folders looks like

$setPPTo = "0"

$casUrl = "https://servername/ews/exchange.asmx"
$mbMailboxEmail= ""
$ewc = new-object EWSUtil.EWSConnection("",$false, "username", "Password", "domain",$casUrl)

$fldarry = new-object EWSUtil.EWS.BaseFolderIdType[] 1
$dTypeFld = new-object EWSUtil.EWS.DistinguishedFolderIdType
$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::msgfolderroot
$mbMailbox = new-object EWSUtil.EWS.EmailAddressType
$mbMailbox.EmailAddress = $mbMailboxEmail
$dTypeFld.Mailbox = $mbMailbox
$fldarry[0] = $dTypeFld

$FolderList = $ewc.GetAllMailboxFolders($fldarry)
$fldarry1 = new-object EWSUtil.EWS.BaseFolderIdType[] $FolderList.Count
for ($fcint=0;$fcint -lt $FolderList.Count;$fcint++){
$fldarry1[$fcint] = $FolderList[$fcint].FolderId
$psPreviewSetting = new-object EWSUtil.EWS.PathToExtendedFieldType
$psPreviewSetting.DistinguishedPropertySetIdSpecified = $true
$psPreviewSetting.DistinguishedPropertySetId = [EWSUtil.EWS.DistinguishedPropertySetType]::PublicStrings
$psPreviewSetting.PropertyName = ""
$psPreviewSetting.PropertyType = [EWSUtil.EWS.MapiPropertyTypeType]::Integer
$beparray = new-object EWSUtil.EWS.BasePathToElementType[] 1
$beparray[0] = $psPreviewSetting
$Folders = $ewc.GetFolder($fldarry1,$beparray)
If ($Folders.Count -ne 0) {
ForEach ($Folder in $Folders) {
$cval = "n/a"
if ($Folder.extendedProperty -ne $null){
$cval = $Folder.extendedProperty[0].Item.ToString()
switch ($Folder.extendedProperty[0].Item.ToString()){
0 { $Folder.DisplayName + " Preview Pane Set Off"}
1 { $Folder.DisplayName + " Preview Pane Set Right"}
2 { $Folder.DisplayName + " Preview Pane Set Bottom"}
$Folder.DisplayName + " Not set will default to On"
if ($cval -ne $setPPTo){
$exProp = new-object EWSUtil.EWS.ExtendedPropertyType
$exProp.ExtendedFieldURI = $psPreviewSetting
$exProp.Item = $setPPTo

Sunday, January 18, 2009

Summarize MailStore growth and whitespace by Storage Group and growth exception reporting

This script takes a slightly different approach on some of the mailstore size and whitespace scripts that I've posted and a few others you may have seen posted around the traps. The great thing about scripting in the time of the WFC is that its free no budget required and the only limitation to what you can do is probably 1. your imagination 2. your skills. Given the WFC you should have much more time now to improve both (well at least the second). One of the new pressures that some admins maybe feeling in said WFC and the push towards virtulization is that the ratio of Servers to Admin is increasing so companies can at least from some perspective say they are cutting cost and raising efficacy of their IT teams.

So what does this have to do with this script well... because its a new year its a good chance to review and rethink traditional approaches. Pretty much any size reporting script you will see will look at giving you the current size of various components of your mailboxes/mailstore possible whitespace and retained items. This is where most scripters/developers stop, some may record this information in a database which is a great idea because you can then start to look at some growth trends. When you have a larger number of Servers, Stores and possibly Organizations if your a service provider just looking at recorded size information while usefull when its gets to large quantities it can be very hard for people to spot any anomalies or areas of interest that may need further investigation. Its also may lead to the discounting of such information by your more junior engineers if you can't provide a more valuable representation of the data your collecting with your scripts. The other users of this information your recording maybe your more senior managers who mostly like nice shiny reports but size reports that don't reflect the nature of change of the underlying system means its missing the point. If your size reports where written a few years ago and have had no changes since then you should get your finger out and your thinking cap on.

My approach to rewriting scripts from the traditional approach is first to make your script record historical data. The two options for doing this is firstly use a database which is a great idea because you can then use higher level reporting tools and SQL. The other option for recording historical information is using history files which is a good option where a database isn't practical which is the case for a lot of environments it also makes your scripting self contained and write once run everywhere so this is the approach I favour. To reduce information overload you should look at summarising data where you can. With this script I've summarised the size/whitespace data by Storage Group. This means if you have a large number of Storage groups spreed across a large number of servers you can a more manageable report. To do the data summerization I've use Custom objects and nested hashtables and a little bit of Math. The result of each run are written to a history file in a history directory which is used then to do exception and growth reporting.

Exception and growth reporting sounds hard but not really basically first there is some code that reads in the all the history file names that are in the history directory and then determines which file will represent the data from 1day 7days and 30days. Its then reads in each of these files into nested hashtable and index's it by combining the ServerName and StorageGroup. Then when the script runs through and builds the normal size report at the same time the current size is checked against the previous sizes and compaired against Threshold varibles. If any of the thresholds are breeched the an entry is added the the thresholds exception table.

Okay so to put all this togther into something thats usefull the script uses the Get-mailboxServer cmdlet to feed the servername of each of the mailbox servers within a Exchange Org into a function that then goes out and queries the size of the Mailbox and Public Folder Stores and then tries to use WMI to query the event logs this process is explained in a previous post. The results are combined into a HTML body of email and then the result is sent via SMTP to a mailbox (of a person who hopefully get some use from it). What gets emailed is firstly a table of all the Storage Group sizes summaries with WhiteSpace information. Then any exceptions are added after this first table. Before running the script you should set the following thresholds all sizes are in MB

$StoreSizeThreshold1day = 100
$StoreSizeThreshold7day = 300
$StoreSizeThreshold30day = 1000

$WhiteSpaceThreshold1day = 100
$WhiteSpaceThreshold7day = 300
$WhiteSpaceThreshold30day = 1000

$SMTPservername = "smtpservername"
$Toaddress = ""
$fromAddress = ""

The script will store the history file in the c:\sizehistory directory based on this line

$HistoryDir = "c:\sizehistory"

I've put a download of this script here the script itself looks like

$StoreSizeThreshold1day = 100
$StoreSizeThreshold7day = 300
$StoreSizeThreshold30day = 1000

$WhiteSpaceThreshold1day = 100
$WhiteSpaceThreshold7day = 300
$WhiteSpaceThreshold30day = 1000

$SMTPservername = ""
$Toaddress = ""
$fromAddress = ""

function GetWSforServer($snServerName){

$SgcombCollection = @()
"Querying Server : " + $snServerName

$StoreFileSizes = @{ }
$StoreWhitespace = @{ }
$StoreRetainItemSize = @{ }
$StoreDeletedMailboxSize = @{ }
$sgTotalHash = @{ }
$hcHourCount = @{ }
$root = [ADSI]'LDAP://RootDSE'
$cfConfigRootpath = "LDAP://" + $root.ConfigurationNamingContext.tostring()
$configRoot = [ADSI]$cfConfigRootpath
$searcher = new-object System.DirectoryServices.DirectorySearcher($configRoot)
$searcher.Filter = '(&(objectCategory=msExchExchangeServer)(cn=' + $snServerName + '))'
$searchres = $searcher.FindOne()
$snServerEntry = New-Object System.DirectoryServices.directoryentry
$snServerEntry = $searchres.GetDirectoryEntry()

$adsiServer = [ADSI]('LDAP://' + $snServerEntry.DistinguishedName)
$dfsearcher = new-object System.DirectoryServices.DirectorySearcher($adsiServer)
$dfsearcher.Filter = "(objectCategory=msExchPrivateMDB)"
$srSearchResults = $dfsearcher.FindAll()
foreach ($srSearchResult in $srSearchResults){
$msMailStore = $srSearchResult.GetDirectoryEntry()
$sgStorageGroup = $msMailStore.psbase.Parent
if ($sgStorageGroup.msExchESEParamBaseName -ne "R00"){
$edbfile = [WMI]("\\" + $snServerName + "\root\cimv2:CIM_DataFile.Name='" + $msMailStore.msExchEDBFile + "'")
$StoreFileSize = [math]::round($edbfile.filesize/1048576,0)
$exStoreName = ($sgStorageGroup.Name.ToString() + "\" + $msMailStore.Name.ToString())
"Finshed Mailstores"
$dfsearcher.Filter = "(objectCategory=msExchPublicMDB)"
$srSearchResults = $dfsearcher.FindAll()
foreach ($srSearchResult in $srSearchResults){
$msMailStore = $srSearchResult.GetDirectoryEntry()
$sgStorageGroup = $msMailStore.psbase.Parent
if ($sgStorageGroup.msExchESEParamBaseName -ne "R00"){
$edbfile = [WMI]("\\" + $snServerName + "\root\cimv2:CIM_DataFile.Name='" + $msMailStore.msExchEDBFile + "'")
$StoreFileSize = [math]::round($edbfile.filesize/1048576,0)
$exStoreName = ($sgStorageGroup.Name.ToString() + "\" + $msMailStore.Name.ToString())
"Finshed Public Folder Stores"
$WmidtQueryDT = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime([DateTime]::UtcNow.AddDays(-3))
Get-WmiObject -computer $snServerName -query ("Select * from Win32_NTLogEvent Where Logfile='Application' and Eventcode = '1221' and TimeWritten >='" + $WmidtQueryDT + "'") | sort $_.TimeWritten -Descending |
$mbnamearray = $_.message.split("`"")
$esEndString = $mbnamearray[2].indexof("megabytes ")-6
if ($StoreWhitespace.Containskey($mbnamearray[1]) -eq $false){
"Finished WhiteSpace"
$WmidtQueryDT = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime([DateTime]::UtcNow.AddDays(-3))
Get-WmiObject -computer $snServerName -query ("Select * from Win32_NTLogEvent Where Logfile='Application' and Eventcode = '1207' and TimeWritten >='" + $WmidtQueryDT + "'") | sort $_.TimeWritten -Descending |
$mbnamearray = $_.message.split("`"")
$enditem = $mbnamearray[2].Substring($mbnamearray[2].indexof("End:"))
$esize = $enditem.SubString($enditem.indexof("items")+7,$enditem.indexof(" Kbytes")-($enditem.indexof("items")+7))
if ($StoreRetainItemSize.Containskey($mbnamearray[1]) -eq $false){

"Finshed Retained Items"
$WmidtQueryDT = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime([DateTime]::UtcNow.AddDays(-3))
Get-WmiObject -computer $snServerName -query ("Select * from Win32_NTLogEvent Where Logfile='Application' and Eventcode = '9535' and TimeWritten >='" + $WmidtQueryDT + "'") | sort $_.TimeWritten -Descending |
$mbnamearray = $_.message.split("`"")
$retMbs = $mbnamearray[2].Substring($mbnamearray[2].indexof("have been removed."))
$retMbsSize = $retMbs.Substring(($retMbs.indexof("(")+1),$retMbs.indexof(")")-($retMbs.indexof("(")+1)).Replace(" KB","")
if ($StoreDeletedMailboxSize.Containskey($mbnamearray[1]) -eq $false){

$StoreFileSizes.GetEnumerator() | sort name -descending | foreach-object {
$snames = $_.key.split("\")
if ($sgTotalHash.containsKey($snames[0])){
if ($StoreDeletedMailboxSize.containskey($_.key)){
$sgTotalHash[$snames[0]].MailboxStores = [int]$sgTotalHash[$snames[0]].MailboxStores + 1
$sgTotalHash[$snames[0]].MSSize = [int]$sgTotalHash[$snames[0]].MSSize + [int]$_.Value
$sgTotalHash[$snames[0]].MSWhiteSpace = [int]$sgTotalHash[$snames[0]].MSWhiteSpace + [int]$StoreWhitespace[$_.key].ToString()
$sgTotalHash[$snames[0]].MSRetainedItems = [int]$sgTotalHash[$snames[0]].RetainedItems + [int]$StoreRetainItemSize[$_.key].ToString()
$sgTotalHash[$snames[0]].RetainedMB = [int]$sgTotalHash[$snames[0]].RetainedMB + [int]$StoreDeletedMailboxSize[$_.key].ToString()

$sgTotalHash[$snames[0]].PublicFolderStores = [int]$sgTotalHash[$snames[0]].MailboxStores + 1
$sgTotalHash[$snames[0]].PFSize = [int]$sgTotalHash[$snames[0]].MSSize + [int]$_.Value
$sgTotalHash[$snames[0]].PFWhiteSpace = [int]$sgTotalHash[$snames[0]].MSWhiteSpace + [int]$StoreWhitespace[$_.key].ToString()
$sgTotalHash[$snames[0]].PFRetainedItems = [int]$sgTotalHash[$snames[0]].RetainedItems + [int]$StoreRetainItemSize[$_.key].ToString()


$sgtotalObject = "" | select ServerName,Name,MailboxStores,PublicFolderStores,MSSize,PFSize,MSWhiteSpace,PFWhiteSpace,MSRetainedItems,PFRetainedItems,RetainedMB
$sgtotalObject.ServerName = $snServerName
$sgtotalObject.Name = $snames[0]
if ($StoreDeletedMailboxSize.containskey($_.key)){
$sgtotalObject.MailboxStores = 1
$sgtotalObject.PublicFolderStores = 0
$sgtotalObject.MSSize = $_.Value.ToString()
$sgtotalObject.PFSize = 0
$sgtotalObject.MSWhiteSpace = $StoreWhitespace[$_.key].ToString()
$sgtotalObject.PFWhiteSpace = 0
$sgtotalObject.MSRetainedItems = $StoreRetainItemSize[$_.key].ToString()
$sgtotalObject.PFRetainedItems = 0
$sgtotalObject.RetainedMB = $StoreDeletedMailboxSize[$_.key].ToString()
else {
$sgtotalObject.MailboxStores = 0
$sgtotalObject.PublicFolderStores = 1
$sgtotalObject.MSSize = 0
$sgtotalObject.PFSize = $_.Value.ToString()
$sgtotalObject.MSWhiteSpace = 0
$sgtotalObject.PFWhiteSpace = $StoreWhitespace[$_.key].ToString()
$sgtotalObject.MSRetainedItems = 0
$sgtotalObject.PFRetainedItems = $StoreRetainItemSize[$_.key].ToString()
$sgtotalObject.RetainedMB = 0



foreach ($row in $sgTotalHash.Values){
$global:rpReport = $global:rpReport + " <tr>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $snServerName + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.Name.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.MailboxStores.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.PublicFolderStores.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.MSSize.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.PFSize.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.MSWhiteSpace.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.PFWhiteSpace.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.MSRetainedItems.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.PFRetainedItems.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "<td>" + $row.RetainedMB.ToString() + "</td>" + "`r`n"
$global:rpReport = $global:rpReport + "</tr>" + "`r`n"
$global:ExcombCollection += $row
if ($onedaystats.Containskey(($snServerName + "\" + $row.Name.ToString()))){
$onedaystats[($snServerName + "\" + $row.Name.ToString())].MSSize
if(([INT]$row.MSSize - [INT]$onedaystats[($snServerName + "\" + $row.Name.ToString())].MSSize) -gt $StoreSizeThreshold1day){
writeThresholdExecption "One Day MailStore Size" $snServerName $row.Name.ToString() $row.MSSize $onedaystats[($snServerName + "\" + $row.Name.ToString())].MSSize.ToString()
if(([INT]$row.PFSize - [INT]$onedaystats[($snServerName + "\" + $row.Name.ToString())].PFSize) -gt $StoreSizeThreshold1day){
writeThresholdExecption "One Day PublicStore Size" $snServerName $row.Name.ToString() $row.PFSize $onedaystats[($snServerName + "\" + $row.Name.ToString())].PFSize.ToString()
if(([INT]$row.MSWhiteSpace - [INT]$onedaystats[($snServerName + "\" + $row.Name.ToString())].MSWhiteSpace) -gt $WhiteSpaceThreshold1day){
writeThresholdExecption "One Day Mail Store WhiteSpace" $snServerName $row.Name.ToString() $row.MSWhiteSpace $onedaystats[($snServerName + "\" + $row.Name.ToString())].MSWhiteSpace.ToString()
if(([INT]$row.PFWhiteSpace - [INT]$onedaystats[($snServerName + "\" + $row.Name.ToString())].PFWhiteSpace) -gt $WhiteSpaceThreshold1day){
writeThresholdExecption "One Day Public Store WhiteSpace" $snServerName $row.Name.ToString() $row.PFWhiteSpace $onedaystats[($snServerName + "\" + $row.Name.ToString())].PFWhiteSpace.ToString()

if ($onedaystats.Containskey(($snServerName + "\" + $row.Name.ToString()))){
$sevendaystats[($snServerName + "\" + $row.Name.ToString())].MSSize
if(([INT]$row.MSSize - [INT]$sevendaystats[($snServerName + "\" + $row.Name.ToString())].MSSize) -gt $StoreSizeThreshold7day){
writeThresholdExecption "Seven Day MailStore Size" $snServerName $row.Name.ToString() $row.MSSize $sevendaystats[($snServerName + "\" + $row.Name.ToString())].MSSize.ToString()
if(([INT]$row.PFSize - [INT]$sevendaystats[($snServerName + "\" + $row.Name.ToString())].PFSize) -gt $StoreSizeThreshold7day){
writeThresholdExecption "Seven Day Public Store Size" $snServerName $row.Name.ToString() $row.PFSize $sevendaystats[($snServerName + "\" + $row.Name.ToString())].PFSize.ToString()
if(([INT]$row.MSWhiteSpace - [INT]$sevendaystats[($snServerName + "\" + $row.Name.ToString())].MSWhiteSpace) -gt $WhiteSpaceThreshold7day){
writeThresholdExecption "Seven Day Mail Store WhiteSpace" $snServerName $row.Name.ToString() $row.MSWhiteSpace $sevendaystats[($snServerName + "\" + $row.Name.ToString())].MSWhiteSpace.ToString()
if(([INT]$row.PFWhiteSpace - [INT]$sevendaystats[($snServerName + "\" + $row.Name.ToString())].PFWhiteSpace) -gt $WhiteSpaceThreshold7day){
writeThresholdExecption "Seven Day Public Store WhiteSpace" $snServerName $row.Name.ToString() $row.PFWhiteSpace $sevendaystats[($snServerName + "\" + $row.Name.ToString())].PFWhiteSpace.ToString()
if ($onemonthsstats.Containskey(($snServerName + "\" + $row.Name.ToString()))){
$onemonthsstats[($snServerName + "\" + $row.Name.ToString())].MSSize
if(([INT]$row.MSSize - [INT]$onemonthsstats[($snServerName + "\" + $row.Name.ToString())].MSSize) -gt $StoreSizeThreshold30day){
writeThresholdExecption "Thirty Day MailStore Size" $snServerName $row.Name.ToString() $row.MSSize $onemonthsstats[($snServerName + "\" + $row.Name.ToString())].MSSize.ToString()
if(([INT]$row.PFSize - [INT]$onemonthsstats[($snServerName + "\" + $row.Name.ToString())].PFSize) -gt $StoreSizeThreshold30day){
writeThresholdExecption "Thirty Day Public Store Size" $snServerName $row.Name.ToString() $row.PFSize $onemonthsstats[($snServerName + "\" + $row.Name.ToString())].PFSize.ToString()
if(([INT]$row.MSWhiteSpace - [INT]$onemonthsstats[($snServerName + "\" + $row.Name.ToString())].MSWhiteSpace) -gt $WhiteSpaceThreshold30day){
writeThresholdExecption "Thirty Day MailStore WhiteSpace" $snServerName $row.Name.ToString() $row.MSWhiteSpace $onemonthsstats[($snServerName + "\" + $row.Name.ToString())].MSWhiteSpace.ToString()
if(([INT]$row.PFWhiteSpace - [INT]$onemonthsstats[($snServerName + "\" + $row.Name.ToString())].PFWhiteSpace) -gt $WhiteSpaceThreshold30day){
writeThresholdExecption "Thirty Day Public Store WhiteSpace" $snServerName $row.Name.ToString() $row.PFWhiteSpace $onemonthsstats[($snServerName + "\" + $row.Name.ToString())].PFWhiteSpace.ToString()


function writeThresholdExecption([String]$threshold,[String]$tsServerName,[String]$tsSGName,[String]$tscValue,[String]$tspValue) {
$global:expReport = $global:expReport + " <tr>" +"`r`n"
$global:expReport = $global:expReport + "<td>" + $threshold + "</td>" +"`r`n"
$global:expReport = $global:expReport + "<td>" + $tsServerName + "</td>" +"`r`n"
$global:expReport = $global:expReport + "<td>" + $tsSGName + "</td>" +"`r`n"
$global:expReport = $global:expReport + "<td>" + $tscValue + "</td>" +"`r`n"
$global:expReport = $global:expReport + "<td>" + $tspValue + "</td>" +"`r`n"
$global:expReport = $global:expReport + "<td>" + ([INT]$tscValue - [INT]$tspValue) + "</td>" +"`r`n"
$global:expReport = $global:expReport + "</tr>" + "`r`n"


$HistoryDir = "c:\sizehistory"

if (!(Test-Path -path $HistoryDir))
New-Item $HistoryDir -type directory
$global:rpReport = ""
$global:expReport = ""
$global:rpReport = $global:rpReport + "<table><tr bgcolor=`"#95aedc`">" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>ServerName</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:15%;`" ><b>SG Name</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:5%;`" ><b>MS #</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:5%;`" ><b>PFS #</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>MS Size</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>PF Size</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>MSWSpace</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>PFWpace</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>MSRetItems</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>PFRetItems</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "<td align=`"center`" style=`"width:10%;`" ><b>RetMB</b></td>" +"`r`n"
$global:rpReport = $global:rpReport + "</tr>" + "`r`n"
$global:expReport = $global:expReport + "<table><tr bgcolor=`"#95aedc`">" +"`r`n"
$global:expReport = $global:expReport + "<td align=`"center`" style=`"width:15%;`" ><b>Threshold</b></td>" +"`r`n"
$global:expReport = $global:expReport + "<td align=`"center`" style=`"width:15%;`" ><b>ServerName</b></td>" +"`r`n"
$global:expReport = $global:expReport + "<td align=`"center`" style=`"width:15%;`" ><b>SGName</b></td>" +"`r`n"
$global:expReport = $global:expReport + "<td align=`"center`" style=`"width:10%;`" ><b>Current Value</b></td>" +"`r`n"
$global:expReport = $global:expReport + "<td align=`"center`" style=`"width:10%;`" ><b>Previous Value</b></td>" +"`r`n"
$global:expReport = $global:expReport + "<td align=`"center`" style=`"width:10%;`" ><b>Growth</b></td>" +"`r`n"
$global:expReport = $global:expReport + "</tr>" + "`r`n"
$global:ExcombCollection = @()
$datetime = get-date
$arArrayList = New-Object System.Collections.ArrayList
dir $script:HistoryDir\*.csv | foreach-object{
$fname = $
$nmArray = $"-")
$spoint = $arArrayList[$arArrayList.Count-1]
$oneday = $spoint
$sevenday = $spoint
$onemonth = $spoint
$oneyear = $spoint
foreach ($file in $arArrayList){
if ($file -gt ($datetime.Adddays(-2).ToString("yyyyMMdd")) -band $file -lt $oneday) {$oneday = $file}
if ($file -gt ($datetime.Adddays(-7).ToString("yyyyMMdd")) -band $file -lt $sevenday) {$sevenday = $file}
if ($file -gt ($datetime.Adddays(-31).ToString("yyyyMMdd")) -band $file -lt $onemonth) {$onemonth = $file}

$onedaystats = @{ }
$sevendaystats = @{ }
$onemonthsstats = @{ }
$oneyearstats = @{ }

Import-Csv ("$script:HistoryDir\" + $oneday + "-wspacerun.csv") | %{
$onedaystats.add(($_.ServerName.ToString() + "\" + $_.Name.ToString()),$_)
Import-Csv ("$script:HistoryDir\" + $sevenday + "-wspacerun.csv") | %{
$sevendaystats.add(($_.ServerName.ToString() + "\" + $_.Name.ToString()),$_)
Import-Csv ("$script:HistoryDir\" + $onemonth + "-wspacerun.csv") | %{
$onemonthsstats.add(($_.ServerName.ToString() + "\" + $_.Name.ToString()),$_)

Get-MailboxServer | foreach-object{
$datetime = get-date
$fname = $script:HistoryDir + "\"
$fname = $fname + $datetime.ToString("yyyyMMdd") + "-wspacerun.csv"
$global:ExcombCollection | export-csv –encoding "unicode" -noTypeInformation $fname
$global:rpReport = $global:rpReport + "</table>" + " <BR><BR><H1>Threshold Exceeds</H1><BR> "
$global:rpReport = $global:rpReport + $global:expReport
$global:rpReport = $global:rpReport + "</table>" + " <BR><BR> "

$SmtpClient = new-object
$ = $SMTPservername
$MailMessage = new-object System.Net.Mail.MailMessage
$MailMessage.From = $FromAddress
$MailMessage.Subject = "Storage Group Reports"
$MailMessage.IsBodyHtml = $TRUE
$MailMessage.body = $global:rpReport
write-host "Mail Sent"