Friday, April 16, 2010

Creating a Pin to Top Email in Outlook using the EWS managed API and Powershell

In such an imperfect world in which we live coming up with different ways to look at seemingly ignorable problems can sometime be at the heart of innovation. It always strikes me a little odd that while the systems around the our inbox’s have grown and changed over’s the years there has always been a deficit in change around the way information is presented to us in that Inbox folder structure and the way in which a person can flexibly control the operation of how information is presented. In 2010 the new conversation view is a good step forward and some nice eye candy with its ability to group messages within a conversation. However for me this feels a little short of how one would intuitively look at a conversation. Eg a true conversation view would be able to explode the email conversation into conservatory elements rather than just grouping the emails together and asking me to do all the handwork. The problem with the later is it becomes another poweruser function useful to all but little used do to a lack of understanding in the user community. (But you can’t win look at the poor old clippy’s much maligned attempts to better educate users).

One area the pain of this verbiage originates from is the easy and frequency that many applications use email alerts and warning (it just so easy to put an alerting module in). As we fill our infrastructures and even clouds with monitoring and alerting systems are mailboxes in tern start to fill with output of these systems an increasing amount of malaise sets in. While the content of such alerts is useful in nature the size of frequency of this over a long period of time means the meaning of such alerts dissipates and rule redirections and other efforts people use to control the flow of these can create more problems with storage and invalidating the real meaning of these alert messages. So the idea behind this script and method was to look at ways of better using email based alerts within a current email environment.

So first for each alert I received I only wanted 1 email per day to represent this eg if your monitoring the event logs of large number of servers having one email that would float at the top of your mailbox and update itself anytime a important error that need investigation happened was what I really wanted. Essentially something that would pin itself on top of my other messages as some sort of reinforcement that this was important. But when you think about it this idea is applicable outside just the area of alerts you could use it for anything where you getting email based updates eg you could use it to create a pin to email that contained the movement of a particular stock price. So at the end of the week you would have 7 messages in your inbox with the movements of the stock price during that particular day and if it was important that you knew each time there was a big movement the current email would get updated and float to the top of you inbox and mark itself as unread. So look there is still some space for innovation and creative thinking in the realm boxed in software infrastructure.

So to deliver this I’ve created a EWS Managed API script that saves the changekey of the current message that it’s following as the postto message (if it exists else it will create it). Once a new update comes in it will update the body of the message with the text of the new event and change the received date and read properties which will cause the email to appear at the top of a folder. To allow the message to expire there is a search filter that only looks for message based on a creation time (not the update time).

To use the script you need to pass it the mailbox name to execute against and the text for the alert eg

./pinto.ps1 mailbox@domain.com “This is Alert Blah Blah Balh”

This is just a warning this is has no real testing and I’m not sure what it would do to archiving system (should be okay).

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

$MailboxName = $args[0]
$MessageText = $args[1]
$MessageSubject = "Pin To Note Subject"

$expire = [DateTime]::Now.AddDays(-1)
if (Test-Path pifile.txt){
$pmailid = gc pifile.txt -totalcount 1
}
else{
$pmailid = "3B"
}
if ($pmailid -eq $null){$pmailid = "3B"}
$pmailid
function ConvertToByteArray($newString){

$byteArray = New-Object byte[] ($newString.length/2)
for($icnt=0;$icnt-lt$newString.length;$icnt=$icnt+2){
if ($icnt -eq 0){$byteArray[$icnt] = [Convert]::ToInt32($newString.SubString($icnt,2).tolower(), 16)}
else{$byteArray[($icnt/2)] = [Convert]::ToInt32($newString.SubString($icnt,2).tolower(), 16)}
}
return $byteArray

}

function CovertToHex($BinArray1){
$hexOut = ""
for($bc=0;$bc -lt $BinArray1.Length;$bc++){
$nhex = [Convert]::ToString($BinArray1[$bc], 16)
if ($nhex.length -eq 1){$nhex = "0" + $nhex}
$hexOut = $hexOut + $nhex
}
return $hexOut
}



$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.0\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)

$windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$sidbind = "LDAP://<SID=" + $windowsIdentity.user.Value.ToString() + ">"
$aceuser = [ADSI]$sidbind

$service.AutodiscoverUrl($aceuser.mail.ToString())

$PrSearchKey = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(12299, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$PR_Client_Submit_Time = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(57, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime);
$PR_Delivery_Time = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3590, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime);
$PR_Flags = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3591, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);

$Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$Propset.Add($PR_Client_Submit_Time)
$Propset.Add($PR_Delivery_Time)
$Propset.Add($PrSearchKey)
$Propset.Add($PR_Flags)
$Propset.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text

$Sfir = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PrSearchKey, [Convert]::ToBase64String((ConvertToByteArray($pmailId))))
$Sflt = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, $expire)

$sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)

$sfCollection.add($Sfir)
$sfCollection.add($Sflt)


$view = new-object Microsoft.Exchange.WebServices.Data.ItemView(1)

$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

$frFolderResult = $InboxFolder.FindItems($sfCollection,$view)
if ($frFolderResult.Items.Count -ne 0){
"Item Found"
$frFolderResult.Items[0].load($Propset)
$frFolderResult.Items[0].Body.Text
$frFolderResult.Items[0].ExtendedProperties[0].Value = [DateTime]::Now
$frFolderResult.Items[0].ExtendedProperties[1].Value = [DateTime]::Now
$frFolderResult.Items[0].Body.Text = $frFolderResult.Items[0].Body.Text + $MessageText + "`r`n"
$frFolderResult.Items[0].isread = $false
$frFolderResult.Items[0].Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
}
else{
"Item Not found"
$message = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)
$message.From = $MailboxName
$message.ToRecipients.Add($MailboxName)
$message.Subject = $MessageSubject
$message.Body = $MessageText + "`r`n"
$message.SetExtendedProperty($PR_Flags,"1")
$message.isread = $false
$message.save($InboxFolder.Id)
$message.load($Propset)
$message.ExtendedProperties[0].Value = [DateTime]::Now
$message.ExtendedProperties[1].Value = [DateTime]::Now
$message.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
CovertToHex($message.ExtendedProperties[2].Value) | out-file pifile.txt
}

No comments: