Tuesday, April 30, 2013

Automating Outlook - adding/updating a contact

I have been given a thankless task - exporting contact details from Priority (ERP program) to a public folder in Outlook. I believe that this task is fundamentally wrong but I have to recognise that Priority has a blind spot regarding emails (not email addresses); Outlook covers this blind spot.

The following snippet of a program shows how to open a file containing contact details and then to create new contacts in Outlook. For every contact in Priority there are four lines in the following file - this was done in order to prevent problems when Hebrew, English and digits are mixed in the same line.
procedure TForm1.BitBtn1Click(Sender: TObject); const olFolderInbox = 1; var olApp, NameSpace, myFolder, Items, Contact: OleVariant; datafile: textfile; line: string; begin olApp:= CreateOleObject('Outlook.Application'); NameSpace:= olApp.GetNameSpace('MAPI'); myFolder:= NmSpace.GetDefaultFolder (olFolderInbox); items:= myFolder.items; assignfile (datafile, 'x:\system\GLOB_CONTACTSFILE.txt'); reset (datafile); readln (datafile, line); while not eof (datafile) do begin line:= trim (line); Contact:= items.add; Contact.Firstname:= line; readln (datafile, line); Contact.Lastname:= line; readln (datafile, line); Contact.Email1Address:= line; readln (datafile, line); Contact.MobileTelephoneNumber:= line; contact.save; readln (datafile, line) end; Contact:= unassigned; Items:= unassigned; myFolder:= unassigned; NameSpace:= unassigned; olApp:= Unassigned; close end;
This is fairly simple, after one gets one's head around the Outlook Application model. Basically the code gets to the default contacts folder then starts reading the file. For every four lines in the file, a new Outlook contact is defined, given values and saved.

Now the complications begin. The above code stores the new contacts in the default contacts folder whereas the task definition states that the contacts should be stored in a public folder. For the purposes of this blog, I will assume that the folder's name is Architects. Outlook stores its folders in a recursive structure; in order to retrieve a given folder, one must first obtain a list of the top-level folders and then perform a recursive search within those folders. I had to play around a bit in order to write suitable code, but in the end I managed to define successfully a GetFolder function as follows
function GetFolder (aFolder: OleVariant): olevariant; var i: Integer; begin for i:= 1 to aFolder.Count do if afolder.item[i].name = 'Architects' then begin result:= afolder.item[i]; break end else result:= GetFolder (aFolder.Item[i].Folders); end; ... NameSpace:= olApp.GetNameSpace('MAPI'); myFolder:= GetFolder (namespace.folders);
The required folder name should really be defined as a global constant, but for my purposes, the above is sufficient.

The second problem was about updating contacts who already exist in this public folder. Let's assume that someone opened a contact in Priority and entered the contact's name and telephone number but not the email address. If this information is transferred immediately to Outlook, then the Outlook contact too will be lacking an email address. At some later stage, the email address is added to the contact in Priority; how will the contact in Outlook be updated (assuming that Priority knows to output the contact's data again)?

I searched high and low on the Internet but was unable to find anything about updating existing information. First, the existing record has to be found and only then can it be updated. As can be seen from the above code, I am transferring the contact's forename, surname, mobile telephone number and email address. None of these fields are 100% suitable for quick searching, so I changed the file structure and my program to add the contact's id number in Priority. This is guaranteed to be unique and so serves as a primary key both in Priority and in Outlook (to whom the concept of a primary key is foreign [SQL pun on purpose]). I decided to store this value in the department field of the contact.

Searching for a given record (or for several records) is surprisingly easy in Outlook. First, it's best to create another level of redirection then search for the records with the 'find' method
myFolder:= GetFolder (namespace.folders); items:= myFolder.items; contact:= items.find ('[department] = ' + line);
What happens when one has obtained the required record? It turns out that in order to update the record, one simply needs to change values within the record and then save it. In other words, unlike SQL which has an INSERT method for new records and UPDATE method for existing records, Outlook uses the same SAVE method. So the final code (at least for the time being) is
procedure TForm1.BitBtn1Click(Sender: TObject); var olApp, NameSpace, myFolder, Items, Contact: OleVariant; datafile: textfile; line: string; function GetFolder (aFolder: OleVariant): olevariant; var i: Integer; begin for i:= 1 to aFolder.Count do if afolder.item[i].name = 'Architects' then begin result:= afolder.item[i]; break end else result:= GetFolder (aFolder.Item[i].Folders); end; begin olApp:= CreateOleObject('Outlook.Application'); NameSpace:= olApp.GetNameSpace('MAPI'); myFolder:= GetFolder (namespace.folders); items:= myFolder.items; assignfile (datafile, 'x:\system\GLOB_CONTACTSFILE.txt'); reset (datafile); readln (datafile, line); while not eof (datafile) do begin line:= trim (line); contact:= items.find ('[department] = ' + line); if VarIsClear (Contact) then // ie the contact was not found begin Contact:= items.add; Contact.Department:= line; end; readln (datafile, line); Contact.Firstname:= line; readln (datafile, line); Contact.Lastname:= line; readln (datafile, line); Contact.Email1Address:= line; readln (datafile, line); Contact.MobileTelephoneNumber:= line; contact.save; readln (datafile, line) end; Contact:= unassigned; Items:= unassigned; myFolder:= unassigned; NameSpace:= unassigned; olApp:= Unassigned; close end;

No comments: