Saturday, December 20, 2008

FreeBusy In/Out Board for Exchange 2007 using EWS and Powershell

Update version see here

Firstly this isn't a new idea there's been a similar script on CDO live http://www.cdolive.com/inoutboard.htm for years that uses CDO 1.2. I've had a couple of people ask about doing this in Powershell and EWS using the new availability service on 2007 so here it is.

How does it work

To get the Free/Busy status of another mailbox in Exchange you need to make use of the getuseravailability operation in EWS. To be able to query another users availability you must have been granted rights to the free/busy information for that users Mailbox by default the default ACL gives users the right to see Free/Busy availability but not the newer Subject and Location information made available in 2007. To change the default Free/Busy Permission you can use something like this post A more granular approach would be to assign these rights to a specific user or group eg something like this would add a group to calendar if it didn't exist


[void][Reflection.Assembly]::LoadFile("c:\temp\EWSUtil.dll")
$mbMailboxEmail = "krudd@domain.com"
$GrouptoAdd = "DefaultPermsGroup@domain.com"
$exists = $false
$calutil = new-object EWSUtil.CalendarUtil($mbMailboxEmail,$false, "username","password","domain",$null)
for ($cpint=0;$cpint -lt $calutil.CalendarDACL.Count; $cpint++){
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUserSpecified -ne $true){
$sidbind = "LDAP://&ltsid=">"
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
if ($aceuser.objectClass[1] -eq "group"){
if ($aceuser.mail -eq $GrouptoAdd){$exists = $true}
}
}
}
if($exists -eq $false){
"Group Doesn't Exist Adding"
$calutil.CalendarDACL.Add($calutil.Reviewer($GrouptoAdd))
$calutil.FreeBusyDACL.Add($calutil.FolderReviewer($GrouptoAdd))
$calutil.Update()
}
else{
"Group Exists Do Nothing"
}



Okay once you have the permissions sorted you can look at actually querying the free/busy information. To do this I've wrapped a few methods that do this using some EWS code in my EWSUtil powershell library so once you've made a connection to the mailbox all you need is to fire the method with firstly an array of mailbox email addresses you want to include in you FB board the duration which consists of the start and end time and the last parameter is the granularity for the Free/Busy time slot eg 60,30 or 15 minutes. So a example of this if you wanted to produce a freeBusy board that create a board for 8:30 to 5:00 with a 30 minute slot period you would need something like this

$drDuration = new-object EWSUtil.EWS.Duration
$drDuration.StartTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 08:30"))
$drDuration.EndTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 17:30"))
$fbents = $ewc.GetAvailiblity($mbs, $drDuration, 30)

This would produce something that look like this

Actually thats jumping ahead a little we also need some code to generate a String array of Mailbox email addressses to use with the GetAvailiblity method. There are several methods to do this using a custom ADSI query, Group Membership or using Get-Mailbox with a specific filter. The limitaiton here is that you need to limit that array you feed into the method to no more then 100 mailboxes. An example of using Get-Mailbox to build an array would look like

$mbHash = @{ }

get-mailbox | foreach-object{
if ($mbHash.ContainsKey($_.WindowsEmailAddress.ToString()) -eq $false){
$mbHash.Add($_.WindowsEmailAddress.ToString(),$_.DisplayName)
}
}
$mbs = @()
foreach($key in $mbHash.keys){
$mbs += $key
}

The second hashtable I use later to map email address's back to display names.

What the method returns is a nested hashtable for the slots you requested with the Subject and Location information indexed to each of the time slot. I've used the Time and Location information as Mouse over titles in the above board so for example when you mouse over a cetain table cell you will get information about that segment eg


Okay thats it some relatively simple powershell code does the color mapping and exporting to a Html file. I've put a download of the script here. To use this script you need to use the latest EWSUtil Powershell library which you can download from http://msgdev.mvps.org/exdevblog/ewsutil.zip. For more information on using the library and connection and authentication options this is documented in another post. The code itself looks like

[void][Reflection.Assembly]::LoadFile("C:\temp\EWSUtil.dll")

$casUrl = "https://servername/ews/exchange.asmx"
$mbHash = @{ }

get-mailbox | foreach-object{
if ($mbHash.ContainsKey($_.WindowsEmailAddress.ToString()) -eq $false){
$mbHash.Add($_.WindowsEmailAddress.ToString(),$_.DisplayName)
}
}
$mbs = @()
foreach($key in $mbHash.keys){
$mbs += $key
}

$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$false, "usernames", "password", "domain",$casUrl)
$drDuration = new-object EWSUtil.EWS.Duration
$drDuration.StartTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 08:30"))
$drDuration.EndTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 17:30"))
$fbents = $ewc.GetAvailiblity($mbs, $drDuration, 30)
$frow = $true
foreach($key in $fbents.keys){
if ($frow -eq $true){
$fbBoard = $fbBoard + "<table><tr bgcolor=`"#95aedc`">" +"`r`n"
$fbBoard = $fbBoard + "<td align=`"center`" style=`"width=200;`" ><b>User</b></td>" +"`r`n"
for($stime = $drDuration.StartTime;$stime -lt $drDuration.EndTime;$stime = $stime.AddMinutes(30)){
$fbBoard = $fbBoard + "<td align=`"center`" style=`"width=50;`" ><b>" + $stime.ToString("HH:mm") + "</b></td>" +"`r`n"
}
$fbBoard = $fbBoard + "</tr>" + "`r`n"
$frow = $false
}
for($stime = $drDuration.StartTime;$stime -lt $drDuration.EndTime;$stime = $stime.AddMinutes(30)){
$valuehash = $fbents[$key]
if ($stime -eq $drDuration.StartTime){
$fbBoard = $fbBoard + "<td bgcolor=`"#CFECEC`"><b>" + $mbHash[$valuehash[$stime.ToString("HH:mm")].MailboxEmailAddress.ToString()] + "</b></td>" + "`r`n"
}
switch($valuehash[$stime.ToString("HH:mm")].FBStatus.ToString()){
"0" {$bgColour = "bgcolor=`"#41A317`""}
"1" {$bgColour = "bgcolor=`"#52F3FF`""}
"2" {$bgColour = "bgcolor=`"#153E7E`""}
"3" {$bgColour = "bgcolor=`"#4E387E`""}
"4" {$bgColour = "bgcolor=`"#98AFC7`""}
"N/A" {$bgColour = "bgcolor=`"#98AFC7`""}
}
$title = "title="
if ($valuehash[$stime.ToString("HH:mm")].FBSubject -ne $null){
if ($valuehash[$stime.ToString("HH:mm")].FBLocation -ne $null){
$title = $title + "`"" + $valuehash[$stime.ToString("HH:mm")].FBSubject.ToString() + " " + $valuehash[$stime.ToString("HH:mm")].FBLocation.ToString() + "`" "
}
else {
$title = $title + "`"" + $valuehash[$stime.ToString("HH:mm")].FBSubject.ToString() + "`" "
}
}
else {
if ($valuehash[$stime.ToString("HH:mm")].FBLocation -ne $null){
$title = $title + "`"" + $valuehash[$stime.ToString("HH:mm")].FBLocation.ToString() + "`" "
}
}
if($title -ne "title="){
$fbBoard = $fbBoard + "<td " + $bgColour + " " + $title + "></td>" + "`r`n"
}
else{
$fbBoard = $fbBoard + "<td " + $bgColour + "></td>" + "`r`n"
}

}
$fbBoard = $fbBoard + "</tr>" + "`r`n"

}
$fbBoard = $fbBoard + "</table>" + " "
$fbBoard | out-file "c:\fbboard.htm"

Sunday, December 07, 2008

Importing and Synchronizing Contacts from a CSV (Outlook Express Export) into Exchange 2007 (private Contacts Folder or Public Contacts Folder) using

Following on from last week I’m going to put the Sync function I created into use and also show you how to create a contact in Powershell with a few lines of code using some wrappered EWS code I’ve put into my EWSUtil powershell library. I’ve also include some code to create a grouped list of contacst flagged with a custom property to allow full sync eg create if don’t exist create , delete if deleted from the syncfile and do property level changes if phone, address of name information is modified. I’ve used a csv file generated by doing an export in Outlook Express but this could be any csv file from any source.
Before we can use this process we need to set the target folder to where the contacts will be imported to or where the contacts we are going to sync with are located. For a local users contact folder something like this is needed.
$lcLocalContactFolderid = new-object EWSUtil.EWS.DistinguishedFolderIdType$lcLocalContactFolderid.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::contacts
$mbMailbox = new-object EWSUtil.EWS.EmailAddressType
$mbMailbox.EmailAddress = “
user@domain.com
$dTypeFld.Mailbox = $mbMailbox
$lcLocalContacts = $ewc.getfolder($lcLocalContactFolderid)
If you want to target a Public folder it’s not just as easy as putting in the path to the folder we need to get the code to traverse that folder path and find the target folder. The method I use is to input a normal folder path the split this into an array and then starting at the root use a separate find folder on each parent folder in the dimensioning hierarchy this is a little hard to explain but the code explains it better.
$pfPublicFolderPath = "/PubContacts/SyncContacts"
$parentFolder = new-object EWSUtil.EWS.DistinguishedFolderIdType
$parentFolder.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::publicfoldersroot
$fldarry = new-object EWSUtil.EWS.BaseFolderIdType[] 1
$fldarry[0] = $parentFolder
$rfRootFolder = $ewc.getFolder($fldarry)
$pfArray = $pfPublicFolderPath.Split("/")
$cfContactsFolder = $rfRootFolder[0]
for ($lint = 1; $lint -lt $pfArray.Length; $lint++) {
$cfContactsFolder = $ewc.FindSubFolder($cfContactsFolder, $pfArray[$lint]);
}
Once we have our target folder we can do a number of things before going into synchronization I want to show some easy code for creating contacts using EWS from Powershell. To create a contact we really only need a csv file with 2 columns one for the name and one for the emailAddress eg
Name,EmailAddress
Fred FlintStone,Fred@slate.com
Barney Rubble,barney@slate.com
If you have a csv file that you want to import into a local users contacts folder first we need to create an Exchange Web Services Connection so we can use EWS and get the target folder as I’ve talked about above. Once this is done you can then read the csv file using the Import-csv powershell cmdlet. We then loop through each line of the csv file and create contacts from the information within. When creating a contact there are several properties you do need to set such as some Extended mapi properties that aren’t included in the EWS contactType class. These represent the displayName for emailaddress's also you need to make sure you set the FileAs and Subject properties so the contact will display correctly in Outlook and the address book. So the simplest code to create contacts from a 2 column csv file would look like (I’ve omitted the connect and folder find functions which I’ll include in the download im also splitting the name into to an array to fill in the firstname and surname properties).
import-csv $fnFileName | foreach-object{
$newcontact = new-object EWSUtil.EWS.ContactItemType
$namearray = $_.Name.split(" ")
$newcontact.GivenName = $namearray[0]
$newcontact.Surname = $namearray[1]
$newcontact.Subject = $_.Name
$newcontact.FileAs = $_.Name
$newcontact.DisplayName = $_.Name
$newcontact.EmailAddresses = new-object EWSUtil.EWS.EmailAddressDictionaryEntryType[] 1
$newcontact.EmailAddresses[0] = new-object EWSUtil.EWS.EmailAddressDictionaryEntryType
$newcontact.EmailAddresses[0].Key = [EWSUtil.EWS.EmailAddressKeyType]::EmailAddress1
$newcontact.EmailAddresses[0].Value = $_.EmailAddress

$dnEmailDisplayName1 = new-object EWSUtil.EWS.PathToExtendedFieldType
$dnEmailDisplayName1.DistinguishedPropertySetIdSpecified = $true
$dnEmailDisplayName1.DistinguishedPropertySetId = [EWSUtil.EWS.DistinguishedPropertySetType]::Address
$dnEmailDisplayName1.PropertyId = 32896
$dnEmailDisplayName1.PropertyIdSpecified = $true
$dnEmailDisplayName1.PropertyType = [EWSUtil.EWS.MapiPropertyTypeType]::String

$daEmailDisplayName1 = new-object EWSUtil.EWS.PathToExtendedFieldType
$daEmailDisplayName1.DistinguishedPropertySetIdSpecified = $true
$daEmailDisplayName1.DistinguishedPropertySetId = [EWSUtil.EWS.DistinguishedPropertySetType]::Address
$daEmailDisplayName1.PropertyId = 32900
$daEmailDisplayName1.PropertyIdSpecified = $true;
$daEmailDisplayName1.PropertyType = [EWSUtil.EWS.MapiPropertyTypeType]::String

$atEmailAddressType1 = new-object EWSUtil.EWS.PathToExtendedFieldType
$atEmailAddressType1.DistinguishedPropertySetIdSpecified = $true
$atEmailAddressType1.DistinguishedPropertySetId = [EWSUtil.EWS.DistinguishedPropertySetType]::Address
$atEmailAddressType1.PropertyId = 32898
$atEmailAddressType1.PropertyIdSpecified = $true;
$atEmailAddressType1.PropertyType = [EWSUtil.EWS.MapiPropertyTypeType]::String

$newcontact.ExtendedProperty = new-object EWSUtil.EWS.ExtendedPropertyType[] 3
$newcontact.ExtendedProperty[0] = new-object EWSUtil.EWS.ExtendedPropertyType
$newcontact.ExtendedProperty[0].ExtendedFieldURI = $dnEmailDisplayName1
$newcontact.ExtendedProperty[0].Item = $_.Name + " (" + $_.EmailAddress + ")"

$newcontact.ExtendedProperty[1] = new-object EWSUtil.EWS.ExtendedPropertyType
$newcontact.ExtendedProperty[1].ExtendedFieldURI = $daEmailDisplayName1
$newcontact.ExtendedProperty[1].Item = $_.EmailAddress

$newcontact.ExtendedProperty[2] = new-object EWSUtil.EWS.ExtendedPropertyType
$newcontact.ExtendedProperty[2].ExtendedFieldURI = $atEmailAddressType1
$newcontact.ExtendedProperty[2].Item = "SMTP"

$_.EmailAddress

$ewc.AddNewContact($newcontact,$lcLocalContacts[0].FolderId)

}
Okay now we can create contacts syncronizing is just matter of using the same logic and adding some code that before creating the contacts checks to see if it exists and if it does then does a property level check of the contact and then updates any information that has changed or does nothing at all. To facilite this i've made a few changes to the code above first i've added a custom Mapi property to mark any contacts that are created with this process. then when it comes time to syncronise i have a function that first finds all the contacts that this process has created when (or if) it had run before. This function returns a Hashtable indexed by the Email address so it makes it easy to check if the contact has already been created and determines if the sync needs to be run for changed properties. On the back side contacts can also be deleted if they no longer exist in the file
So the Full sync,add,delete logic would look like
$_."E-mail Address"
if ($contactsHash.containskey($_."E-mail Address")){
"Address Exist Test Sync"
$dsContact = $contactsHash[$_."E-mail Address"]
$diffs = $ewc.DiffConact($newcontact,$dsContact)
if ($diffs.Count -ne 0){
"Number Changes Found " + $diffs.Count
[VOID]$ewc.UpdateContact($diffs,$dsContact.ItemId)
}

}else{
"CreateContact"
$ewc.AddNewContact($newcontact,$cfContactsFolder.FolderId )
}

}
$diDelCollection = @()
$delitems = $false
foreach ($key in $contactsHash.Keys){
if ($fileContactsHash.ContainsKey($key) -eq $false){
$diDelCollection += $contactsHash[$key]
$delitems = $true

}
}
if ($delitems -eq $true){
$delbis = new-object EWSUtil.EWS.BaseItemIdType[] $diDelCollection.length
for($delint=0;$delint -lt [Int]$diDelCollection.length;$delint++){
$diDelCollection[$delint].Subject.ToString()
$delbis[$delint] = $diDelCollection[$delint].ItemId}

$delbis
$ewc.DeleteItems($delbis,[EWSUtil.EWS.DisposalType]::SoftDelete)
"Items Deleted"
}
In the download i've create a sample that uses an Outlook Express Address Book csv Export with all the fields ticked that imports and syncronzes with a public Contacts folders as well as the Local contacts import sample I've talked about. I've put the download here. To use these scrripts you need to use the latest EWSUtil Powershell library which you can download from https://github.com/gscales/Powershell-Scripts/raw/master/ScriptArchive/.
For more information on using the library and connection and authentication options this is documented in another post

Sunday, November 30, 2008

Syncronizing contacts in Exchange 2007 with other things - helper class

Copying a Contact in Exchange is one thing synchronizing is another this can be one of the great contradictions and issues that faces anybody looking at synchronizing contacts between different storage mediums. Whether that be a Database, CSV file or other Mail System underlying its the same information stored and accessed in a different way. Because Exchange isn't your normal flat file database more a relatively complex property store that uses some complex datatypes doing property level synchronization does provide a unique set of challenges. Exchange Web Service presents some of these properties in more workable format but apart from this doesn't give a lot of help is solving this fundamental issue. The SyncFolderItem and notification operations only provide an Item level ability to spot changes on an item so the brave souls who want to embark on a greater level of synchronization must take this challenge unto themselves.

If your going to compare two contacts it first helps to have them in the same format there are a few methods of doing this one way you might go about this if your using Exchange 2007 and SQL 2005 is using something like http://msdn.microsoft.com/en-gb/library/bb508823.aspx. The path I've gone down it to convert them into the ContactType class that's used in EWS. This may or may not be the best method but it does have a flexibility that I'm after. With flexibility comes a great ability to do some really cool things.

So what I've come up with is a method that takes two ContactType objects as parameters and then returns a generic list of SetItemFieldType updates that can then be used direct in a UpdateItem Operation. With EWS you need to have a separate SetItemFieldType for each property of an item you want to update. If you have a large number of properties you are updating on an Item especially if they are different datatypes then these requests can get lengthy and complex. The first contact object represents the source object which has the new information you want synchronized and the second is destination object which you want updated. The changes that are returned are the differences on the source object when compared against the destination object. Because Contacts are made up of complex datatypes the compare class needs to deal differently depending on which property you are looking at.

String Properties:
For string properties a simple comparison is done to work out if one property is different from the other.

String Arrays: For String arrays the elements of the arrays are joined and then the strings of the joined array is compared.

Email Addresses, Phone Numbers, Street Addresses: These are special indexed arrays so to compare the index valued two hashtables are used. The Hashtables allow the ability to do string comparison of the values based on the on the indexed values. With Street Addresses because of the way the indexing works with these properties nested hash tables are used.

Extended Properties:Extended Mapi properties are one of the things that make objects in Exchange hard to deal with. The class will go through any that are set in the source contact and that are retrieved in the destination object and and will do a comparison based on either the propertyID or propertyTag. Because there is no real good method to get all the Extended properties that have been set on a item unless you use ExMapi this is one area where you can and will loose fidelity on items you might be synchronizing because each of these properties must be explicitly specified if you want to synchronize them.

Because the SetItemUpdate will be different for each of these types there is a separate method that handles building the update.

Whats missing - This helper class will look for whats been added or changed in a contact and produce an update for those properties. But it wont produce
DeleteItemFieldType to delete properties that may have been removed in the source contact.

How to use this : Watch this space i'll have a few sample over the next couple of weeks on how to use this but i just wanted to introduce the code in a seperate post. I've put a download of the code I've talked about here the actual code is too large to post.







Sunday, November 16, 2008

Find Unused Mailbox Powershell Gui Exchange 2007


Finding unused mailboxes is one of those mind numbing tasks a Sys Admin must perform routinely in Large Exchange organizations. Many of the scripts on this blog help to show ways to tackle different issues around unused or disabled mailboxes such as removing disabled users accounts from groups or finding broken delegate forwarding rules etc. I've also posted a few scripts before that showed some methods to track down unused mailboxes by looking at the number of unread email and the last time a mailbox sent a message. Well this script puts some of these methods together in a powershell GUI script that uses Exchange Web Services and some Exchange Management Shell cmdlets to look at all mailboxes on a server and show us information about when a mailbox was logged into, how big it is, how many unread email there is and when the last sent and/or received email was. It presents all this information back to you as a GUI with data grid and also has a button that allows exporting the result to a CSV file.

How it works?

To get the information about when the mailbox was last logged onto the EMS cmdlet get-mailbox is used and also get-Mailboxstatistics is also used to get the mailbox size in MB. To get the number of unread email in a mailbox and to get the last time a mail was sent from the sent items folder Exchange Web Services is used to query the mailbox. To do this from Powershell and the EMS I’ve used my EWSUtil library which has some routine in there that will perform a finditem operation with the nessasary restrictions so it will only show Unread messages. The library will handle Autodiscover and allow for both delegate and impersonation access method. The Form allows for all of these combinations and also allows you to specify overrides for both authentication and the URL for the CAS server to use (you need to use the full url to the EWS if you going to use eg https://servername/ews/exchange.asmx). The rest of the script just builds the form elements for the GUI and adds some simple function that does a export of the data shown in the GRID.

To use this script you need to have the EwsUtil.dll in c:\temp you can download the library from https://github.com/gscales/Powershell-Scripts/raw/master/ScriptArchive/ewsutil.zip. You can download the actual script from here I've include a few other scripts in the download that show simpler examples of read the unread email from mailboxes as well (Just for fun). I've put the download of the script here the Code itself looks like


[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$form = new-object System.Windows.Forms.form
[void][Reflection.Assembly]::LoadFile("C:\temp\EWSUtil.dll")
$mbcombCollection = @()
$Emailhash = @{ }

function ExportGrid{

$exFileName = new-object System.Windows.Forms.saveFileDialog
$exFileName.DefaultExt = "csv"
$exFileName.Filter = "csv files (*.csv)|*.csv"
$exFileName.InitialDirectory = "c:\temp"
$exFileName.Showhelp = $true
$exFileName.ShowDialog()
if ($exFileName.FileName -ne ""){
$logfile = new-object IO.StreamWriter($exFileName.FileName,$true)
$logfile.WriteLine("UserName,EmailAddress,Last_Logon,MailboxSize,
Inbox_Number_Unread,Inbox_Unread_LastRecieved,Sent_Items_LastSent")
foreach($row in $msTable.Rows){
$logfile.WriteLine("`"" + $row[0].ToString() + "`"," + $row[1].ToString() + "," + $row[2].ToString() + "," + $row[3].ToString() + "," + $row[4].ToString() + "," + $row[5].ToString() + "," + $row[6].ToString())
}
$logfile.Close()
}

}

function Getinfo(){

$mbcombCollection = @()
$Emailhash.clear()
$msTable.clear()

Get-Mailbox -server $snServerNameDrop.SelectedItem.ToString() -ResultSize Unlimited | foreach-object{
if ($Emailhash.containskey($_.ExchangeGuid) -eq $false){
$Emailhash.add($_.ExchangeGuid.ToString(),$_.windowsEmailAddress.ToString())
}
}

get-mailboxstatistics -server $snServerNameDrop.SelectedItem.ToString() | foreach-object{
$mbcomb = "" | select DisplayName,EmailAddress,Last_Logon,MailboxSize,
Inbox_Number_Unread,Inbox_Unread_LastRecieved,Sent_Items_LastSent
$mbcomb.DisplayName = $_.DisplayName.ToString()
if ($Emailhash.ContainsKey($_.MailboxGUID.ToString())){
$mbcomb.EmailAddress = $Emailhash[$_.MailboxGUID.ToString()]
}
if ($_.LastLogonTime -ne $null){
$mbcomb.Last_Logon = $_.LastLogonTime.ToString()
}
$mbcomb.MailboxSize = $_.TotalItemSize.Value.ToMB()

"Mailbox : " + $mbcomb.EmailAddress
if ($mbcomb.EmailAddress -ne $null){
$mbMailboxEmail = $mbcomb.EmailAddress
if ($unCASUrlTextBox.text -eq ""){ $casurl = $null}
else { $casurl= $unCASUrlTextBox.text}
$useImp = $false
if ($seImpersonationCheck.Checked -eq $true) {
$useImp = $true
}
if ($seAuthCheck.Checked -eq $true) {
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$useImp, $unUserNameTextBox.Text, $unPasswordTextBox.Text, $unDomainTextBox.Text,$casUrl)
}
else{
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$useImp, "", "", "",$casUrl)
}


$drDuration = new-object EWSUtil.EWS.Duration
$drDuration.StartTime = [DateTime]::UtcNow.AddDays(-365)
$drDuration.EndTime = [DateTime]::UtcNow

$dTypeFld = new-object EWSUtil.EWS.DistinguishedFolderIdType
$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::inbox
$dTypeFld2 = new-object EWSUtil.EWS.DistinguishedFolderIdType
$dTypeFld2.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::sentitems

$mbMailbox = new-object EWSUtil.EWS.EmailAddressType
$mbMailbox.EmailAddress = $mbMailboxEmail
$dTypeFld.Mailbox = $mbMailbox
$dTypeFld2.Mailbox = $mbMailbox

$fldarry = new-object EWSUtil.EWS.BaseFolderIdType[] 1
$fldarry[0] = $dTypeFld
$msgList = $ewc.FindUnread($fldarry, $drDuration, $null, "")
$mbcomb.Inbox_Number_Unread = $msgList.Count
if ($msgList.Count -ne 0){
$mbcomb.Inbox_Unread_LastRecieved = $msgList[0].DateTimeSent.ToLocalTime().ToString()
}

$fldarry = new-object EWSUtil.EWS.BaseFolderIdType[] 1
$fldarry[0] = $dTypeFld2
$msgList = $ewc.FindItems($fldarry, $drDuration, $null, "")
if ($msgList.Count -ne 0){
$mbcomb.Sent_Items_LastSent = $msgList[0].DateTimeSent.ToLocalTime().ToString()
}
$msTable.Rows.add($mbcomb.DisplayName,$mbcomb.EmailAddress,$mbcomb.Last_Logon,
$mbcomb.MailboxSize,$mbcomb.Inbox_Number_Unread,
$mbcomb.Inbox_Unread_LastRecieved,$mbcomb.Sent_Items_LastSent)
$mbcombCollection += $mbcomb}
}
$dgDataGrid.DataSource = $msTable
}

$msTable = New-Object System.Data.DataTable
$msTable.TableName = "Mailbox Info"
$msTable.Columns.Add("UserName")
$msTable.Columns.Add("EmailAddress")
$msTable.Columns.Add("Last Logon Time",[DateTime])
$msTable.Columns.Add("Mailbox Size(MB)",[int64])
$msTable.Columns.Add("Inbox Number Unread",[int64])
$msTable.Columns.Add("Inbox unread Last Recieved",[DateTime])
$msTable.Columns.Add("Sent Item Last Sent",[DateTime])



# Add Server DropLable
$snServerNamelableBox = new-object System.Windows.Forms.Label
$snServerNamelableBox.Location = new-object System.Drawing.Size(10,20)
$snServerNamelableBox.size = new-object System.Drawing.Size(80,20)
$snServerNamelableBox.Text = "ServerName"
$form.Controls.Add($snServerNamelableBox)

# Add Server Drop Down
$snServerNameDrop = new-object System.Windows.Forms.ComboBox
$snServerNameDrop.Location = new-object System.Drawing.Size(90,20)
$snServerNameDrop.Size = new-object System.Drawing.Size(150,30)
get-mailboxserver | ForEach-Object{$snServerNameDrop.Items.Add($_.Name)}
$form.Controls.Add($snServerNameDrop)

# Add Export Grid Button

$exButton2 = new-object System.Windows.Forms.Button
$exButton2.Location = new-object System.Drawing.Size(250,20)
$exButton2.Size = new-object System.Drawing.Size(125,20)
$exButton2.Text = "Execute"
$exButton2.Add_Click({Getinfo})
$form.Controls.Add($exButton2)


# Add Export Grid Button

$exButton1 = new-object System.Windows.Forms.Button
$exButton1.Location = new-object System.Drawing.Size(10,610)
$exButton1.Size = new-object System.Drawing.Size(125,20)
$exButton1.Text = "Export Grid"
$exButton1.Add_Click({ExportGrid})
$form.Controls.Add($exButton1)

# Add Impersonation Clause

$esImpersonationlableBox = new-object System.Windows.Forms.Label
$esImpersonationlableBox.Location = new-object System.Drawing.Size(10,75)
$esImpersonationlableBox.Size = new-object System.Drawing.Size(130,20)
$esImpersonationlableBox.Text = "Use EWS Impersonation"
$form.controls.Add($esImpersonationlableBox)

$seImpersonationCheck = new-object System.Windows.Forms.CheckBox
$seImpersonationCheck.Location = new-object System.Drawing.Size(150,70)
$seImpersonationCheck.Size = new-object System.Drawing.Size(30,25)
$form.controls.Add($seImpersonationCheck)

# Add Auth Clause

$esAuthlableBox = new-object System.Windows.Forms.Label
$esAuthlableBox.Location = new-object System.Drawing.Size(10,105)
$esAuthlableBox.Size = new-object System.Drawing.Size(130,20)
$esAuthlableBox.Text = "Specify Credentials"
$form.controls.Add($esAuthlableBox)

$seAuthCheck = new-object System.Windows.Forms.CheckBox
$seAuthCheck.Location = new-object System.Drawing.Size(140,100)
$seAuthCheck.Size = new-object System.Drawing.Size(30,25)
$seAuthCheck.Add_Click({if ($seAuthCheck.Checked -eq $true){
$unUserNameTextBox.Enabled = $true
$unPasswordTextBox.Enabled = $true
$unDomainTextBox.Enabled = $true
}
else{
$unUserNameTextBox.Enabled = $false
$unPasswordTextBox.Enabled = $false
$unDomainTextBox.Enabled = $false}})
$form.controls.Add($seAuthCheck)

# Add UserName Box
$unUserNameTextBox = new-object System.Windows.Forms.TextBox
$unUserNameTextBox.Location = new-object System.Drawing.Size(230,100)
$unUserNameTextBox.size = new-object System.Drawing.Size(100,20)
$form.controls.Add($unUserNameTextBox)

# Add UserName Lable
$unUserNamelableBox = new-object System.Windows.Forms.Label
$unUserNamelableBox.Location = new-object System.Drawing.Size(170,105)
$unUserNamelableBox.size = new-object System.Drawing.Size(60,20)
$unUserNamelableBox.Text = "UserName"
$unUserNameTextBox.Enabled = $false
$form.controls.Add($unUserNamelableBox)

# Add Password Box
$unPasswordTextBox = new-object System.Windows.Forms.TextBox
$unPasswordTextBox.PasswordChar = "*"
$unPasswordTextBox.Location = new-object System.Drawing.Size(400,100)
$unPasswordTextBox.size = new-object System.Drawing.Size(100,20)
$form.controls.Add($unPasswordTextBox)

# Add Password Lable
$unPasswordlableBox = new-object System.Windows.Forms.Label
$unPasswordlableBox.Location = new-object System.Drawing.Size(340,105)
$unPasswordlableBox.size = new-object System.Drawing.Size(60,20)
$unPasswordlableBox.Text = "Password"
$unPasswordTextBox.Enabled = $false
$form.controls.Add($unPasswordlableBox)

# Add Domain Box
$unDomainTextBox = new-object System.Windows.Forms.TextBox
$unDomainTextBox.Location = new-object System.Drawing.Size(550,100)
$unDomainTextBox.size = new-object System.Drawing.Size(100,20)
$form.controls.Add($unDomainTextBox)

# Add Domain Lable
$unDomainlableBox = new-object System.Windows.Forms.Label
$unDomainlableBox.Location = new-object System.Drawing.Size(510,105)
$unDomainlableBox.size = new-object System.Drawing.Size(50,20)
$unDomainlableBox.Text = "Domain"
$unDomainTextBox.Enabled = $false
$form.controls.Add($unDomainlableBox)


# Add CASUrl Box
$unCASUrlTextBox = new-object System.Windows.Forms.TextBox
$unCASUrlTextBox.Location = new-object System.Drawing.Size(280,75)
$unCASUrlTextBox.size = new-object System.Drawing.Size(400,20)
$unCASUrlTextBox.text = $strRootURI
$form.Controls.Add($unCASUrlTextBox)

# Add CASUrl Lable
$unCASUrllableBox = new-object System.Windows.Forms.Label
$unCASUrllableBox.Location = new-object System.Drawing.Size(200,75)
$unCASUrllableBox.size = new-object System.Drawing.Size(50,20)
$unCASUrllableBox.Text = "CASUrl"
$form.Controls.Add($unCASUrllableBox)

# Add DataGrid View

$dgDataGrid = new-object System.windows.forms.DataGridView
$dgDataGrid.Location = new-object System.Drawing.Size(10,145)
$dgDataGrid.size = new-object System.Drawing.Size(1000,450)
$dgDataGrid.AutoSizeRowsMode = "AllHeaders"
$form.Controls.Add($dgDataGrid)



$form.Text = "Exchange 2007 Unused Mailbox Form"
$form.size = new-object System.Drawing.Size(1200,700)
$form.autoscroll = $true
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

Tuesday, November 04, 2008

Dealing with Non Delivery Reports with Exchange Web Services

If there was one thing i would change if I could redesign email from the ground up it would have to be NDR's. While these delivery reports are functional in what they do from a user and even an administrative prospective they are horribly disfunctional. While there isn't much you can do around the underlying design of NDR's with a little help from your favourite Exchange API there are certain tasks that can be made more livable.

NDR Overload

If your users are sending and receiving a lot of email they you'll probably find that a number of NDR's are being generated for a vast number of different reasons. A lot of the times the loop will get closed where your user having tryed and failed to decipher what the NDR says gives up and calls the helpdesk. If your sending a copy of NDR's to a central mailbox what can be usefull for everybody concerned is to have a digest list of all the NDR's that have been recieved in a certain time frame this can allow you to do some analysis on why these NDR's are being generated in the first place and be a little proactive if you have a problems that a causing these NDR's such as certain DNS records or maybe your server has being blacklisted or just having the ability to have at your finger tips the NDR that a user received without then having to forward it to you.

Using Some Code

Here's the fun part to get a list of NDR's using EWS you need to use a few different operations. The first is you need to use a FindItem operations with a restriction on the MessageClass to limit the items returns to those of type REPORT.IPM.Note.NDR. To get the body of the NDR which will contain the NDR reason a GetItem request that includes the messagebody is nessasary and then the reason can be parsed out of the body its sperated with # characters so it pretty easy. Other important pieces of information can be pulled at the same time like the original subject ,recipient and senders by using the appropriate Extended mapi properties for those items.

To make this a little more flexbile the code i wrote only checks for unread NDR's and then marks them as read once they have been included in the Digest. The Digest mail is then sent using EWS.

Proccessing Attachments on NDR's

If you have attachments on messages that are being sent and then bounced and included in the NDR and you want to process those attachments this can get a little confusing to deal with but its kind of logical when you see it in action. To download any attachment using EWS you need to know the attachmentID. To get an embeeded attachment its no different but to find that attachment you first need to get the orignial message using a GetAttachment request and then go through the attachment collection on that original message and use a Getattachment again from this embeeded message. I include a routine to do this and download any filebased attachments to the C#.

I've put a download of all these functions here some of the code look like

static List GetNDRs(ExchangeServiceBinding esb, BaseFolderIdType[] biArray)
{

List intFiFolderItems = new List();
try
{

BasePathToElementType[] beAdditionproperties = new BasePathToElementType[5];

PathToExtendedFieldType osOriginalSenderEmail = new PathToExtendedFieldType();
osOriginalSenderEmail.PropertyTag = "0x0067";
osOriginalSenderEmail.PropertyType = MapiPropertyTypeType.String;

PathToExtendedFieldType osOriginalSenderAdrType = new PathToExtendedFieldType();
osOriginalSenderAdrType.PropertyTag = "0x0066";
osOriginalSenderAdrType.PropertyType = MapiPropertyTypeType.String;

PathToExtendedFieldType osOriginalSenderName = new PathToExtendedFieldType();
osOriginalSenderName.PropertyTag = "0x005A";
osOriginalSenderName.PropertyType = MapiPropertyTypeType.String;

PathToExtendedFieldType osOriginalSubject = new PathToExtendedFieldType();
osOriginalSubject.PropertyTag = "0x0049";
osOriginalSubject.PropertyType = MapiPropertyTypeType.String;

PathToExtendedFieldType osOriginalRecp = new PathToExtendedFieldType();
osOriginalRecp.PropertyTag = "0x0074";
osOriginalRecp.PropertyType = MapiPropertyTypeType.String;


beAdditionproperties[0] = osOriginalSenderAdrType;
beAdditionproperties[1] = osOriginalSenderEmail;
beAdditionproperties[2] = osOriginalSenderName;
beAdditionproperties[3] = osOriginalRecp;
beAdditionproperties[4] = osOriginalSubject;

try
{
FindItemType fiFindItemRequest = new FindItemType();
fiFindItemRequest.ParentFolderIds = biArray;
fiFindItemRequest.Traversal = ItemQueryTraversalType.Shallow;
ItemResponseShapeType ipItemProperties = new ItemResponseShapeType();
ipItemProperties.BaseShape = DefaultShapeNamesType.AllProperties;
ipItemProperties.AdditionalProperties = beAdditionproperties;
fiFindItemRequest.ItemShape = ipItemProperties;
RestrictionType ffRestriction = new RestrictionType();

IsEqualToType ieToTypeClass = new IsEqualToType();
PathToUnindexedFieldType itItemType = new PathToUnindexedFieldType();
itItemType.FieldURI = UnindexedFieldURIType.itemItemClass;
ieToTypeClass.Item = itItemType;
FieldURIOrConstantType constantType = new FieldURIOrConstantType();
ConstantValueType constantValueType = new ConstantValueType();
constantValueType.Value = "REPORT.IPM.Note.NDR";
constantType.Item = constantValueType;
ieToTypeClass.Item = itItemType;
ieToTypeClass.FieldURIOrConstant = constantType;

IsEqualToType ieToTypeRead = new IsEqualToType();
PathToUnindexedFieldType rsReadStatus = new PathToUnindexedFieldType();
rsReadStatus.FieldURI = UnindexedFieldURIType.messageIsRead;
ieToTypeRead.Item = rsReadStatus;
FieldURIOrConstantType constantType1 = new FieldURIOrConstantType();
ConstantValueType constantValueType1 = new ConstantValueType();
constantValueType1.Value = "0";
constantType1.Item = constantValueType1;
ieToTypeRead.Item = rsReadStatus;
ieToTypeRead.FieldURIOrConstant = constantType1;

AndType raRestictionAnd = new AndType();
raRestictionAnd.Items = new SearchExpressionType[2];
raRestictionAnd.Items[0] = ieToTypeClass;
raRestictionAnd.Items[1] = ieToTypeRead;

ffRestriction.Item = raRestictionAnd;
fiFindItemRequest.Restriction = ffRestriction;

FindItemResponseType frFindItemResponse = esb.FindItem(fiFindItemRequest);
if (frFindItemResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success)
{
foreach (FindItemResponseMessageType firmtMessage in frFindItemResponse.ResponseMessages.Items)
{
Console.WriteLine("Number of Items Found : " + firmtMessage.RootFolder.TotalItemsInView);
if (firmtMessage.RootFolder.TotalItemsInView > 0)
{
foreach (ItemType miMailboxItem in ((ArrayOfRealItemsType)firmtMessage.RootFolder.Item).Items)
{
GetItemType giGetItem = new GetItemType();
giGetItem.ItemIds = new BaseItemIdType[1] { miMailboxItem.ItemId };
giGetItem.ItemShape = new ItemResponseShapeType();
giGetItem.ItemShape.AdditionalProperties = beAdditionproperties;
giGetItem.ItemShape.BaseShape = DefaultShapeNamesType.Default;
giGetItem.ItemShape.BodyType = BodyTypeResponseType.Text;
giGetItem.ItemShape.BodyTypeSpecified = true;
GetItemResponseType giResponse = esb.GetItem(giGetItem);
if (giResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Error)
{
Console.WriteLine("Error Occured");
Console.WriteLine(giResponse.ResponseMessages.Items[0].MessageText);
}
else
{
ItemInfoResponseMessageType rmResponseMessage = giResponse.ResponseMessages.Items[0] as ItemInfoResponseMessageType;
intFiFolderItems.Add(rmResponseMessage.Items.Items[0]);
// Create an object of update item type
UpdateItemType updateItemType = new UpdateItemType();
updateItemType.ConflictResolution = ConflictResolutionType.AlwaysOverwrite;
updateItemType.MessageDisposition = MessageDispositionType.SaveOnly;
updateItemType.MessageDispositionSpecified = true;
updateItemType.ItemChanges = new ItemChangeType[1];
ItemChangeType changeType = new ItemChangeType();

changeType.Item = rmResponseMessage.Items.Items[0].ItemId;
changeType.Updates = new ItemChangeDescriptionType[1];

// Create a set item field to identify the type of update
SetItemFieldType setItemEmail = new SetItemFieldType();

PathToUnindexedFieldType epExPath = new PathToUnindexedFieldType();
epExPath.FieldURI = UnindexedFieldURIType.messageIsRead;

MessageType mtMessage = new MessageType();
mtMessage.IsRead = true;
mtMessage.IsReadSpecified = true;

setItemEmail.Item = epExPath;
setItemEmail.Item1 = mtMessage;

changeType.Updates[0] = setItemEmail;
updateItemType.ItemChanges[0] = changeType;
// Send the update item request and receive the response
UpdateItemResponseType updateItemResponse = esb.UpdateItem(updateItemType);
if (updateItemResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success)
{
Console.WriteLine("Update Successful");
}
else
{
Console.WriteLine(updateItemResponse.ResponseMessages.Items[0].MessageText.ToString());
}

}

}
}


}
}
else
{
Console.WriteLine("Error During FindItem request : " + frFindItemResponse.ResponseMessages.Items[0].MessageText.ToString());
}

}
catch (Exception exException)
{
Console.WriteLine(exException.ToString());
// return exException.ToString();
}
return intFiFolderItems;

}
catch (Exception exException)
{
Console.WriteLine(exException.ToString());
// return exException.ToString();
}
return intFiFolderItems;

}

Sunday, October 26, 2008

Vb.NET sample for getting the number of Unread messages from an inbox using Impersonation with Exchange Web Services

Although im not a great user of VB any more there does seem to be a bit of a dirth of samples for those people who haven't made the leap to using C#. So here's something that might fill the void for a few people I've put a donwload of this code here the code itself looks like

Imports ewsvbsamp.ews
Imports System.Net
Imports System.Net.Security
Imports System.Security.Cryptography.X509Certificates
Module Module1

Sub Main()
Console.WriteLine(GetUnreadEmailCount("user@domain.com"))
End Sub
Public Function GetUnreadEmailCount(ByVal emailaddress As String) As Integer
Dim UnreadCount As Integer = 0
ServicePointManager.ServerCertificateValidationCallback = New RemoteCertificateValidationCallback(AddressOf ValidateCertificate)
Dim esb As New ExchangeServiceBinding
esb.RequestServerVersionValue = New RequestServerVersion
esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1
esb.Credentials = New NetworkCredential("user", "password", "domain")
esb.Url = "https://servername/EWS/Exchange.asmx"

Dim csConSid As New ConnectingSIDType
csConSid.PrimarySmtpAddress = emailaddress
Dim exImpersonate As New ExchangeImpersonationType
exImpersonate.ConnectingSID = csConSid
esb.ExchangeImpersonation = exImpersonate

Dim biArray(1) As BaseFolderIdType
Dim dfFolder As New DistinguishedFolderIdType
dfFolder.Id = DistinguishedFolderIdNameType.inbox
biArray(0) = dfFolder
Dim geGetFolder As New GetFolderType
geGetFolder.FolderIds = biArray
geGetFolder.FolderShape = New FolderResponseShapeType
geGetFolder.FolderShape.BaseShape = DefaultShapeNamesType.AllProperties
Dim gfResponse As GetFolderResponseType = esb.GetFolder(geGetFolder)
Dim rmta As ResponseMessageType() = gfResponse.ResponseMessages.Items
Dim rmt As FolderInfoResponseMessageType = DirectCast(rmta(0), FolderInfoResponseMessageType)
If rmt.ResponseClass = ResponseClassType.Success Then
Dim folder As FolderType = DirectCast(rmt.Folders(0), FolderType)
UnreadCount = folder.UnreadCount
End If
Return UnreadCount
End Function

Private Function ValidateCertificate(ByVal sender As Object, ByVal certificate As X509Certificate, ByVal chain As X509Chain, ByVal sslPolicyErrors As SslPolicyErrors) As Boolean
'Return True to force the certificate to be accepted.
Return True
End Function
End Module

Tuesday, October 14, 2008

WebService to release Quarantined messages in Exchange 2007

This post follows on from the quarantine digest message post last month and looks at the challeges around resubmitting a Quarantined message. Outlook 2007 and OWA 2007 have the ability to resend a message from the Quarantine mailbox using the Resend facilities that are documented in http://msdn.microsoft.com/en-us/library/ms527630(EXCHG.10).aspx. Unfortunately you can't do this using Exchange Web Services because EWS doesn't give you full access to to the recipient collection so this means its not possible to set the PR_RECIPIENT_TYPE for each recipient you want the message delivered to and to also mark those message recipients you dont want the message delivered to. One interesting piece of functionality the quarantine mailbox has is the ability to sendas any email address be it Internal or External eg all you need to do is set the following Mapi property on the message your sending and this will become the sender of the message when you send it. Note this will only work for the quarantine mailbox

ExtendedPropertyType osOrginalSender = new ExtendedPropertyType();
PathToExtendedFieldType epExPath = new PathToExtendedFieldType();
epExPath.DistinguishedPropertySetIdSpecified = true;
epExPath.DistinguishedPropertySetId = DistinguishedPropertySetType.PublicStrings;
epExPath.PropertyName = "quarantine-original-sender";
epExPath.PropertyType = MapiPropertyTypeType.String;
osOrginalSender.ExtendedFieldURI = epExPath;
osOrginalSender.Item = "";
Message.ExtendedProperty = new ExtendedPropertyType[1];
Message.ExtendedProperty[0] = osOrginalSender;

Back on track if you want to release a quarantined message you have the option of using Mapi or using the Replay directory on a Hub Role Server. There is a good description of the Replay directory here what makes the replay directory usefull for the task of resubmitting a quarantined message is that it allows specifying X-Sender and X-Reciever to allow you to specify who this message is delievered to ignoring the existing To and From Mail headers. With Quarantined messages the actual message that is quarantined is an attachment of the NDR message that is delivered to the quarantined mailbox. To to be able to submit the message to the replay directory I've come up with a WebService that uses EWS and takes one parameter which is the ItemID of the quarantine report message. It then Grabs this message using EWS looks at the attachments on the message and grabs the original message and then writes X-headers to represent the Sender and Recipients the message is to be delivered to. The messsage is then saved to the replay directory on the Hub Server.

This is still pretty rough and needs more work but this is the skeleton I've put a download here the code looks like.

using System;
using System.Collections.Generic;
using System.Web;
using System.Net;
using System.Web.Services;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using WebService1.ews;
using System.IO;

namespace WebService1
{
///
/// Summary description for Service1
///

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class Service1 : System.Web.Services.WebService
{

[WebMethod]
public string ResubmitMessage(String MessageID)
{
ServicePointManager.ServerCertificateValidationCallback =
delegate(Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
// Replace this line with code to validate server certificate.
return true;
};
string res = "";
ExchangeServiceBinding esb = new ExchangeServiceBinding();
esb.RequestServerVersionValue = new RequestServerVersion();
esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1;
esb.Credentials = new NetworkCredential("user", "password", "domain");
esb.Url = @"https://casserver/EWS/Exchange.asmx";
MessageType[] oMessages = GetQuantined(esb, MessageID);
if (oMessages[0] != null & oMessages[1] != null)
{
byte[] MimeContent = Convert.FromBase64String(oMessages[1].MimeContent.Value);
System.Text.ASCIIEncoding asEncoding = new System.Text.ASCIIEncoding();
String xsXSender = "X-Sender: <" + oMessages[0].ExtendedProperty[0].Item.ToString() + ">";
String xrXreciever = "";
foreach (EmailAddressType recp in oMessages[0].ToRecipients)
{
xrXreciever = xrXreciever + "X-Receiver: <" + recp.EmailAddress + ">" + "\r\n";
}
String emlMessage = xsXSender + "\r\n" + xrXreciever + asEncoding.GetString(MimeContent);
String MessageGuid = Guid.NewGuid().ToString();
MessageGuid = MessageGuid + DateTime.Now.Ticks.ToString();
StreamWriter emlwriter = new StreamWriter((@"C:\Program Files\Microsoft\Exchange Server\TransportRoles\replay\" + MessageGuid + "eml"), true);
emlwriter.WriteLine(emlMessage);
emlwriter.Close();
res = "Email Written to Replay Directory";

}
else {
res = "Error geting message";
}
return res;
}
private MessageType[] GetQuantined(ExchangeServiceBinding esb,String MessageID) {
MessageType[] msMessages = new MessageType[2];
ItemIdType iiItemId = new ItemIdType();
iiItemId.Id = MessageID;
ItemIdType[] giGetItemArray = new ItemIdType[1];
giGetItemArray[0] = iiItemId;
BasePathToElementType[] beAdditionproperteis = new BasePathToElementType[1];
PathToExtendedFieldType osOriginalSenderEmail = new PathToExtendedFieldType();
osOriginalSenderEmail.PropertyTag = "0x0067";
osOriginalSenderEmail.PropertyType = MapiPropertyTypeType.String;
beAdditionproperteis[0] = osOriginalSenderEmail;
ItemResponseShapeType isItemResponseShape = new ItemResponseShapeType();
isItemResponseShape.BaseShape = DefaultShapeNamesType.AllProperties;
GetItemType giGetItem = new GetItemType();
isItemResponseShape.AdditionalProperties = beAdditionproperteis;
giGetItem.ItemShape = isItemResponseShape;
giGetItem.ItemIds = giGetItemArray;
GetItemResponseType giGetItemResponse = esb.GetItem(giGetItem);
if (giGetItemResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success)
{
ItemType miNDRItem = ((ItemInfoResponseMessageType)giGetItemResponse.ResponseMessages.Items[0]).Items.Items[0];
msMessages[0] = (MessageType)miNDRItem;
foreach (AttachmentType atAttachment in miNDRItem.Attachments)
{
if (atAttachment.ContentType == "message/rfc822")
{
msMessages[1] = GetOrigMsg(esb, atAttachment.AttachmentId);
}

}

}
else {
// Handle Error
}
return msMessages;
}
private MessageType GetOrigMsg(ExchangeServiceBinding esb, AttachmentIdType atAttachID)
{
MessageType omOrigMessage = null;
GetAttachmentType gaGetAttachment = new GetAttachmentType();
AttachmentIdType[] atArray = new AttachmentIdType[1];
atArray[0] = atAttachID;
gaGetAttachment.AttachmentShape = new AttachmentResponseShapeType();
gaGetAttachment.AttachmentShape.IncludeMimeContent = true;
gaGetAttachment.AttachmentShape.IncludeMimeContentSpecified = true;
gaGetAttachment.AttachmentIds = atArray;
GetAttachmentResponseType gaGetAttachmentResponse = esb.GetAttachment(gaGetAttachment);
foreach (AttachmentInfoResponseMessageType atAttachmentResponse in gaGetAttachmentResponse.ResponseMessages.Items)
{
if (atAttachmentResponse.ResponseClass == ResponseClassType.Success)
{
ItemAttachmentType itIemAttachmentType = atAttachmentResponse.Attachments[0] as ItemAttachmentType;
omOrigMessage = (MessageType)itIemAttachmentType.Item;
}
else
{
//Deal with error
}
}
return omOrigMessage;
}
}
}

Thursday, October 02, 2008

Exchange Reverse Permission audit Powershell Gui version 2 Exchange 2007

This script builds on a script I posted earlier in the year that produced a GUI that showed permissions and reverse permissions for Exchange Mailboxes (eg what other mailboxes a particular mailbox has access to). The previous version just looked at the Mailbox Rights and Send AS/Receive AS rights. This new version using Exchange Web Services looks at firstly the rights on the shared folders in a mailbox (eg Root,calendar,inbox,notes,task,journal) and also the Outlook Delegates and the rights assigned to the delegates such as Private Items access and Meeting recipients as well as all the other permissions it did previously. Sorry i haven't got the snapshot functionality done yet which i think i promised before but...

The cool thing is this script now lets us answer a bunch of new questions such as

Which users have given acess to paricular folders in their Mailbox to other people(including the default user which may mean the have unwillingly delegated access to everyone). And we can look at the reverse of this which is what mailboxes folders a particular mailbox has been given access to.

We can see all the Outlook delegations that have been configured and who will receive who's meeting request etc. And the reverse which is what delegate meeting request a particular mailbox will receive.

The big thing is that this script pulls together all the disparate permissions you have to consider when determining mailbox access and displays them in one location so any of the above type of questions become exceedingly easier to answer. Well that's the theory anyway.

This script uses the EWS Powershell library I've been building for a while to provide some eaiser to use functions when accessing Exchange Web Services from Powershell . The ability to access/set folder permissions was added in SP1 with Exchange Web Services. Folder permissions are a little tricky to deal with as I've talked about in the past, basically you have two different types of permission sets, calendarpermissions and normal folder permissions. So its important to detect and differentiate between them and there is also a problem with calendar permissions in that the Outlook Roles don't map to the CalendarPermissionLevelType in EWS. Fortunately i solved this one in the past with a few helper routines that will map the custom levels back to their correct Outlook roles so they make a bit more sense. The delegate code is pretty simple and just returns the outlook delegates as a genericlist.

To report on folder permissions and Delegates using Exchange Web Services you need to have rights on the mailboxes you are going to report on. There are a few ways of granting these rights firstly you can do this by granting those rights to a specific user using add-mailboxpermission as documented here. Or you can grant the rights via EWS impersonation by allowing impersonation on the server and mailStores your going to access see this. To allow for these varying degrees of rights allocations when you run the script its will first prompt you for the EWS authentication and Autodiscover settings. If you have autodiscover configured correctly the script should be able to query AD to find the correct EWS URL to use otherwise you can manually input the URL to your CAS which would look something like "https://casservername.domain.com/ews/exchange.asmx". Once you have made the EWS authentication choices the script will start querying active directory and the mailboxes for every users permissions and depending on the number of mailboxes and users you have hopefully eventually get back to you with a result.

To use this script you need to download the latest copy of the EWSUtil from here and put it in the c:\temp directory as this is where the script expect the dll eg

[void][Reflection.Assembly]::LoadFile("c:\temp\EWSUtil.dll")

I've put a download of the script here the new bits of the code look like.

if ($seAuthCheck.Checked -eq $false) {
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$useImp, $unUserNameTextBox.Text, $unPasswordTextBox.Text, $unDomainTextBox.Text,$casUrl)
}
else{
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$useImp, "", "", "",$casUrl)
}
$fldarry = new-object EWSUtil.EWS.BaseFolderIdType[] 6
for ($fcint=0;$fcint -lt 6;$fcint++){
$dTypeFld = new-object EWSUtil.EWS.DistinguishedFolderIdType

switch ($fcint){
0 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::inbox}
1 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::calendar}
2 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::contacts}
3 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::tasks}
4 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::journal}
5 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::msgfolderroot}
}
$mbMailbox = new-object EWSUtil.EWS.EmailAddressType
$mbMailbox.EmailAddress = $mbMailboxEmail
$dTypeFld.Mailbox = $mbMailbox
$fldarry[$fcint] = $dTypeFld
}
$Folders = $ewc.GetFolder($fldarry)
If ($Folders.Count -ne 0) {
ForEach ($Folder in $Folders) {
if ($Folder.GetType() -eq [EWSUtil.EWS.CalendarFolderType]){
ForEach ($Permissions in $Folder.PermissionSet.CalendarPermissions){
if ($Permissions.UserId.DistinguishedUserSpecified -eq $false){
$sidbind = "LDAP://sid="
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),$aceuser.samaccountname.ToString(),$ewc.enumOutlookRole($Permissions),"Allow")
$fpCount++
if ($rvFolderPerms.Containskey($aceuser.samaccountname.ToString())){
$rvFolderPerms[$aceuser.samaccountname.ToString()] = [int]$rvFolderPerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvFolderPerms.add($aceuser.samaccountname.ToString(),1)
}
}
else{
if ($Permissions.UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Default){
if ($Permissions.CalendarPermissionLevel -ne [EWSUtil.EWS.CalendarPermissionLevelType]::None){
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),"Default",$ewc.enumOutlookRole($Permissions),"Allow")
$dfCount++
}
}
}
}


}
else {
ForEach ($Permissions in $Folder.PermissionSet.Permissions){
if ($Permissions.UserId.DistinguishedUserSpecified -eq $false){
$sidbind = "LDAP://sid="
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),$aceuser.samaccountname.ToString(),$Permissions.PermissionLevel.ToString(),"Allow")
$fpCount++
if ($rvFolderPerms.Containskey($aceuser.samaccountname.ToString())){
$rvFolderPerms[$aceuser.samaccountname.ToString()] = [int]$rvFolderPerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvFolderPerms.add($aceuser.samaccountname.ToString(),1)
}
}
else{
if ($Permissions.UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Default){
if ($Permissions.PermissionLevel -ne [EWSUtil.EWS.PermissionLevelType]::None){
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),"Default",$Permissions.PermissionLevel.ToString(),"Allow")
$dfCount++
}
}
}
}
}
}
}
$delegates = $ewc.getdeletgates($mbMailboxEmail)
foreach ($delegate in $delegates){
$sidbind = "LDAP://sid="
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
$dgCount++
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),"DelegateRight",$aceuser.samaccountname.ToString(),"Mailbox Delegated","Allow")
if ($delegate.DelegateUser.ReceiveCopiesOfMeetingMessagesSpecified -eq $true){
if ($delegate.DelegateUser.ReceiveCopiesOfMeetingMessages-eq $true){
$dgCount++
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),"DelegateRight",$aceuser.samaccountname.ToString(),"Recieve Meetings","Allow")
if ($rvDelegatePerms.Containskey($aceuser.samaccountname.ToString())){
$rvDelegatePerms[$aceuser.samaccountname.ToString()] = [int]$rvDelegatePerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvDelegatePerms.add($aceuser.samaccountname.ToString(),1)
}
}
}

if ($delegate.DelegateUser.ViewPrivateItemsSpecified -eq $true){
if ($delegate.DelegateUser.ViewPrivateItems-eq $true){
$dgCount++
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),"DelegateRight",$aceuser.samaccountname.ToString(),"View Private Items","Allow")
if ($rvDelegatePerms.Containskey($aceuser.samaccountname.ToString())){
$rvDelegatePerms[$aceuser.samaccountname.ToString()] = [int]$rvDelegatePerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvDelegatePerms.add($aceuser.samaccountname.ToString(),1)
}
}
}
if ($rvDelegatePerms.Containskey($aceuser.samaccountname.ToString())){
$rvDelegatePerms[$aceuser.samaccountname.ToString()] = [int]$rvDelegatePerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvDelegatePerms.add($aceuser.samaccountname.ToString(),1)
}

}

Sunday, September 21, 2008

WizBang Exchange 2007 Message Tracking Powershell Gui Version 1

Unlocking the secrets from the depths of the Message Tracking Logs is an ever recurring theme on this blog and in general an important area of Exchange Server Management. In this latest incarnation we boldly go where no Message Tracking application has gone before as well as the normal aggregation, graphing and exports bits and pieces. The new functionality this script introduces expands on post I was talking about a couple of weeks ago which is Using Exchange Web Services to Enhance Exchange Message Tracking. So what we end up is a GUI that does aggregation and filtering and then the ability to look at the content of a particular message by using EWS to find the message based on its message-ID and then being able to see the Subject,Body and then export attachments or the whole message as an EML file. As this is version one i haven't quite go around to making the message search do a Deleted Item Scan (Dumpster Dive) and there is also an issue with Sent Items where the Clients in using Outlook in Cache mode. This is because when you are using Outlook in Cached Mode Items saved to the Sent Items receive a different MessageID which is described in this KB. So this mean you cant find messasges that where sent if the client is using Outlook in Cache mode (if the mesage was sent internally you would be able to get if from the Inbox of the recipient).

This script produces a Multi-Tabbed Winform with different functionality provided by each tab

  1. Tab1 - Setup tab this allows you to specify the parameter for the search of the message tracking logs
  2. Tab2 - Graphs and Summarised tables this tab shows some Google chart's graphs of the Last 6 hours of Message Tracking activity. Graphs of the total Aggregates of Internal/External and Sent/Received Messages. Last hour top 5 Message Sent/Receivers and Organization Totals
  3. Tab3 - Email Summaries per Internal User for Internal Sent/Receive, External Sent/Received and the ability to show the section of the Message Tracking logs for those particular messages. Also Find these message from these filtered Tracking logs via EWS and the Tab5 message Find function. (expot to csv for filtering and aggregated logs)
  4. Tab4 - Raw Message Tracking view
  5. Tab5 - EWS Message Find tab This use EWS to find messages based on message ID invoked from Tab 3 (show message button). Allows export of attachments and message to eml.
To do the search of the Message Tracking logs the script using the get-messagetrackinglogs Exchange powershell cmdlet to return all the Logs entries and then filters based on Send and Receive Entries. To ensure that a log entry is not counted twice the recipients of a message are expanded and a Hastable is used to ensure that the combination of MessageID-Sender and Recipient isn't counted more then once. Being version one this process hasn't been well test so there are probably lots of bugs with this method but so far it seems to work okay.

The graphing functions are provided using Google charts online service and a winform picture control which is the method I've described in the past. The one issue with the method is that it does require that you have a direct Internet connection from the client (eg NAT etc). If your going out through proxies then generally you may see that the graphs don't draw properly this is because the Picture control cant deal with Internet proxies.

The aggregate functions are all done through using a combination of hashtable and custom powershell objects.

The EWS Find functionality is done using the EWS powershell library which i've been using a fair bit and changing and adding more bits and pieces too. The library will handle Autodiscover and allow for both delegate and impersonation access method. The Form allows for all of these combinations and also allows you to specify overrides for both authentication and the URL for the CAS server to use (you need to use the full url to the EWS if you going to use eg https://servername/ews/exchange.asmx).

To use EWS find message function you need a copy of the EWSUtil.dll in c:\temp you can download this from here. You also need rights on the mailboxes you are searching either via delegation or Impersonation.

I've put a download of the script here the code itself is a little large to post.

Thursday, September 18, 2008

Adding Entries to the Safe Sender,Safe Recipients and Blocked Senders in Exchange 2007 via a script workaround

The following is an extension to the Junk Mail enable script for 2007 i posted here. Using the same OWA automation workaround this extends the ability of this script to also add entries to the Safe Senders, Safe Recipients and Blocked Sender by re-using the cmds from OWA 2007.

To add these entires the following XML is posted to /ev.owa?oeh=1&ns=JunkEmail&ev=Add

<params><iLT>1</iLT><sNE>" & emEmailAddress & "</sNE><fFrmOpt>1</fFrmOpt></params>

The <iLT>1</iLT> is the only things that changes eg

<iLT>0</iLT> Adds to Safe Senders
<iLT>1</iLT> Adds to Safe Recipients
<iLT>2</iLT> Adds to Blocked Senders

Make sure you read the original post I've linked above which documents the objects used and the potential issue around using this type of method which is just a quick workaround.

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

snServername = "servername"
mnMailboxname = "mailboxname"
domain = "domain"
strpassword = "password"

Targetmailbox = "mailbox@domain.com"

strusername = domain & "\" & mnMailboxname
szXml = "destination=http://" & snServername & "/owa/&flags=0&username=" & strusername
szXml = szXml & "&password=" & strpassword & "&SubmitCreds=Log On&forcedownlevel=0&trusted=0"


set req = createobject("MSXML2.ServerXMLHTTP.6.0")
req.Open "post", "http://" & snServername & "/owa/auth/owaauth.dll", False
req.SetOption 2, 13056
req.send szXml

reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next

Call UpdateJunk(Targetmailbox)
call AddToSafeSenders(Targetmailbox,"address@domain.com")
call AddToBlockedSenders(Targetmailbox,"address@domain.com")
call AddToSafeRecipients(Targetmailbox,"address@domain.com")

Sub UpdateJunk(mbMailbox)

xmlstr = "<params><fEnbl>1</fEnbl></params>"
req.Open "POST", "http://" & snServername & "/owa/" & mbMailbox & "/ev.owa?oeh=1&ns=JunkEmail&ev=Enable", False
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Content-Length", Len(xmlstr)
req.send xmlstr
wscript.echo req.status
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next
If InStr(req.responsetext,"name=lngFrm") Then
wscript.echo "Mailbox has not been logged onto before via OWA"
'Create a regular expression object
Dim objRegExp
Set objRegExp = New RegExp

objRegExp.Pattern = "<option selected value=""(.*?)"">"
objRegExp.IgnoreCase = True
objRegExp.Global = True

Dim objMatches
Set objMatches = objRegExp.Execute(req.responsetext)
If objMatches.count = 2 then
lcidarry = Split(objMatches(0).Value,Chr(34))
wscript.echo lcidarry(1)
tzidarry = Split(objMatches(1).Value,Chr(34))
wscript.echo tzidarry(1)
pstring = "lcid=" & lcidarry(1) & "&tzid=" & tzidarry(1)
req.Open "POST", "http://" & snServername & "/owa/" & mbMailbox & "/lang.owa", False
req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
req.setRequestHeader "Content-Length", Len(pstring)
' req.SetRequestHeader "cookie", reqCadata
req.send pstring
if instr(req.responsetext,"errMsg") then
wscript.echo "Permission Error"
else
wscript.echo req.status
If req.status = 200 and not instr(req.responsetext,"errMsg") Then
Call UpdateJunk(mbMailbox)
Else
wscript.echo "Failed to set Default OWA settings"
End if
end if

Else
wscript.echo "Script failed to retrieve default values"
End if
Else
wscript.echo "Junk Mail Setting Updated"
End if
End sub

Sub AddToSafeSenders(mbMailbox,emEmailAddress)

xmlstr = "<params><iLT>1</iLT><sNE>" & emEmailAddress & "</sNE><fFrmOpt>1</fFrmOpt></params>"
req.Open "POST", "http://" & snServername & "/owa/" & mbMailbox & "/ev.owa?oeh=1&ns=JunkEmail&ev=Add", False
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Content-Length", Len(xmlstr)
req.send xmlstr
wscript.echo req.status
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next
wscript.echo req.responsetext

end Sub

Sub AddToBlockedSenders(mbMailbox,emEmailAddress)

xmlstr = "<params><iLT>2</iLT><sNE>" & emEmailAddress & "</sNE><fFrmOpt>1</fFrmOpt></params>"
req.Open "POST", "http://" & snServername & "/owa/" & mbMailbox & "/ev.owa?oeh=1&ns=JunkEmail&ev=Add", False
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Content-Length", Len(xmlstr)
req.send xmlstr
wscript.echo req.status
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next
wscript.echo req.responsetext

end Sub

Sub AddToSafeRecipients(mbMailbox,emEmailAddress)

xmlstr = "<params><iLT>3</iLT><sNE>" & emEmailAddress & "</sNE><fFrmOpt>1</fFrmOpt></params>"
req.Open "POST", "http://" & snServername & "/owa/" & mbMailbox & "/ev.owa?oeh=1&ns=JunkEmail&ev=Add", False
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Content-Length", Len(xmlstr)
req.send xmlstr
wscript.echo req.status
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next
wscript.echo req.responsetext

end Sub

Monday, September 08, 2008

Setting the OWA themeid via a script in Exchange 2007

This is a workaround for lack of the ability to set the OWA themeid programatically for those interested in the Technical side this setting is held in a FAI Message with a message class of "IPM.Configuration.OWA.UserOptions" in the Non_IPM_subtree of a mailbox. The setting is held as part of XML string in the 0x7C070102 binary mapi property on this item. Because there is no documentation for the structure of this property it makes setting and reverse engineering this risky and potentially error prone. One realtivly easy workaround to do this is to use some OWA automation code to use the method OWA uses to set this property. I've used this type of thing before to enable junkemail filtering on 2007 in OWA see

Using the MSXML2.ServerXMLHTTP.6.0 object this object is included with the Microsoft XML Parser (MSXML) and is a better choice for this script because it firstly supports the ability to ignore any SSL errors that might happen (eg self signed Certs, Alternate names etc) and it also handles dealing with the Forms Based Authentication cookie without the necessity to add additional code. The code still needs to perform the synthetic forms logon this works similar to 2003 with a few URL tweaks.

Dealing with the Language form for users who have never logged on to OWA before. Because this script is simulating a user in OWA if the mailbox your trying to set the junk email settings has never been logged onto before in OWA then the default language form will be presented to the user (or the script in this case) asking the person to choose there timezone and language. What this script does to cater for this is that it looks for this form in the response if it finds the form it then uses 2 regular expressions to parse the default values from the form and then posts these values. One problem this creates is that the user will nolonger be shown this form when they first logon anymore this may or may not be a problem for you. If you need for this form to still appear at first logon there are two options the first is to delete the OWA storage object in the root of the mailbox using WebDAV or Neil Hobson posted another method on the Exchange Blog.

The operation of the script is pretty simple after logging in it tries to post to the following URL QueryString in the Target Mailbox “ev.owa?oeh=1&ns=Options&ev=SaveGenOpts” with a body

<params><anrFst>0</anrFst><thmId>& themeID &</thmId><optAcc>0</optAcc></params>

The themeID is the interger of the ThemeID to set within 2007 the following intergers represent the default themes

0 Seattle Sky
1 Carbon Black
2 Xbox Theme
3 Zune Theme

To use this script you need to hard code the username and password of a user that has been give delegate access to the target mailbox using something like this in the Exchange Command Shell

Add-MailboxPermission –Identity ‘Mailbox’ –User ‘User’ –AccessRights FullAccess

Or has been given Send As and Receive As rights on the target mailbox. Basically the user needs to be able to open the target mailbox as another mailbox in OWA you can test if the account you want to use is going to be work by testing this yourself in OWA. Its also important that the account that you want to use has logged onto OWA once as well this is because the Language form would be presented to this user the first time the user tries to logon to OWA while the script caters for this for the target user it doesn’t do it for the source so this would cause a timeout error in the case the source user has never logged on to OWA. So before using the script you need to configure the following variables

snServername = "ServerName"
mnMailboxname = "mailbox"
domain = "domain"
strpassword = "password"

In the snServername variable make sure you use the servername of your CAS server this may or may not be different from your mailbox server.

The following variable is for the target mailbox you want to set the junkemail setting on this should be the primary SMTP address of the target mailbox

Targetmailbox = "user@domain.com"

The scripts output is pretty verbose you should see the full response headers outputted for each request the script make this helps if you ever need to diagnose why the script isn’t working

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

snServername = "servername"
mnMailboxname = "mailbox"
domain = "domain"
strpassword = "password"

Targetmailbox = "user@domain.com"

strusername = domain & "\" & mnMailboxname
szXml = "destination=https://" & snServername & "/owa/&flags=0&username=" & strusername
szXml = szXml & "&password=" & strpassword & "&SubmitCreds=Log On&forcedownlevel=0&trusted=0"


set req = createobject("MSXML2.ServerXMLHTTP.6.0")
req.Open "post", "https://" & snServername & "/owa/auth/owaauth.dll", False
req.SetOption 2, 13056
req.send szXml

reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next

Call updateTheme(Targetmailbox,3)

Sub updateTheme(mbMailbox,themeID)

xmlstr = "0" & themeID & "0"
req.Open "POST", "https://" & snServername & "/owa/" & mbMailbox & "/ev.owa?oeh=1&ns=Options&ev=SaveGenOpts", False
req.setRequestHeader "Content-Type", "text/xml; charset=""UTF-8"""
req.setRequestHeader "Content-Length", Len(xmlstr)
req.send xmlstr
wscript.echo req.status
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for each ent in reqhedrarry
wscript.echo ent
Next
If InStr(req.responsetext,"name=lngFrm") Then
wscript.echo "Mailbox has not been logged onto before via OWA"
'Create a regular expression object
Dim objRegExp
Set objRegExp = New RegExp

objRegExp.Pattern = "