tcom Allows Emacs as Editor for MS Outlook

MNO Playing around with the tcom extension after the inspiration of some Python code at http://disgruntled-programmer.com/notes/emacs-with-outlook.html , I came up with the following:-

It basically allows a running NTEmacs editor to grab the text of an email reply/forward etc. from Outlook and edit it in Emacs. It is then possible to transfer the edited text back to the outlook window and send it from there.

Currently only tested with Outlook 2000, ActiveTcl 8.4.3 and NTEmacs 21.3.1 under Windows 2000. I have no idea if it will work with Outlook 98, Outlook Express etc.

PLEASE READ THE LAST POINT UNDER "KNOWN ISSUES" BELOW BEFORE USING THIS CODE!

Pre-requisites

Installation

  • put the outlookedit.el lisp file somewhere in your load-path and add a (require 'outlookedit) into your .emacs file
  • place the two Tcl scripts onto your system and edit the locations in the outlookedit.el file

Usage

  • select a message in Outlook and click "Reply" (or "Reply to all" etc.)
  • switch to your NTEmacs window and press C-c o e (that is Control-C followed by "o" then "e" - mnemonic: Outlook Edit)
  • the message text should now appear in an Emacs buffer
  • edit in the emacs buffer
  • press C-c o s (mnemonic Outlook Save)
  • the edited text should now replace the text in the outlook window
  • (optionally) select "Format->Plain Text" to switch the message format back to plain text (see Known issues below for an explanation)
  • press Send as usual

Known Issues

  • Outlook 2000 (contrary to its documentation) renders the edited message as Rich Text regardless of the original format or the users specified preferred format. Outlook 2002 is reported to fix this by introducing a BodyFormat parameter.
  • If you change the configuration of active Outlook windows between grabbing the text and saving it back to Outlook you will probably overwrite the wrong text. You have been warned! Don't blame me!

The Code

The Emacs-Lisp file outlookedit.el

 ;;; outlookedit.el
 ;; use a couple of Tcl scripts to invoke my Tcl scripts to do COM
 ;; related actions on Outlook to get and replace text in the
 ;; reply/compose boxes allowing it to be edited in Emacs
 ;;
 (defvar mno-get-outlook-body 
   "tclsh C:\\Userdata\\Tcl\\grabOutlookMessage.tcl")
 (defvar mno-put-outlook-body 
   "tclsh C:\\Userdata\\Tcl\\putMessageInOutlook.tcl")
 (defvar mno-outlook-default-justification 'full)  

 (global-set-key "\C-coe" 'mno-edit-outlook-message)
 (global-set-key "\C-cos" 'mno-save-outlook-message)

 (defun mno-edit-outlook-message ()
   "* Slurp an outlook message into a new buffer ready for editing 

 The message must be in the active Outlook window.  Typically the
 user would press the Reply or Reply-all button in Outlook then
 switch to Emacs and invoke \\[mno-edit-outlook-message]

 Once all edits are done, the function mno-save-outlook-message
 (invoked via \\[mno-save-outlook-message]) can be used to send the
 newly edited text back into the Outlook window.  It is important that
 the same Outlook window is current in outlook as was current when the
 edit was started when this command is invoked."
   (interactive)
   (save-excursion
     (let ((buf (get-buffer-create "*Outlook Message*"))
          (body (shell-command-to-string mno-get-outlook-body)))
       (switch-to-buffer buf)
       (message-mode) ; enables automagic reflowing of text in quoted
                      ; sections
       (setq default-justification mno-outlook-default-justification)
       (setq body (replace-regexp-in-string "\r" "" body))
       (delete-region (point-min) (point-max))
       (insert body)
       (goto-char (point-min))))) 


 (defun mno-save-outlook-message ()
   "* Send the outlook message buffer contents back to Outlook current window 

 Unfortunately, Outlook 2000 then renders this text as Rich Text format
 rather than plain text, overriding any user preference for plain text.
 The user then needs to select Format->Plain text in the outlook
 compose window to reverse this.

 Outlook 2002 apparently has a BodyFormat parameter to control this."
   (interactive)
   (save-excursion
     (let ((buf (get-buffer "*Outlook Message*")))
       (set-buffer buf)
       (shell-command-on-region (point-min) (point-max) mno-put-outlook-body)
       (set-buffer-modified-p 'nil) ; now kill-buffer won't complain!
       (kill-buffer "*Outlook Message*")))) 

 (provide 'outlookedit)
 ;;;end of file outlookedit.el

The grab and put functions used above. First putMessageInOutlook.tcl:-

 #!/bin/sh
 # Emacs please open this in -*-Tcl-*- mode
 # do not remove this backslash! -> \
     exec tclsh $1 ${1+"$@"}
 #
 # loosely based on Python code from 
 # http://disgruntled-programmer.com/notes/emacs-with-outlook.html
 #
 package require tcom

 set body [read stdin]
 #
 # find Outlook:-
 if { [catch {::tcom::ref getactiveobject Outlook.Application} o] } {
     puts "Couldn't find Outlook via COM: $o"
     exit 1
 }
 # get the current item:-
 if { [catch {$o -get ActiveInspector} a] } {
     puts "Couldn't get the ActiveInspector for Outlook via COM: $a"
     exit 2
 }
 if { [catch {$a -get CurrentItem} i] } {
     puts "Couldn't get the CurrentItem for Outlook via COM: $i"
     exit 3
 }   
 # and finally replace its body
 if { [catch {$i -set Body $body} err] } {
     puts "Couldn't replace the Body via COM: $err"
     exit 4
 }
 # The following (untested) might convert back from RTF in Outlook 2002
 catch {$i -set BodyFormat 1}
 # that's it

and second, grabOutlookMessage.tcl:-

 #!/bin/sh
 # Emacs please open this in -*-Tcl-*- mode
 # do not remove this backslash! -> \
     exec tclsh $1 ${1+"$@"}
 #
 # loosely based on Python code from 
 # http://disgruntled-programmer.com/notes/emacs-with-outlook.html
 #
 package require tcom
 #
 # find Outlook:-
 if { [catch {::tcom::ref getactiveobject Outlook.Application} o] } {
     puts "Couldn't find Outlook via COM: $o"
     exit 1
 }
 # get the current item:-
 if { [catch {$o -get ActiveInspector} a] } {
     puts "Couldn't get the ActiveInspector for Outlook via COM: $a"
     exit 2
 }
 if { [catch {$a -get CurrentItem} i] } {
     puts "Couldn't get the CurrentItem for Outlook via COM: $i"
     exit 3
 }   
 # and finally slurp its body
 if { [catch {$i -get Body} body] } {
     puts "Couldn't retrieve body from CurrentItem via COM: $body"
     exit 4
 }
 #
 # spew it out to stdout
 #
 puts $body
 # that's it

Stefan Vogel 7 Jan 04:

Hey ... wow ... this is way cool. That's what I always wished to do. Get rid of this lousy MS-Outlook-Editor (it's not even worth to be called an editor).


Ephrem Christopher Walborn[L1 ] - 20040107

The tcl-scripts were indeed swapped, but I've fixed it. This works beautifully with XP and Office XP, which is great since I just got the word today that I'm no longer to use anything but Outlook for my work e-mail.

The next thing I did, once I got this working, was to write a quick batch script to avoid having to do C-coe:

    c:\emacs\bin\gnudoit.exe (mno-edit-outlook-message)

Drag & drop it onto the shortcuts bar in outlook... it'd be even better if it could be a button on the e-mail message form and also switch focus to emacs.

Outlook does now allow me to drag&drop a batch-file anywhere. How did you do this?


MNO 8 Jan 04:

Oops - thanks for spotting and fixing the transposition of the get and put scripts.

I'm not sure why message-mode would raise an error there, It's not essential to the operation but it does add functionality (particularly reformatting of quoted material if you use the "> " prefix quoting setting in Outlook).

... Stefan Vogel ... hmmm message-mode .. ooops ... some private "improvements" (missing *.el-files) :-)


MNO After reading ECW's suggestions above I started digging a bit further into Outlook.

I made a small VB macro which looks like:-

 Sub mnoEditInEmacs()
     Shell ("C:\userdata\emacs-21.3\bin\gnudoit.exe (mno-edit-outlook-message)")
 End Sub

I then defined a new toolbar in the compose email form window, and added two items to it, the "plain text" format button and my macro as defined above.

I now have a button in the compose window (albeit with the ugly name "Project1.mnoEditInEmacs") which triggers the mno-edit-outlook-message function and a button to turn the message back to plain text again afterwards (which may not be necessary in Outlook 2002 if the bodyFormat thingy mentioned above works as imagined)).

There are a couple of thjings still not quite right though - I had to change my macro security setting to "medium" to make this work (and restart/stop/restart Outlook a couple of times) (it may be possible to digitally sign the macro and register the source as trusted to get round this in a more secure fashion). This means that the first time i hit the button in a new Outlook session, I am prompted whether to enable macros or not.


Martin Stemplinger If you right-click on the new icon and select "standard" the ugly name disappears. There is also an option to change the icon.


Ernie Longmire Following on after MNO's VB macro, two changes allow window focus to automatically follow the message between Outlook and NTEmacs. First, change the Shell command in mnoEditInEmacs() to:

    Shell ("C:\emacs-21.3\bin\gnudoit.exe (progn (mno-edit-outlook-message) (select-frame-set-input-focus (selected-frame)))")

and add the following at the end of putMessageInOutlook.tcl:

    if { [catch {$a -method Activate} err] } {
        puts "Couldn't Activate Inspector via COM: $err"
        exit 5
    }

Note that Windows XP's "Prevent applications from stealing focus" setting (available in TweakUI) must be disabled, or the app windows will only flash in the toolbar instead of raising and grabbing focus.

Tested under Outlook 2003.


Ernie Longmire The following revision of mnoEditInEmacs() adds a message format check, and can convert HTML- and RTF-format messages to plain text in Outlook before loading the message into emacs. This makes sending an Outlook message to emacs for editing a single-click operation under all circumstances.

 Sub mnoEditInEmacs()
 ' Ernie Longmire, 17 Feb 2005
 ' http://www.studio-nibble.com/

     Dim itmCur As Object
     Dim intFmt As Integer
     Dim strFmt As String
     Dim intRes As Integer

     Set itmCur = Outlook.Application.ActiveInspector.CurrentItem
     intFmt = itmCur.BodyFormat

     If intFmt <> olFormatPlain Then

         If intFmt = olFormatRichText Then
             strFmt = "rich text"
         ElseIf intFmt = olFormatHTML Then

             'HTML messages store the full text of the body with HTML
             '   markup in a separate string, MailItem.HTMLBody.  In these
             '   messages MailItem.Body contains a version of the message
             '   with HTML markup removed.

             'When editing an HTML message, .Body is not synchronized with
             '   .HTMLBody until .Body is accessed directly in some way.
             '   In these cases, changing .BodyFormat to olFormatPlain puts
             '   the empty .Body string in the editing buffer, discarding the
             '   actual message content.  That's why we explicitly reference
             '   .Body below.

             strFmt = itmCur.Body
             strFmt = "HTML"
         Else
             strFmt = "other"
         End If

         intRes = MsgBox("This will convert the message to plain text format." + vbCrLf + "All " + strFmt + " formatting will be lost.", vbOKCancel + vbExclamation, "Conversion Warning")
         If intRes = vbOK Then
             itmCur.BodyFormat = Outlook.olFormatPlain
         Else
             Exit Sub
         End If
     End If

     Shell ("C:\emacs-21.3\bin\gnudoit.exe (progn (mno-edit-outlook-message) (select-frame-set-input-focus (selected-frame)))")

 End Sub

I was completely unable to get GNU Emacs to respond to the VBA function, but I did get XEmacs to work. The Shell call has to be changed to

     Shell ("C:\Progra~1\XEmacs\XEmacs-21.4.19\i586-pc-win32\gnuclient.exe -e (progn (mno-edit-outlook-message) (focus-frame (selected-frame)))")

where the required change is to use the focus-frame function. I decided to use gnuclient instead of gnudoit, as that's what the man pages say to do nowadays.

In XEmacs' init.el I uncommented the (gnuserv-start) call; as the comment in the default init.el says, this technique works if you only run one instance of XEmacs at a time (if you run multiple instances, only one should run gnuserv, and that should be specified either on the command line when starting up that instance or interactively once the instance is running).

I install the gnus package thinking that would get me message-mode, but it doesn't; XEmacs' mail-mode seems to be slightly different -- one thing I notice is that mail-mode line wraps automatically, which I don't like to do in Outlook, and I can't seem to find how to turn off auto-wrapping.

Also, the replace-regexp-in-string call seems to need to modified to replace "\n\n\n" with "\n", rather than removing "\r".

- Albert Davidson Chou


dcd great stuff. if you add the following line right after (message-mode) in outlookedit.el, you'll get the same binding you'd normally use for a gnuclient buffer, i.e. C-x# to close the buffer and exit:

 (message-mode); .....
 (local-set-key "\C-x#" 'mno-save-outlook-message)

I also had to change the double backslashes to forward slashes in these lines:

 (defvar mno-get-outlook-body
   "tclsh C:/usr/share/Tcl/scripts/grabOutlookMessage.tcl")
 (defvar mno-put-outlook-body
   "tclsh C:/usr/share/Tcl/scripts/putMessageInOutlook.tcl")

- maybe because I have my shell set to cygwin bash rather than cmd.exe, but I think because this is a command line arg to tclsh.

gnuclient or gnudoit work for me in outlook 2003 and GnuEmacs with the annoying caveat that outlook complains someone is trying to access my email addresses every time.


Category Windows


dholm - 2011-02-20 16:16:14

I created a github repository based on this useful module. Please feel free to fork it and improve or contact me with patches.

Github: dholm/outlookedit