2007-11-02

Fighting exchange calendaring

My company decided to outsource email for reasons that surpass knowing. What I do know is that mailstreet must have decided to do everything in their power to prevent any normal method of accessing the raw data for third party applications from working. Even getting outlook working requires jumping through hoops well beyond the normal. Since I don't have windows on my desktop, and even if I did, outlook doesn't have the very simple features I demand from a calendaring application(*) I wanted to force the data to something that could.

A little investigation proved that google calendar seemed to fit the bill. While their documented help suggested that it was impossible, it actually turns out that you can convince google to auto-accept the appointments you send it with your primary calendar. This told me that my goal was possible, but still google would not auto-accept calendar events if I forwarded them from exchange via outlook rules. I was confused by this for some time since I eventually got all of the mail headers to be exactly the same, but then I realized the distinguishing factor google was using was actually in the MIME calendar body. Well, there was nothing that forwarding from exchange was going to do about that, but I already was pulling email down to my desktop using fetchmail, so I could simply slip in a procmail rule, write a perl script, and be on my way. The perl script was more complicated than I truly desired, possibly because I was being a bit anal about working with the various ways that Exchange has been known to to send events as opposed to the absolute minimum necessary. Still, I'm now able to export most events automatically from Exchange get them imported into Google calendar automatically, and then get them downloaded into my phone automatically. I then get a 5 minute beforehand reminder from my calendar and a 15 minute beforehand email. What more could I possibly want?

Well, this only works for calendar events people schedule me for and for calendar events that I schedule for an alias I am on. Any appointment I create for myself or meeting I create for individual addresses including my own address does not get forwarded by exchange? Why? Only Microsoft knows. Other outlook rules fire, just not forwarding emails. So, if I want to create an appointment for myself, I need to cc my gmail account directly.

(*) At a minimum, I demand my calendaring application sends email at a configured time before the event to an email address, like say a SMS address, of my choosing. At the maximum, allow calendar downloads with the same configurable reminders to my semi-smart phone (smart enough to synchronize calendars, not smart enough to run windows mobile--or is that smart enough to not run windows mobile?)).

Don't take this as code I am particularly proud of. It seems to do what I need
and really I have already spent too much time on this puppy anyway. Also it looks like blogger.com was not set up to include code snippets and some bits may not have been included properly (sigh). If you have problems, let me know and I'll see if a cleaner copy can be made available.


#!/usr/bin/perl
#
#
#
use MIME::Tools;
use MIME::Parser;
use Data::Dumper;
use Getopt::Long;
use MIME::Decoder;

my($USAGE) = "Usage: $0: <--to emailaddress> <--from emailaddress>\n";
my(%OPTIONS);
Getopt::Long::Configure("bundling", "no_ignore_case", "no_auto_abbrev", "no_getopt_compat", "require_order");
GetOptions(\%OPTIONS, 'debug', 'to=s', 'from=s') || die $USAGE;

die "$USAGE" unless ($OPTIONS{'to'} && $OPTIONS{'from'});

my $msg = <>;
$msg = <>; # Skip leading line
my $envelope = $msg;

while (<>)
{
$msg .= $_;
}

exit(0) unless ($msg =~ m^Content-Type: text/calendar^);

my $parser = new MIME::Parser;
$parser->ignore_errors(0);

eval
{
my $entity = eval {$parser->parse_data($msg)};
my $error = ($@ || $parser->last_error);
if ($error)
{
die "$error\n";
}

my $head = $entity->head();
my $body = $entity->body();

for($p=0;$p<$entity->parts;$p++)
{
my $part = $entity->parts($p);
if ($part->mime_type eq 'text/calendar')
{
my $io = $part->open('r');
my @lines;
while (my $line = $io->getline())
{
if ($line =~ /^ (.*)/s)
{
my $cont = $1;
chomp($lines[$#lines]);
$lines[$#lines] =~ s/\r$//;
$lines[$#lines] .= $cont;
}
else
{
push(@lines,$line);
}
}
my ($seen_attendee) = 0;
for(my $i = 0; $i < $#lines; $i++)
{

# print "LINE $lines[$i]";
if ($lines[$i] =~ /^(ORGANIZER;.*MAILTO:)[^\@]+\@[^;: \r\n]+(.*)/s)
{
#print "Switch from\n";
$lines[$i] = "$1$OPTIONS{'from'}$2";
}
if (!$seen_attendee && $lines[$i] =~ /^(ATTENDEE;.*MAILTO:)[^\@]+\@[^;: \r\n]+(.*)/s)
{
#print "Add from\n";
# Add myself as first person with same arguments as old first person
splice(@lines,$i,0,"$1$OPTIONS{'to'}$2");
$seen_attendee = 1;
}
}

$io->close;
#print @lines;
$io = $part->open('w');
$io->print(@lines);
$io->close;
# $entity->add_part($part,$p++);
}
}
$entity->print;
};

if ($@)
{
# Log error and output unchanged message.
chomp($@);
my $err = $@;
print STDERR "$@\n" if ($@);

$parser->filer->purge;

exit 1;
}
$parser->filer->purge;

exit 0;

No comments: