Friday, June 22, 2007

Testing SMTP verbs and sending an alert based on the SMTP reply code

I had an interesting problem with a SMTP gateway this week because one of its internal processes had failed the server was processing mail down to the TO verb in the SMTP Protocol exchange and then failing with a specific Status code. Usually you could detect this type of fault by looking at growing queue sizes or delayed delivery times but I wanted a solution that would go out and proactively check the SMTP verbs on a mail server so I could make sure that I would be alerted if there was any issue with anything going on in the SMTP stack.

A while ago I posted a SMTP test script for Powershell which simulated a telnet test against port 25 using Powershell. Using this as a base I streamlined the code to stop outputting information to the commandline and added some logic to parse the first 3 characters from the exchange SMTP response which will relate to the SMTP status code. If everything is going well with your SMTP box you should get a 250 status code see for a full list of status codes. If you don’t get a 250 (or 200) then there’s a possible problem in your SMTP stack the script then creates and sends a SMTP message. The way I used this was to do a direct delivery to my cell phone email account which then gets sent to me as an SMS. You really want to bypass going through your normal gateway for alerting eg if this script is checking that gateway. For the first helo verb I set the script to detect both 220 and 250 response codes this was because I was having an issue where certain firewalls would affect this response code.

Alerting is great but I didn’t want the script to constantly alert me for the same issue once it was detected so there are routines in the script to ensure that it doesn’t send any more the 1 alert per hour. It does this by creating a file in the temp directory and writing the date the last alert was sent. When an error is detected and an alert needs to be sent this file is checked and if the timespan between the last alert being sent and the current date is greater than 60 minutes then an alert is sent if not the script just exits without sending an alert.

Because the script is meant to be self contained I used hardcoded variables which need to be set

$remoteHost = "host.targetdomain.com"
$domain = "targetdomain.com"
$sendingdomain = "youdomain.com"
$AlertFilePath="c:\temp\msMailAlert.txt"
$SmtpServerforAlertDelivery = "smsgateway.com"
$AlertFrom = mailserverwarning@youdomain.com
$AerttTo = "0419999999@youcelphone.com"

To run the script I used a schedule task which is a little tricky under powershell in the run line of the scheduled task you need a line such as

powershell -nologo -noprofile -command "c:\utils\smtpverbmailalert.ps1"

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

$remoteHost = "host.targetdomain.com"
$domain = "targetdomain.com"
$sendingdomain = "youdomain.com"
$Global:SendAlert = 0
$Global:SentAlert = 0
$AlertFilePath="c:\temp\msMailAlert.txt"
$SmtpServerforAlertDelivery = "smsgateway.com"
$AlertFrom = "mailserverwarning@youdomain.com"
$AerttTo = "0419999999@youcelphone.com"

function readResponse($verb) {

while($stream.DataAvailable)
{
$read = $stream.Read($buffer, 0, 1024)
$rstring = $encoding.GetString($buffer, 0, $read)
switch ($verb){
"HELO" {if ($rstring.substring(0,3) -ne "220"){
if ($rstring.substring(0,3) -ne "250"){
SendAlert("HELO verb Result " + $rstring.substring(0,3))}}
}
"FROM" {if ($rstring.substring(0,3) -ne "250"){SendAlert("From verb Result " +
$rstring.substring(0,3))} }
"TO" {if ($rstring.substring(0,3) -ne "250"){SendAlert("To verb ResultStatus " +
$rstring.substring(0,3))} }
}
}
}

function SendAlert($Problem) {

# Create and write to a file
if (! [System.Io.File]::Exists($AlertFilePath))
{ $alertFile=[System.Io.File]::Createtext($AlertFilePath)
$cdate = get-date
$alertFile.WriteLine($cdate.ToString())
$alertFile.Close()
$Global:SendAlert = 1
}
else{
$alertFile = [System.Io.File]::OpenText($AlertFilePath)
$alertTime = $alertFile.ReadLine()
$alertFile.Close()
$LastAlertTimeSpan = New-TimeSpan -start ($alertTime) -end $(Get-Date)
if ($LastAlertTimeSpan.TotalMinutes -gt 60){
[System.Io.File]::Delete($AlertFilePath)
if (! [System.Io.File]::Exists($AlertFilePath))
{ $alertFile=[System.Io.File]::Createtext($AlertFilePath)
$cdate = get-date
$alertFile.WriteLine($cdate.ToString())
$alertFile.Close()
$Global:SendAlert = 1
}
}
else{
$Global:SendAlert = 0
}

}

if ($Global:SendAlert -eq 1 -band $Global:SentAlert -eq 0) {

$Title = "Mail Server Failed " + $Problem
$Body = $Problem
$SmtpClient = new-object system.net.mail.smtpClient
$SmtpClient.host = $SmtpServerforAlertDelivery
$SmtpClient.Send($AlertFrom,$AerttTo,$title,$Body)
$Global:SentAlert = 1

}

}

$port = 25
$socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port)
if($socket -eq $null) { return; }
$stream = $socket.GetStream()
$writer = new-object System.IO.StreamWriter($stream)
$buffer = new-object System.Byte[] 1024
$encoding = new-object System.Text.AsciiEncoding
readResponse($stream)
$command = "HELO "+ $domain
$writer.WriteLine($command)
$writer.Flush()
start-sleep -m 500
readResponse($stream,"HELO")
$command = "MAIL FROM: <smtpcheck@" + $sendingdomain + ">"
$writer.WriteLine($command)
$writer.Flush()
start-sleep -m 500
readResponse($stream,"FROM")
$command = "RCPT TO: <postmaster@" + $domain + ">"
$writer.WriteLine($command)
$writer.Flush()
start-sleep -m 500
readResponse($stream,"TO")
$command = "QUIT"
$writer.WriteLine($command)
$writer.Flush()
start-sleep -m 500
readResponse($stream,"QUIT")
## Close the streams
$writer.Close()
$stream.Close()

Thursday, June 07, 2007

Using Exchange Web Services to create appointments in the Google Calendar using the Google Calendar API

I’ve being playing around with some code over the last couple of months for synchronizing an Exchange calendar with a Google Calendar using a SyncFolderItems operation on the EWS Side to get a list of updates from a calendar and using the Google .NET client library to create the appointments in a Google calendar. I did manage to successfully create something that was able to create appointments in the Google calendar based on the EWS output. I even got recurring events working by building some logic that converted the recurring output types that EWS uses into a RRULE that could be used in the Google calendar. But when I sat down to look at the rest of the synchronization logic I would need to write to get exceptions to recurring appointments,updates and deletes working the motivation to do this wasn’t really there so instead of just committing this to the back burner I thought I’d post the code which might help someone else out.

The EWS SyncFolderItems operation is very similar to WebDAV replication in that It acts like a change notification repository. The clobblob has been replaced by the SyncState property which is a ID you need to store and submit with future queries to get a manifest of the changes to a folder your synchronizing. For this app I’ve used an XML file to store the ID I was planning to expand the use of this XML which is necessary to work around the shortcoming of both the Google Calendar API and Exchange Web Services. The code will first look for the existence of the file to work out if this is an initial sync or partial sync. If it’s the initial sync then the file is created and the appropriate SyncFolderItems operation is executed. If it’s a paritial sync then the Syncstat is retrieved from the XML file and used in the SyncFolderItems operation and then the file is updated with the new ID retrieved from the SyncFolderItems response. The next part of the code goes though the updated manifest and processes the items based on whether they are creation,updates of deletes. I only got as far as doing the creation side. When the creation method is called a GetItem request is made to get the full details of the item that has created in Exchange. The information is then used to create a Google calendar item.

This is where it starts to get a little bit more complicated because of the various different types of appointments there can be. At the moment this code caters for 3 different types of appointments (or combination of these 3). So there is code to deal with single appointments, All Day appointments and recurring appointments. To relate exchange appointment to Google appointments I had was thinking of using the extended properties that Google allows you to create on a calendar item. Using this I managed to add the ItemID and ChangeID from EWS to the Google calendar appointment. Unfortunately the Google calendar API doesn’t allow you to do a search based on these extended properties so this is where the XML file thats being used to store the SyncStat was going to come into use so you can related properties that are searchable to EWS entryID’s.

That’s about it both of these API’s give you a solid base to do things in but some of the extensibility and feature gaps are a little disappointing IMO but there are always workarounds as long as you have time to write the logic. This code require the Google .NET client side libraries see

I’ve put a download able copy of this code here the code itself looks like.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using ewsSync.EWS;
using Google.GData.Calendar;
using Google.GData.Client;
using Google.GData.Extensions;

namespace ewsSync
{
class Program
{
static void Main(string[] args)
{
String guGCalUserName = "username@gmail.com";
String gpGCalPassword = "password";
String exUserName = "username";
String exDomain = "domain";
String exPassword = "password";
String sfSyncFilePath = @"c:\google-Calendar.xml";
String exBindingURL = @"https://servername/EWS/exchange.asmx";
String cuCalendarURL = "http://www.google.com/calendar/feeds/" + guGCalUserName
+ "/private/full";

//Athentic to Google Calendar
CalendarService csGoogleCalendarServer = new CalendarService("EWS-Cal-Sync");
csGoogleCalendarServer.setUserCredentials(guGCalUserName, gpGCalPassword);
//Deal with Self Signed Certificate Errors
ServicePointManager.ServerCertificateValidationCallback = delegate(Object obj,
X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
};

String fnSyncFileName = sfSyncFilePath;
ExchangeServiceBinding ewsServiceBinding = new ExchangeServiceBinding();
ewsServiceBinding.Credentials = new
NetworkCredential(exUserName,exPassword,exDomain);
ewsServiceBinding.Url = exBindingURL;
if (File.Exists(fnSyncFileName))
{
Program.PartialSync(ewsServiceBinding,
fnSyncFileName,csGoogleCalendarServer,cuCalendarURL);
}
else
{
Program.InitialSync(ewsServiceBinding,
fnSyncFileName,csGoogleCalendarServer,cuCalendarURL);

}

}
static private void InitialSync(ExchangeServiceBinding ewsServiceBinding, string
fnSyncFileName, CalendarService csGoogleCalendarServer, string cuCalendarURL)
{
XmlDocument sfSyncFile = new XmlDocument();
StringWriter xsXmlString = new StringWriter();
XmlWriter xrXmlWritter = new XmlTextWriter(xsXmlString);
xrXmlWritter.WriteStartDocument();
xrXmlWritter.WriteStartElement("CalendarSync");
SyncFolderItemsType siSyncItemsRequest = new SyncFolderItemsType();
siSyncItemsRequest.ItemShape = new ItemResponseShapeType();
siSyncItemsRequest.ItemShape.BaseShape = DefaultShapeNamesType.IdOnly;
siSyncItemsRequest.SyncFolderId = new TargetFolderIdType();
DistinguishedFolderIdType cfCalendar = new DistinguishedFolderIdType();
cfCalendar.Id = DistinguishedFolderIdNameType.calendar;
siSyncItemsRequest.SyncFolderId.Item = cfCalendar;
siSyncItemsRequest.MaxChangesReturned = 512;
SyncFolderItemsResponseType syncItemsResponse =
ewsServiceBinding.SyncFolderItems(siSyncItemsRequest);
SyncFolderItemsResponseMessageType responseMessage = new
SyncFolderItemsResponseMessageType();
responseMessage = syncItemsResponse.ResponseMessages.Items[0] as
SyncFolderItemsResponseMessageType;
if (responseMessage.ResponseClass == ResponseClassType.Error)
{
throw new Exception(responseMessage.MessageText);
}
else
{
xrXmlWritter.WriteAttributeString("SyncState",responseMessage.SyncState);
xrXmlWritter.WriteEndElement();
xrXmlWritter.WriteEndDocument();
sfSyncFile.LoadXml(xsXmlString.ToString());
sfSyncFile.Save(fnSyncFileName);
string changes = responseMessage.Changes.Items.Length.ToString();
Console.WriteLine("Number of items to synchronize: " + changes);
Int32 ncNumberOfChanges = responseMessage.Changes.Items.Length;
for (int scSyncChange = 0; scSyncChange < ncNumberOfChanges; scSyncChange++)
{
Console.WriteLine(responseMessage.Changes.ItemsElementName[scSyncChange].ToString());
switch (responseMessage.Changes.ItemsElementName[scSyncChange].ToString())
{
case "Delete": SyncFolderItemsDeleteType diDeletedItem =
(SyncFolderItemsDeleteType)responseMessage.Changes.Items[scSyncChange];
break;
case "Create": SyncFolderItemsCreateOrUpdateType ciCreateItem =
(SyncFolderItemsCreateOrUpdateType)responseMessage.Changes.Items[scSyncChange];
createGoogleCalItem(ciCreateItem, ewsServiceBinding,
csGoogleCalendarServer,cuCalendarURL);
break;
case "Update": SyncFolderItemsCreateOrUpdateType uiUpdateItem =
(SyncFolderItemsCreateOrUpdateType)responseMessage.Changes.Items[scSyncChange];
modifyGoolgCalItem(uiUpdateItem, ewsServiceBinding, csGoogleCalendarServer);
break;

}
}
}
}
private static void PartialSync(ExchangeServiceBinding ewsServiceBinding, string
fnSyncFileName, CalendarService csGoogleCalendarServer, string cuCalendarURL)
{
string ssSyncState = "";
XmlDocument sfSyncFile = new XmlDocument();
sfSyncFile.Load(fnSyncFileName);
XmlNodeList snSyncStateNodes = sfSyncFile.SelectNodes("//CalendarSync");
foreach (XmlNode xnSyncNode in snSyncStateNodes) {
ssSyncState = xnSyncNode.Attributes.GetNamedItem("SyncState").Value;
}

SyncFolderItemsType siSyncItemsRequest = new SyncFolderItemsType();
siSyncItemsRequest.ItemShape = new ItemResponseShapeType();
siSyncItemsRequest.ItemShape.BaseShape = DefaultShapeNamesType.IdOnly;
siSyncItemsRequest.SyncFolderId = new TargetFolderIdType();
DistinguishedFolderIdType cfCalendar = new DistinguishedFolderIdType();
cfCalendar.Id = DistinguishedFolderIdNameType.calendar;
siSyncItemsRequest.SyncFolderId.Item = cfCalendar;
siSyncItemsRequest.SyncState = ssSyncState;
siSyncItemsRequest.MaxChangesReturned = 512;
SyncFolderItemsResponseType syncItemsResponse =
ewsServiceBinding.SyncFolderItems(siSyncItemsRequest);
SyncFolderItemsResponseMessageType responseMessage = new
SyncFolderItemsResponseMessageType();
responseMessage = syncItemsResponse.ResponseMessages.Items[0] as
SyncFolderItemsResponseMessageType;
if (responseMessage.ResponseClass == ResponseClassType.Error)
{
throw new Exception(responseMessage.MessageText);
}
else
{
foreach (XmlNode xnSyncNode in snSyncStateNodes)
{
xnSyncNode.Attributes.GetNamedItem("SyncState").Value =
responseMessage.SyncState;
}
sfSyncFile.Save(fnSyncFileName);
if (responseMessage.Changes.Items == null) {
Console.WriteLine("Nothing to Syncronise");
}
else
{
Int32 ncNumberOfChanges = responseMessage.Changes.Items.Length;

Console.WriteLine("Number of items to synchronize: " +
ncNumberOfChanges.ToString());
for(int scSyncChange=0;scSyncChange < ncNumberOfChanges ;scSyncChange++){
Console.WriteLine(responseMessage.Changes.ItemsElementName[scSyncChange].ToString());
switch (responseMessage.Changes.ItemsElementName[scSyncChange].ToString()){
case "Delete" : SyncFolderItemsDeleteType diDeletedItem =
(SyncFolderItemsDeleteType)responseMessage.Changes.Items[scSyncChange];
break ;
case "Create": SyncFolderItemsCreateOrUpdateType ciCreateItem =
(SyncFolderItemsCreateOrUpdateType)responseMessage.Changes.Items[scSyncChange];
createGoogleCalItem(ciCreateItem,ewsServiceBinding,
csGoogleCalendarServer,cuCalendarURL);
break ;
case "Update": SyncFolderItemsCreateOrUpdateType uiUpdateItem =
(SyncFolderItemsCreateOrUpdateType)responseMessage.Changes.Items[scSyncChange];
modifyGoolgCalItem(uiUpdateItem, ewsServiceBinding, csGoogleCalendarServer);
break;

}

}

}
}

}
private static void modifyGoolgCalItem(SyncFolderItemsCreateOrUpdateType
ciCreateItem, ExchangeServiceBinding ewsServiceBinding, CalendarService
csGoogleCalendarServer) {


}
private static void createGoogleCalItem(SyncFolderItemsCreateOrUpdateType
ciCreateItem, ExchangeServiceBinding ewsServiceBinding, CalendarService
csGoogleCalendarServer, String cuCalendarURL)
{


GetItemType giRequest = new GetItemType();
ItemIdType iiItemId = new ItemIdType();
iiItemId.Id = ciCreateItem.Item.ItemId.Id;
iiItemId.ChangeKey = ciCreateItem.Item.ItemId.ChangeKey;
ItemResponseShapeType giResponseShape = new ItemResponseShapeType();
giResponseShape.BaseShape = DefaultShapeNamesType.AllProperties;
giResponseShape.IncludeMimeContent = true;
giRequest.ItemShape = giResponseShape;

giRequest.ItemIds = new ItemIdType[1];
giRequest.ItemIds[0] = iiItemId;
giRequest.ItemShape.BaseShape = DefaultShapeNamesType.AllProperties;
giRequest.ItemShape.IncludeMimeContent = true;
giRequest.ItemShape.BodyType = BodyTypeResponseType.Text;
giRequest.ItemShape.BodyTypeSpecified = true;

GetItemResponseType giResponse = ewsServiceBinding.GetItem(giRequest);
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;
CalendarItemType ciCalentry =
(CalendarItemType)rmResponseMessage.Items.Items[0];
EventEntry ceCalendarEntry = new EventEntry();
ceCalendarEntry.Title.Text = ciCalentry.Subject;
if (ciCalentry.Body != null) { ceCalendarEntry.Content.Content =
ciCalentry.Body.Value; }
AtomPerson auAuthor = new AtomPerson(AtomPersonType.Author);
auAuthor.Name = ciCalentry.Organizer.Item.Name;
auAuthor.Email = ciCalentry.Organizer.Item.EmailAddress;
ceCalendarEntry.Authors.Add(auAuthor);

When cwCalenderWhen = new When();

if (ciCalentry.IsAllDayEvent == true)
{
cwCalenderWhen.StartTime = ciCalentry.Start.ToLocalTime();
cwCalenderWhen.EndTime = ciCalentry.End.ToLocalTime();
cwCalenderWhen.AllDay = true;
}
else {
if (ciCalentry.CalendarItemType1 == CalendarItemTypeType.RecurringMaster)
{

RecurrenceType rtRecurrance = ciCalentry.Recurrence;
RecurrenceRangeBaseType rrRecurranceRange = rtRecurrance.Item1;
String rpRecurData = "DTSTART:" + ciCalentry.Start.ToString("yyyyMMddTHHmmssZ")
+ " \r\n"
+ "DTEND:" + ciCalentry.End.ToString("yyyyMMddTHHmmssZ") + " \r\n";
string mdDay;
int frFirstRun = 0;
Hashtable mhMonthhash = new Hashtable();
string msMonthString = "jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec";
string[] ysYearMonths = msMonthString.Split((char)44);
int mval = 1;
foreach (string msMonth in ysYearMonths)
{
mhMonthhash.Add(msMonth, mval);
mval++;
}
RecurrencePatternBaseType rpRecurrancePattern = rtRecurrance.Item;
String rtRecuranceType = rpRecurrancePattern.GetType().Name.ToString();
switch (rtRecuranceType) {
case "WeeklyRecurrencePatternType": WeeklyRecurrencePatternType
wpWeeklyRecurrence = (WeeklyRecurrencePatternType)rpRecurrancePattern;
rpRecurData = rpRecurData + "RRULE:FREQ=WEEKLY;BYDAY=";
frFirstRun = 0;
string[] WeekDays = wpWeeklyRecurrence.DaysOfWeek.Split((char)32);
foreach(string dsDay in WeekDays){
if (frFirstRun == 0)
{
rpRecurData = rpRecurData + dsDay.Substring(0, 2);
frFirstRun = 1;
}
else {
rpRecurData = rpRecurData + "," + dsDay.Substring(0, 2);
}
}
rpRecurData = rpRecurData + ";";
break ;
case "DailyRecurrencePatternType": DailyRecurrencePatternType dpDailyRecurrence
= (DailyRecurrencePatternType)rpRecurrancePattern;
rpRecurData = rpRecurData + "RRULE:FREQ=DAILY;INTERVAL=" +
dpDailyRecurrence.Interval.ToString() + ";";
break;
case "AbsoluteMonthlyRecurrencePatternType":
AbsoluteMonthlyRecurrencePatternType amMonthlyrecurance =
(AbsoluteMonthlyRecurrencePatternType)rpRecurrancePattern;
rpRecurData = rpRecurData + "RRULE:FREQ=MONTHLY;INTERVAL=" +
amMonthlyrecurance.Interval.ToString() + ";";
rpRecurData = rpRecurData + "BYMONTHDAY=" +
amMonthlyrecurance.DayOfMonth.ToString();
rpRecurData = rpRecurData + ";";
break;
case "RelativeMonthlyRecurrencePatternType":
RelativeMonthlyRecurrencePatternType rmMonthlyrecurance =
(RelativeMonthlyRecurrencePatternType)rpRecurrancePattern;
rpRecurData = rpRecurData + "RRULE:FREQ=MONTHLY;INTERVAL=" +
rmMonthlyrecurance.Interval.ToString() + ";";
rpRecurData = rpRecurData + "BYDAY=";
mdDay = rmMonthlyrecurance.DaysOfWeek.ToString().Substring(0, 2);
switch (rmMonthlyrecurance.DayOfWeekIndex.ToString())
{
case "First": rpRecurData = rpRecurData + "1" + mdDay;
break;
case "Second": rpRecurData = rpRecurData + "2" + mdDay;
break;
case "Third": rpRecurData = rpRecurData + "3" + mdDay;
break;
case "Fourth": rpRecurData = rpRecurData + "4" + mdDay;
break;
case "Last": rpRecurData = rpRecurData + "-1" + mdDay;
break;

}
rpRecurData = rpRecurData + ";";
break;
case "RelativeYearlyRecurrencePatternType": RelativeYearlyRecurrencePatternType
ypYearlyRecurrance = (RelativeYearlyRecurrencePatternType)rpRecurrancePattern;
rpRecurData = rpRecurData + "RRULE:FREQ=YEARLY;";
rpRecurData = rpRecurData + "BYMONTH=" +
mhMonthhash[ypYearlyRecurrance.Month.ToString().ToLower().Substring(0,
3)].ToString() + ";";
mdDay = ypYearlyRecurrance.DaysOfWeek.ToString().Substring(0, 2);
rpRecurData = rpRecurData + "BYDAY=";
switch (ypYearlyRecurrance.DayOfWeekIndex.ToString())
{
case "First": rpRecurData = rpRecurData + "1" + mdDay;
break;
case "Second": rpRecurData = rpRecurData + "2" + mdDay;
break;
case "Third": rpRecurData = rpRecurData + "3" + mdDay;
break;
case "Fourth": rpRecurData = rpRecurData + "4" + mdDay;
break;
case "Last": rpRecurData = rpRecurData + "-1" + mdDay;
break;

}
rpRecurData = rpRecurData + ";";
break;
case "AbsoluteYearlyRecurrencePatternType": AbsoluteYearlyRecurrencePatternType
yaYearlyRecurrance = (AbsoluteYearlyRecurrencePatternType)rpRecurrancePattern;
rpRecurData = rpRecurData + "RRULE:FREQ=YEARLY;";
rpRecurData = rpRecurData + "BYMONTH=" +
mhMonthhash[yaYearlyRecurrance.Month.ToString().ToLower().Substring(0,
3)].ToString() + ";";
rpRecurData = rpRecurData + "BYDAY=" +
yaYearlyRecurrance.DayOfMonth.ToString().Substring(0, 2) + ";";
break;
}
string rtRangeType = rrRecurranceRange.GetType().Name.ToString();
switch (rtRangeType)
{
case "NumberedRecurrenceRangeType": NumberedRecurrenceRangeType nrNumberRecRange
= (NumberedRecurrenceRangeType)rrRecurranceRange;
rpRecurData = rpRecurData + "COUNT=" +
nrNumberRecRange.NumberOfOccurrences.ToString() + ";";
break;
case "EndDateRecurrenceRangeType": EndDateRecurrenceRangeType edDateRecRange =
(EndDateRecurrenceRangeType)rrRecurranceRange;
rpRecurData = rpRecurData + "UNTIL=" +
edDateRecRange.EndDate.ToString("yyyyMMddTHHmmssZ") + ";";
break;
}
rpRecurData = rpRecurData + "\r\n";
Recurrence reRecurrence = new Recurrence();
reRecurrence.Value = rpRecurData;
cwCalenderWhen.StartTime = ciCalentry.Start;
cwCalenderWhen.EndTime = ciCalentry.End;
ceCalendarEntry.Recurrence = reRecurrence;


}
else
{
cwCalenderWhen.StartTime = ciCalentry.Start;
cwCalenderWhen.EndTime = ciCalentry.End;
}
}

ceCalendarEntry.Times.Add(cwCalenderWhen);
if (ciCalentry.Location != null)
{
Where cwCalendarWhere = new Where();
cwCalendarWhere.ValueString = ciCalentry.Location;
ceCalendarEntry.Locations.Add(cwCalendarWhere);
}
ExtendedProperty exIDPropperty = new ExtendedProperty();
exIDPropperty.Name = "http://msgdev.mvps.org/EWSItemID";
exIDPropperty.Value = ciCreateItem.Item.ItemId.Id.ToString();
ceCalendarEntry.ExtensionElements.Add(exIDPropperty);
ExtendedProperty exIDPropperty1 = new ExtendedProperty();
exIDPropperty1.Name = "http://msgdev.mvps.org/EWSChangeKey";
exIDPropperty1.Value = ciCreateItem.Item.ItemId.ChangeKey.ToString();
ceCalendarEntry.ExtensionElements.Add(exIDPropperty1);
Uri piPostUri = new Uri(cuCalendarURL);
AtomEntry insertedEntry = csGoogleCalendarServer.Insert(piPostUri,
ceCalendarEntry);
}

}
}
}