Thursday, October 26, 2006

Showing which mailboxes have been enabled with Outlook Direct Booking via a script

Outlook direct booking is one of the many ways in which you can handle resource mailboxes in Exchange for a full list of other methods have a look at slipstick. Knowing which mailboxes have been enabled in your Exchange Organization could be a little tricky to keep a track of if you have a large number of mailboxes. One method of finding what mailboxes are using Direct booking is by using the freebusy data on a server. How Free-Busy data works and is stored is documented on Technet and this post on the Exchange Team Blog . The Free Busy data also comes into to play with Outlook Direct booking. When a user configures direct booking via the 3 check boxes under resource scheduling in Outlook – Calendar options there are 3 Mapi properties that get set on the local freebusy object in a mailbox. (Note this is not the only thing that happens you do enable direct booking permissions are also modified on the calendar and freebusy object in the resource mailbox)

Automatically accept meeting and process cancellations relates to 0x686D000B
Automatically decline conflicting meeting requests relates to 0x686F000B
Automatically decline recurring meeting requests relates to 0x686E000B

These properties are also published on the users freebusy object located in the SCHEDULE+ FREE BUSY public folder. When a user creates an appointment and selects a mailbox as a resource Outlook queries the public free busy object for that mailbox to determine if Direct Booking is enabled. So basically what you can do in a script is to walk the objects in the public free busy folder and check for these properties on each of the objects. If you read the documentation in the links I provided above you will see that Freebusy data is stored per Administrative Group in Exchange. The script I wrote uses CDO 1.2 to connect to a mailbox (its not really that important which mailbox as long as it is publishing freebusy data). In then uses the PR_FREEBUSY_ENTRYIDS 0x36E41102 mapi property which is a multivalued binary property stored in the root of a mailbox that holds the EntryID’s of various freebusy objects for that mailbox. The second ID in the prop holds the EntryID for the local freebusy object the third holds the EntryID for the public freebusy object for this mailbox. I’ve made use of this third one by using it to connect to the public freebusy object of the mailbox the script is connecting to and then just moving up to the parent folder object which will give the script the ability to walk all the objects in the public freebusy folder by using the message collection of this parent folder. To finish with the script generates a small html report called DOBreport.htm is c:\temp with a list of what mailboxes have direct booking enabled and what resource scheduling options have been selected. If you have multiple Administrative groups in your Exchange Organization you will need to connect to a mailbox in each exchange admin group to cover all mailboxes. Before running the script you need to configure the following two variables with the servername and mailbox of a mailbox to use.

MailServer = "servername"
Mailbox = "mailbox"

I’ve put a downloadable copy of the script here the script itself look like

MailServer = "mailbox"
Mailbox = "user"
Const PR_FREEBUSY_ENTRYIDS = &H36E41102
Const PR_PARENT_ENTRYID = &H0E090102
Const PR_RECALCULATE_FREEBUSY = &H10F2000B
Const PR_FREEBUSY_DATA = &H686C0102
report = "<table border=""1"" width=""100%"">" & vbcrlf
report = report & " <tr>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Mailbox-Name</font></b></td>"
& vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Auto
Process Meetings</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Auto
Decline conflicts</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Auto
Decline recurring</font></b></td>" & vbcrlf
report = report & "</tr>" & vbcrlf

set objSession = CreateObject("MAPI.Session")
strProfile = MailServer & vbLf & Mailbox
objSession.Logon "",,, False,, True, strProfile
Set objInfoStores = objSession.InfoStores
set objInfoStore = objSession.GetInfoStore
Set objpubstore = objSession.InfoStores("Public Folders")
Set objRoot = objInfoStore.RootFolder

set non_ipm_rootfolder =
objSession.getfolder(objroot.fields.item(PR_PARENT_ENTRYID),objInfoStore.id)
fbids = non_ipm_rootfolder.fields.item(PR_FREEBUSY_ENTRYIDS).value
set publicfbusy = objSession.getmessage(fbids(2),objpubstore.id)
set publicfbusyfold =
objSession.getfolder(publicfbusy.fields.item(PR_PARENT_ENTRYID),objpubstore.id)
on error resume next
for each fbmess in publicfbusyfold.messages
wscript.echo fbmess.subject
wscript.echo "Automatically accept meeting and process cancellations : " &
fbmess.fields.item(&H686D000B)
wscript.echo "Automatically decline conflicting meeting requests : " &
fbmess.fields.item(&H686F000B)
wscript.echo "Automatically decline recurring meeting requests : " &
fbmess.fields.item(&H686E000B)
wscript.echo
if err.number <> 0 then
err.clear
else
if fbmess.fields.item(&H686D000B).value = true then
report = report & "<tr>" & vbcrlf
report = report & "<td align=""center"">" & fbmess.subject & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & fbmess.fields.item(&H686D000B) &
"&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & fbmess.fields.item(&H686F000B) &
"&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & fbmess.fields.item(&H686E000B) &
"&nbsp;</td>" & vbcrlf
report = report & "</tr>" & vbcrlf
end if
end if
next
report = report & "</table>" & vbcrlf
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\temp\DOBreport.htm",2,true)
wfile.write report
wfile.close
set wfile = nothing
set fso = nothing

Friday, October 20, 2006

Creating a Domain based auto response rule using rule.dll

Somebody asked last week about creating a personalized auto response message based on the sender domain. While this would be pretty easy to do with a normal store based event sink another option you can use is to create a server side rule using the rule.dll. The script to do this is pretty basic there are numerous samples that show how to create a rule that will process mail based on a string in the subject field. Well all you really need to do is take that same logic and apply it to the sender address instead. So you basically end up with a rule that does a substring on the sender address looking for in this case a particular email domain and then use the Reply action which creates a normal automatic reply email using the text you configure.

To use the script just configure the following line with the domain you want to use

importPropVal.Value = "@domain.com”

And this line with the text to respond with

objReplyMsg.Text = "Im Sorry this Mailbox isn't currently maned for after hour enquires please contact 2223-2222-222"

I’ve put a download copy of the script here the script itself looks like.

const SUBSTRING = 1 ' Substring
const IGNORECASE = &H00010000 ' Ignore case
const PR_SENDER_EMAIL_ADDRESS = &H0C1F001E
Const ACTION_REPLY = 4


servername = "servername"
mailboxname = "mailbox"

Set objSession = CreateObject("MAPI.Session")

objSession.Logon "","",false,true,true,true,servername & vbLF & mailboxname
Set objRules = CreateObject("MSExchange.Rules")
objRules.Folder = objSession.Inbox
Set objInbox = objSession.Inbox

Set CdoInfoStore = objSession.GetInfoStore
Set CdoFolderRoot = CdoInfoStore.RootFolder
Set CdoFolders = CdoFolderRoot.Folders

Set importPropVal = CreateObject("MSExchange.PropertyValue")
importPropVal.Tag = PR_SENDER_EMAIL_ADDRESS
importPropVal.Value = "@domain.com”

Set importPropCond = CreateObject("MSExchange.ContentCondition")
importPropCond.PropertyType = PR_SENDER_EMAIL_ADDRESS
importPropCond.Operator = SUBSTRING + IGNORECASE
importPropCond.Value = importPropVal

' Create reply message and store in HiddenMessages collection.
Set objReplyMsg = objInbox.HiddenMessages.Add

' Set reply message properties.
objReplyMsg.Type = "IPM.Note.Rules.ReplyTemplate.Microsoft"
objReplyMsg.Text = "Im Sorry this Mailbox isn't currently maned for after hour enquires please contact 333-333-33"
objReplyMsg.Update


' Create action
Set objAction = CreateObject("MSExchange.Action")
objAction.ActionType = ACTION_REPLY
objAction.Arg = Array(objReplyMsg.ID,objReplyMsg.FolderID)

' Create new rule
Set objRule = CreateObject("MSExchange.Rule")
objRule.Name = "Domain Reply Rule"

' Add action and assign condition
objRule.Actions.Add , objAction
objRule.Condition = importPropCond

' Add rule and update
objRules.Add , objRule
objRules.Update

' Log off and cleanup
objSession.Logoff

Set objRules = Nothing
Set objSession = Nothing
Set importProp = Nothing
Set importPropVal = Nothing
Set objAction = Nothing
Set objRule = Nothing

Thursday, October 12, 2006

Counting the number and type of Members in a Distribution list / Group via a script

A couple of months back I posted this script that would detect any empty distribution groups and give you the chance to delete them. Another useful thing to do now and again when your examining distribution lists in your Exchange organization is to look at how many members each group has and also what type of objects are members of certain lists. In Exchange apart from just user mailboxes you may have contacts (both internal and external), nested groups, Query based distribution lists or even public folders that are members of a distribution list. So its possible that some lists may have collected unwanted baggage in the normal course of events.

So what I’ve done is come up with 3 different scripts for counting members based on the type of member of each distribution list and then generating a little html report to show the number of each type of object within each distribution list. The first script just does a basic count of the members of each list the second expands any nested groups so it comes up with the true number of members of a list (e.g. if you send a mail to this list how many mailboxes a mail will be delivered to etc). The third script is a script that looks at all the Query based distribution lists (where the first two look just at groups) and expands each list.

The script is broken up into a multitude of ADSI queries the first query that is done is for the purpose of calculating whether contacts that are enumerated by the script have a external or internal target address. To differentiated between internal and external all the domains that are listed in each of the recipient policies are enumerated and then added to a scripting dictionary. The dictionary can then be easily checked to see if the domain of the target email address for the contact is internal or external.

The next query then enumerates all groups that are mail enabled and goes through each group one at a time and looks though the members collection of each group looking at the email address and type of each member. Separate variables track the number of each of the type of objects and if the type of object is a group or dynamic distribution list then the group is expanded and each member of that group is also checked. To guard against circular references a separate scripting dictionary object is maintained to ensure that each object is checked and recorded only once per iteration. A separate sub exists to handle the expansion of query based distribution lists by reading the filter AD attributes and then doing a separate ADSI query baaed on these values. Finally there is some code that then builds the result into a html table to display to the users. The report is named Groupreport.htm and is stored in c:\temp directory.

I’ve put a downloadable copy of the three scripts here basically the three scripts contained in the download are

gnumbs.vbs Only counts member of the current list doesn’t process nested lists

gnumbsnested.vbs Counts all member of a groups including those in nested groups and Query based DL’s

gnumbsqbdl.vbs Displays total number of members for Querybased Distribution lists

the nested group script looks like

set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strDefaultNamingContext = iAdRootDSE.Get("defaultNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"

report = "<table border=""1"" width=""100%"">" & vbcrlf
report = report & " <tr>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Group-Name</font></b></td>"
& vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Total
Number of Members</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Mailboxes</font></b></td>"
& vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Contacts-Internal</font></b></td>"
& vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Contacts-External</font></b></td>"
& vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Groups</font></b></td>"
& vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">QueryBased
Dl's</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">Public
Folders</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font color=""#FFFFFF"">InterOrg-Person</font></b></td>"
& vbcrlf
report = report & "</tr>" & vbcrlf

trackCicularref = ""

numcheck = 0
usNumberUsers = 0
cnNumberinContacts = 0
cnNumberexContacts = 0
nsNumberGroups = 0
pfNumberPublicFolders = 0
ioNumberIorgPersons = 0
QbNumberQBDLs = 0
set trackmembers = CreateObject("Scripting.Dictionary")


GALQueryFilter = "(&(mail=*)(objectCategory=group))"
strQuery = "<LDAP://" & strDefaultNamingContext & ">;" & GALQueryFilter & ";distinguishedName,displayname,legacyExchangeDN,homemdb;subtree"
Com.ActiveConnection = Conn
Com.Properties("SearchScope") = 2 ' we want to search everything
Com.Properties("Page Size") = 500 ' and we want our records in lots of 500 (must
be < query limit)
polQuery = "<LDAP://" & iAdRootDSE.Get("configurationNamingContext") & ">;(objectCategory=msExchRecipientPolicy);distinguishedName,gatewayProxy;subtree"
Com.CommandText = polQuery
set sdSMTPDomains = CreateObject("Scripting.Dictionary")
Set adRs = Com.Execute
while not adRs.eof
for each adr in adRs.fields("gatewayproxy").value
if instr(lcase(adr),"smtp:") then
if sdSMTPDomains.exists(LCase(right(adr,(len(adr)-instr(adr,"@"))))) then
else
sdSMTPDomains.Add LCase(right(adr,(len(adr)-instr(adr,"@")))),1
end if
end if
next
adRs.movenext
Wend

Com.CommandText = strQuery
Set Rs = Com.Execute

while not rs.eof
trackmembers.RemoveAll
numcheck = 0
usNumberUsers = 0
cnNumberinContacts = 0
cnNumberexContacts = 0
nsNumberGroups = 0
pfNumberPublicFolders = 0
ioNumberIorgPersons = 0
QbNumberQBDLs = 0
trackmembers.add rs.fields("distinguishedName"),1
enumgroup(getobject("LDAP://" &
replace(rs.fields("distinguishedName"),"/","\/")))
wscript.echo rs.fields("displayname")
wscript.echo usNumberUsers
wscript.echo cnNumberinContacts
wscript.echo cnNumberexContacts
wscript.echo nsNumberGroups
wscript.echo QbNumberQBDLs
wscript.echo pfNumberPublicFolders
wscript.echo ioNumberIorgPersons
report = report & "<tr>" & vbcrlf
report = report & "<td align=""center"">" & rs.fields("displayname") &
"&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & numcheck & "&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & usNumberUsers & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & cnNumberinContacts & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & cnNumberexContacts & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & nsNumberGroups & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & QbNumberQBDLs & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & pfNumberPublicFolders &
"&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & ioNumberIorgPersons & "&nbsp;</td>"
& vbcrlf
report = report & "</tr>" & vbcrlf
rs.movenext
wend

report = report & "</table>" & vbcrlf
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\temp\Groupreport.htm",2,true)
wfile.write report
wfile.close
set wfile = nothing
set fso = nothing

sub enumGroup(objgroup)
for each member in objgroup.members
if Not trackmembers.exists(member.distinguishedName) Then
trackmembers.add member.distinguishedName,1
wscript.echo member.class & " " & member.displayname
If member.mail <> "" then
Select Case member.Class
Case "user" usNumberUsers = usNumberUsers + 1
Case "contact" wscript.echo
right(member.mail,(len(member.mail)-instr(member.mail,"@")))
If
sdSMTPDomains.exists(LCase(right(member.mail,(len(member.mail)-instr(member.mail,"@")))))
Then
cnNumberinContacts = cnNumberinContacts + 1
Else
cnNumberexContacts = cnNumberexContacts + 1
End if
Case "group" nsNumberGroups = nsNumberGroups + 1
enumgroup(getobject("LDAP://" & replace(member.distinguishedName,"/","\/")))
Case "publicFolder" pfNumberPublicFolders = pfNumberPublicFolders + 1
Case "inetOrgPerson" ioNumberIorgPersons = ioNumberIorgPersons + 1
Case "msExchDynamicDistributionList" QbNumberQBDLs = QbNumberQBDLs + 1
enumQBDL(getobject("LDAP://" & replace(member.distinguishedName,"/","\/")))
End Select
numcheck = numcheck + 1
End if
end if
Next
end Sub

Sub enumQBDL(obdlobject)
if obdlobject.msExchDynamicDLBaseDN = "" then
strQuerydl = "<LDAP://" & strDefaultNamingContext & ">;" &
obdlobject.msExchDynamicDLFilter &
";mail,ObjectClass,distinguishedName,displayname,legacyExchangeDN,homemdb;subtree"
else
strQuerydl = "<LDAP://" & obdlobject.msExchDynamicDLBaseDN & ">;" &
obdlobject.msExchDynamicDLFilter &
";mail,ObjectClass,distinguishedName,displayname,legacyExchangeDN,homemdb;subtree"
end if
set com1 = createobject("ADODB.Command")
Com1.ActiveConnection = Conn
Com1.Properties("SearchScope") = 2 ' we want to search everything
Com1.Properties("Page Size") = 500 ' and we want our records in lots of 500
(must be < query limit)
wscript.echo strQuerydl
Com1.CommandText = strQuerydl
Set qdlRs = Com1.Execute
While Not qdlRS.eof
Set member = getobject("LDAP://" &
replace(qdlRS.fields("distinguishedName"),"/","\/"))
if Not trackmembers.exists(member.distinguishedName) Then
trackmembers.add member.distinguishedName,1
wscript.echo member.class & " " & member.displayname
If member.mail <> "" then
Select Case member.Class
Case "user" usNumberUsers = usNumberUsers + 1
Case "contact" wscript.echo
right(member.mail,(len(member.mail)-instr(member.mail,"@")))
If
sdSMTPDomains.exists(LCase(right(member.mail,(len(member.mail)-instr(member.mail,"@")))))
Then
cnNumberinContacts = cnNumberinContacts + 1
Else
cnNumberexContacts = cnNumberexContacts + 1
End if
Case "group" nsNumberGroups = nsNumberGroups + 1
enumgroup(getobject("LDAP://" & replace(member.distinguishedName,"/","\/")))
Case "publicFolder" pfNumberPublicFolders = pfNumberPublicFolders + 1
Case "inetOrgPerson" ioNumberIorgPersons = ioNumberIorgPersons + 1
Case "msExchDynamicDistributionList" QbNumberQBDLs = QbNumberQBDLs + 1
enumQBDL(getobject("LDAP://" & replace(member.distinguishedName,"/","\/")))
End Select
numcheck = numcheck + 1
End if
end if
qdlRS.movenext
wend

End Sub

Thursday, October 05, 2006

BYO Message Tracking Center with PowerShell

One of the things I blog and create a lot of scripts for is message tracking there is so much good information stored in the Message Tracking Logs once you can pull it out and start doing things beyond what the Message Tracking Center can do in ESM. With the ability to access and manipulate WinForms like proper .net applications and also access WMI information you can with a little effort build your own replacement to the normal Message Tracking Center in ESM with a powershell script that will give you a pretty GUI based front-end that even your average Admin should be able to get their head around. And the really cool thing is you can start doing a lot more with the data that you’re mining such as aggregation. By this I mean grouping the number of emails by size and number to alllow you to see trends such as who is sending the most email, who's recieving the most email, how much data is being send and recieved from each email domain and also how much email was sent on a particular date. You can also do a lot more filtering such as only looking at mail that was sent over a certain size , sent/receive or external/internal. These last two are a little experimental which I’ll try to explain later. Most of this stuff I’ve done in the past with other scripts and web apps but this is the first time I’ve pulled it all together using the live logs from a server and I’ve managed to patch up some of my screwy logic (and maybe I’ve created some more).And also you can do one of the things that has always frustrated me about the message tracking center which is export the results of your query to a CSV file.

What does this script do well basically it creates a Winform and adds a whole bunch of controls to that winform such as datapickers, textboxes, checkboxes, numericupanddown s,labels and buttons. Its then wires ups some functions to the button clicks so that when you click the search button in will query the Exchange Message Tracking logs via WMI. The data returned is then added to a ADO.NET data table which is then data bound to a datagridview to display back in the form. Clicking the export button will fire a save-file dialogue box and some code will then export the table results to a csv file. The WMI query looks for message tracking events of type 1020 and 1028 which from previous experience is the best way to capture all the messaging activity without having to worry about doing any address translations. I’ve included some textbox’s to do the normal filtering you would expect in the message tracking center such as from and sent address if these are left blank it will search on all addresses. Also I’ve added the ability to do add an extra filter such as showing only email over a certain size. The other two slightly experimental filters are the Direction of email which is meant to filter email that’s being sent or receive and the Type of Email which is meant to filter email that is Internal or External. Now these two filters work after the query has been made there is a section in the script that queries active directory for all the recipient policies in a domain and goes through each of the polices and adds each of the domains it finds into a hashtable. Then to work out if an email is Internal or External its basically checks the domain of the sender address to see if its contained within the hash table (meaning that its coming from a local sender) and then it also does the same check to see if the recipients domain is in the hash table if both match then the email is an Internal message if they don’t then its External. For sent and received this is much the e same process except a Sent message will have the logic that the from address of a message must be from a domain in the hash table and for a Received message the from address shouldn’t be in the hash table. From a logic standpoint this works okay on a single server but there will be some situations that maybe this wont work but hey..

But the filtering is really just the normal stuff the cool stuff is in the Extras Groups box on the right hand side of the form. When you select one of these boxes (and there is some code to make sure you don’t select more then one at a time) the byo message tracker now turns into an aggregate engine. The aggregation works by instead of just adding the results directly to a Datatable the results are instead feed into two hash tables. One hashtable tracks the number of messages for the value your aggregating and the other tracks the size of the messages for the value your aggregating. So for example if you wanted to find out how much email was being sent to fred.blogs@domain.com when the script was iterating though the WMI results collection it would first check to see if fred.blogs@domain.com was already in the hash table. If it was it would then for the “number of messages” hashtable increments it by one and for the “size” hash table adds the size of the message to the current value in the hash table. Once the WMI result iteration has finished there is some code that then loops though the hash table and adds the hash table results to the data-table and then bind this to the datagridview. This generally works the same for all the group by’s the domain group by just separates the domain from the email address and groups on that instead of the email address and the date again groups on the date instead of the address.

To run the script is basically straight forward when you run it the script will build the winform and should then present this as an active window. The only necessary text box you need to fill out is the servername box for the server you wish to query. From and To are optional leaving these blank means it will query for all address’s. Using the other features should be pretty much self explanatory. A word of warning depending on the size of you message tracking logs and the period of time you query for you may have to be patient waiting for the results.

The main section of the code look like this the full code is included in the download because it’s just to large to post. The code can be downloaded from here


[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")

function CheckExtraSettings{
$extrasettings = 0
if ($GroupbySender.Checked -eq $true) {$extrasettings = 1}
if ($GroupByReciever.Checked -eq $true) {$extrasettings = 1}
if ($GroupByRecieverDomain.Checked -eq $true) {$extrasettings = 1}
if ($GroupBySenderDomain.Checked -eq $true) {$extrasettings = 1}
if ($GroupByDate.Checked -eq $true) {$extrasettings = 1}
return $extrasettings
}

function AggResults([string]$saAddress){
if ($gbhash1.ContainsKey($saAddress)){
$tsize = [int]$gbhash2[$saAddress] + [int]$svSizeVal
$tnum = [int]$gbhash1[$saAddress] + 1
$gbhash1[$saAddress] = $tnum
$gbhash2[$saAddress] = $tsize
}
else{
$gbhash1.Add($saAddress,1)
$gbhash2.Add($saAddress,$svSizeVal)
}

}

function GetEmailDomains{

$reRootDse = New-Object System.DirectoryServices.DirectoryEntry("LDAP://rootDSE")

$cfConfigroot = New-Object directoryservices.directoryentry("LDAP://" + $reRootDse.configurationnamingcontext)

$dsSearcher = New-Object directoryservices.directorySearcher($cfConfigroot)
$dsSearcher.PropertiesToLoad.Add("gatewayProxy")
$dsSearcher.filter = "(objectCategory=msExchRecipientPolicy)"
$rsResults = $dsSearcher.findall()
foreach ($rsResult in $rsResults) {
$rpProps = $rsResult.properties
foreach ($eaEmailAddress in $rpProps.gatewayproxy){
if ($eaEmailAddress.ToLower().indexofany("smtp:") -eq 0){
$arEmailAddress = $eaEmailAddress.split("@")
if ($adhash.ContainsKey($arEmailAddress[1])){}
else {$adhash.Add($arEmailAddress[1],1)}
}
}
}
}

function adddata{

$adhash.Clear()
$ssTable.Clear()
$gsTable.Clear()
$dchash.Clear()
$gbhash1.Clear()
$gbhash2.Clear()
$servername = $snServerNameTextBox.text
$extrasettings = CheckExtraSettings
$inIncludeit = 0

$dtQueryDT = New-Object System.DateTime
$dpTimeFrom.value.year,$dpTimeFrom.value.month,
$dpTimeFrom.value.day,$dpTimeFrom2.value.hour,
$dpTimeFrom2.value.minute,$dpTimeFrom2.value.second
$dtQueryDTf = New-Object System.DateTime
$dpTimeFrom1.value.year,$dpTimeFrom1.value.month,
$dpTimeFrom1.value.day,$dpTimeFrom3.value.hour,
$dpTimeFrom3.value.minute,$dpTimeFrom3.value.second
$WmidtQueryDT =
[System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($dtQueryDT.ToUniversalTime())
$WmidtQueryDTf =
[System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($dtQueryDTf.ToUniversalTime())
$WmiNamespace = "ROOT\MicrosoftExchangev2"

$filterblock1 = "OriginationTime <= '" + $WmidtQueryDTf + "' and OriginationTime
>= '" + $WmidtQueryDT + "' and entrytype = '1028'"
$filterblock2 = "OriginationTime <= '" + $WmidtQueryDTf + "' and OriginationTime
>= '" + $WmidtQueryDT + "' and entrytype = '1020'"

if ($seSizeCheck.Checked -eq $true){
$schfor = $seSizeCheckNum.value*1024
$filterblock1 = $filterblock1 + " and Size > '" + $schfor.ToString(0.00) + "'"

$filterblock2 = $filterblock2 + " and Size > '" + $schfor.ToString(0.00) + "'"
}

if ($snSenderAddressTextBox.text -ne ""){
$filterblock1 = $filterblock1 + " and SenderAddress = '" +
$snSenderAddressTextBox.text + "'"
$filterblock2 = $filterblock2 + " and SenderAddress = '" +
$snSenderAddressTextBox.text + "'"
}

$filter = $filterblock1 + " or " + $filterblock2
if ($etTypeCheck.Checked -eq $true -bor $edTypeCheck.Checked -eq
$true){GetEmailDomains}
if ($snRecipientAddressTextBox.text -eq ""){
get-wmiobject -class Exchange_MessageTrackingEntry -Namespace $WmiNamespace
-ComputerName $servername -filter $filter | ForEach-Object{
if ($etTypeCheck.Checked -eq $true){
$inInternal = 0
$inIncludeit = 0
$rmRecpMatch = 1
$senddomarray1 = $_.SenderAddress.split("@")
foreach($recp in $_.RecipientAddress){
$recpdomarray1 =$recp.split("@")
if ($adhash.ContainsKey($recpdomarray1[1])){}
else {$rmRecpMatch = 0}
}
if ($senddomarray1.length -ne 1){
if ($adhash.ContainsKey($senddomarray1[1]) -band $rmRecpMatch -eq 1){$inInternal
= 1}}
if ($etTypeCheckDrop.SelectedItem -eq "Internal" -band $inInternal -eq 1){
$inIncludeit = 1
}
if ($etTypeCheckDrop.SelectedItem -eq "External" -band $inInternal -eq 0){
$inIncludeit = 1
}
}
else {$inIncludeit = 1}
if ($edTypeCheck.Checked -eq $true -band $inIncludeit -eq 1){
$issentex = 1
$senddomarray1 = $_.SenderAddress.split("@")
if ($senddomarray1.length -ne 1){
if ($adhash.ContainsKey($senddomarray1[1])){
$issentex = 0
}
}
if ($edTypeCheckDrop.SelectedItem -eq "Sent" -band $issentex -eq 1){$inIncludeit
= 0}
if ($edTypeCheckDrop.SelectedItem -eq "Recieved" -band $issentex -eq
0){$inIncludeit = 0}

}
if ($inIncludeit -eq 1){
$recpval = ""
foreach($recp in $_.RecipientAddress){
if ($recpval -eq ""){$recpval = $recp}
else {$recpval = $recpval + ";" + $recp}}

if($dchash.ContainsKey($_.MessageID)){}
else{
$dchash.Add($_.MessageID,1)
$svSizeVal = $_.size/1024
if ($extrasettings -eq 0){
$ssTable.Rows.Add([System.Management.ManagementDateTimeConverter]::ToDateTime($_.OriginationTime),$_.SenderAddress,$recpval,$_.subject,[int]$svSizeVal.ToString(0.00))
}
if ($GroupbySender.Checked -eq $true -bor $GroupBySenderDomain.Checked -eq $true
){
if ($GroupbySender.Checked -eq $true){
AggResults($_.SenderAddress)
}
else{
$senddomarray = $_.SenderAddress.split("@")
AggResults($senddomarray[1])
}

}
if ($GroupByReciever.Checked -eq $true -bor $GroupByRecieverDomain.Checked -eq
$true ){
foreach($recp in $_.RecipientAddress){
if ($GroupByReciever.Checked -eq $true){
AggResults($recp)
}
else{
$recpdomarray = $recp.split("@")
AggResults($recpdomarray[1])
}
}
}
if ($GroupByDate.Checked -eq $true){
$dateag =
[System.Management.ManagementDateTimeConverter]::ToDateTime($_.OriginationTime)
AggResults($dateag.ToShortDateString())
}
}
}
}
}
else{
get-wmiobject -class Exchange_MessageTrackingEntry -Namespace $WmiNamespace
-ComputerName $servername -filter $filter | where-object {$_.RecipientAddress
-eq $snRecipientAddressTextBox.text} | ForEach-Object{
if ($etTypeCheck.Checked -eq $true){
$inInternal = 0
$inIncludeit = 0
$rmRecpMatch = 1
$senddomarray1 = $_.SenderAddress.split("@")
foreach($recp in $_.RecipientAddress){
$recpdomarray1 =$recp.split("@")
if ($adhash.ContainsKey($recpdomarray1[1])){}
else {$rmRecpMatch = 0}
}
if ($senddomarray1.length -ne 1){
if ($adhash.ContainsKey($senddomarray1[1]) -band $rmRecpMatch -eq 1){$inInternal
= 1}}
if ($etTypeCheckDrop.SelectedItem -eq "Internal" -band $inInternal -eq 1){
$inIncludeit = 1
}
if ($etTypeCheckDrop.SelectedItem -eq "External" -band $inInternal -eq 0){
$inIncludeit = 1
}
}
else {$inIncludeit = 1}
if ($edTypeCheck.Checked -eq $true -band $inIncludeit -eq 1){
$issentex = 1
$senddomarray1 = $_.SenderAddress.split("@")
if ($senddomarray1.length -ne 1){
if ($adhash.ContainsKey($senddomarray1[1])){
$issentex = 0
}
}
if ($edTypeCheckDrop.SelectedItem -eq "Sent" -band $issentex -eq 1){$inIncludeit
= 0}
if ($edTypeCheckDrop.SelectedItem -eq "Recieved" -band $issentex -eq
0){$inIncludeit = 0}

}
if ($inIncludeit -eq 1){
$recpval = ""
foreach($recp in $_.RecipientAddress){if ($recpval -eq ""){$recpval = $recp}
else {$recpval = $recpval + ";" + $recp}}
if($dchash.ContainsKey($_.MessageID)){}
else{
$dchash.Add($_.MessageID,1)
if ($extrasettings -eq 0){
$svSizeVal = $_.size/1024
$ssTable.Rows.Add([System.Management.ManagementDateTimeConverter]::ToDateTime($_.OriginationTime),$_.SenderAddress,$recpval,$_.subject,[int]$svSizeVal.ToString(0.00))
}
if ($GroupbySender.Checked -eq $true -bor $GroupBySenderDomain.Checked -eq $true
){
if ($GroupbySender.Checked -eq $true){
AggResults($_.SenderAddress)
}
else{
$senddomarray = $_.SenderAddress.split("@")
AggResults($senddomarray[1])
}

}
if ($GroupByReciever.Checked -eq $true -bor $GroupByRecieverDomain.Checked -eq
$true ){
foreach($recp in $_.RecipientAddress){
if ($GroupByReciever.Checked -eq $true){
AggResults($recp)
}
else{
$recpdomarray = $recp.split("@")
AggResults($recpdomarray[1])
}
}
}
if ($GroupByDate.Checked -eq $true){
$dateag =
[System.Management.ManagementDateTimeConverter]::ToDateTime($_.OriginationTime)
AggResults($dateag.ToShortDateString())
}
}}}}

if ($extrasettings -eq 0){$dgDataGrid.DataSource = $ssTable

}
else { foreach ($htent in $gbhash1.keys){
$spemarray = $htent.split("@")
$gsTable.Rows.Add($htent,$spemarray[1],[int]$gbhash1[$htent],[int]$gbhash2[$htent])
}
$dgDataGrid.DataSource = $gsTable
}}

function Exportcsv{

$exFileName = new-object System.Windows.Forms.saveFileDialog
$exFileName.DefaultExt = "csv"
$exFileName.Filter = "csv files (*.csv)|*.csv"
$exFileName.InitialDirectory = "c:\temp"
$exFileName.ShowDialog()
if ($exFileName.FileName -ne ""){
$logfile = new-object IO.StreamWriter($exFileName.FileName,$true)
$logfile.WriteLine("OriginationTime,SenderAddress,RecipientAddress,Subject,Size")
foreach($row in $ssTable.Rows){
$logfile.WriteLine($row[0].ToString() + "," + $row[1].ToString() + "," +
$row[2].ToString() + ",`"" + $row[3].ToString() + "`"," + $row[4].ToString())
}
$logfile.Close()
}
}

$Dataset = New-Object System.Data.DataSet
$ssTable = New-Object System.Data.DataTable
$ssTable.TableName = "TrackingLogs"
$ssTable.Columns.Add("Origination Time",[DateTime])
$ssTable.Columns.Add("SenderAddress")
$ssTable.Columns.Add("RecipientAddress")
$ssTable.Columns.Add("Subject")
$ssTable.Columns.Add("Size (KB)",[int])
$Dataset.tables.add($ssTable)
$gsTable = New-Object System.Data.DataTable
$gsTable.TableName = "Grouped-TrackingLogs-Sender"
$gsTable.Columns.Add("EmailAddress")
$gsTable.Columns.Add("Domain")
$gsTable.Columns.Add("Number_Messages",[int])
$gsTable.Columns.Add("Size (KB)",[int])
$Dataset.tables.add($gsTable)
Note ** Code abreviated for size full code availible in the download http://msgdev.mvps.org/exdevblog/mtrackv1.zip