Pragmatic Development Notes

2008-12-17

With A Little Help From Ruby

Filed under: C++,General,Ruby & RubyOnRails — Boško Ivanišević @ 9:20
Tags: , , , , , , , ,

Recently I had to work with Outlook and its OLE objects. More precisely I had to handle drag and drop of Outlook folders into an ActiveX control made in C++. Anyone who has ever worked with OLE in C++ knows that it is anything else but fun. Debugging support in Visual Studio is quite poor. Since it is impossible to see even values of properties and Visual Studio will not allow you to call OLE methods neither in Watch nor in Quick Watch windows you are forced to trace messages and that enlarges code one have to type.

It is needlessness to say that Microsoft’s documentation about Otlook’s folders drag and drop was of no help. The most I could find is that Outlook can handle this and that is the way how you can send someone a link to your Inbox on the Exchange server. Not so useful isn’t it?

Examining available clipboard formats that Outlook sets during folder drag, I’ve found out that I can get a file with the .xnk extension. Unfortunately those are binary files with no explanation of their format. One more problem on the list to be solved, but at least something to start with. Quick look at the file in hexl-mode in Emacs revealed that 20 of last 24 bytes of the file keep something that looks like ID.

Hexadecimal view of Outlook's .xnk file

Hexadecimal view of Outlook's .xnk file

But I had to check that. It was time to start up Visual Studio and make a new project where I’ll be able to test it, right? Well, not exactly. Let’s see what would procedure for testing ideas look like in C++. First I would have to make OLE wrapper classes around Outlook’s objects. I’ve tried that several times before and for some reason Visual Studio didn’t add all methods and properties to wrapper classes. But if we assume that main methods would be implemented (which, naturally, turns out to not to be true) we would have something to start with. Since it is unknown which classes we would need we’ll have to make wrappers incrementally.

Next step would be to extract ID from .xnk file and to try to get Outlook folder. And at that early stage we are entering unknown. Should we use Folders collection and iterate over each member of collection or there is some shortcut? Searching through documentation will give us an answer – we can use Outlook’s Session GetFolderFromID method. But there is method with the same name in Namespace object too. Which one should we use and do both of them return same object? Obviously we would have to try both.

That means creating wrapper classes for Namespace and Session objects, call their GetFolderFromID methods and trace some properties of the resulting object. And if we missed, we would have to go through implement-build-debug procedure again. Moreover there is a big chance that some of the methods we need will be missing in wrapper classes. In that case we will have to find method’s dispatch ID, its parameters and to implement method call through InvokeHelper (there are other ways too, but it is not relevant to this article) manually.

Obviously further experimenting in C++ would be time consuming and I needed something faster. It was time to fire up Emacs and to experiment with Ruby. I’ve created class which I’ll use to work with Outlook’s OLE interfaces.

require 'win32ole'

class Outlook

  def initialize
    @outlook_app = WIN32OLE.new('Outlook.Application')
    @mapi_namespace = @outlook_app.GetNameSpace('MAPI')
  end

end

Initialization method will create necessary members. First one to work with Outlook application and the second one is MAPI Namespace object which is needed to get and create messages, folders and other Outlook’s objects.

Next step was to load .xnk file, extract ID and to try to get Outlook folder for it.

  # Returns Outlook item defined by ID passed in
  # the argument during function call.
  def get_item(item_id)
    begin
      item = @mapi_namespace.GetItemFromID(item_id)
    rescue WIN32OLERuntimeError
      item = nil
    end

    return item unless item.nil?

    begin
      item = @outlook_app.Session.GetFolderFromID(item_id)
    rescue WIN32OLERuntimeError
      item = nil
    end

    item
  end

  # Loads .xnk file (got during drag n' drop of Outlook
  # folder) and extracts ID of the folder.
  def get_folder_from_xnk(path_to_xnk)
    puts("Processing #{path_to_xnk}...")
    xnk_size = File.size(path_to_xnk)
    puts("File size is #{xnk_size}")
    xnk_file = File.new(path_to_xnk)
    # Read last 28 characters
    xnk_file.pos = xnk_size - 28
    all_xnk = xnk_file.read(28).unpack('H*')
    # Entry ID is represented by first 48 array members
    item_id = all_xnk[0][0..47]
    puts("Folder id: #{item_id}")
    folder = get_item(item_id)
  end

First method creates either Outlook item or Outlook folder. Since class will not handle only folders with ID given in .xnk file, but also messages in those folders we need one method which we can use to extract them. Second method reads .xnk file, extracts folder ID and returns Outlook’s folder.

Now when we have folder it is not hard to extract items stored in it. Here is complete class:

# This is utility class for manipulating and
# examining MS Outlook content.
#
# Author::    Bosko Ivanisevic (bosko.ivanisevic at gmail.com)

require 'win32ole'

class Outlook

  OL_FOLDER = 2

  def initialize
    @outlook_app = WIN32OLE.new('Outlook.Application')
    @mapi_namespace = @outlook_app.GetNameSpace('MAPI')
  end

  # Returns array of messages found in the folder.
  def get_folder_messages(folder)
    messages = Array.new

    begin
      return messages unless OL_FOLDER == folder.Class

      table = folder.GetTable
      while (!table.EndOfTable)
        row = table.GetNextRow
        message_id = row.GetValues[0]
        msg = @mapi_namespace.GetItemFromID(message_id)
        messages << msg unless msg.nil?
      end
    rescue
      puts "Error! Argument is not Outlook folder"
    end

    messages
  end

  # Method returns Hash of folder content. Folder name is
  # stored under :name key. All sub-folders are stored in
  # the array under :folders key, and all messages are in
  # the array under :messages key.
  def get_folder_content(folder)
    content = Hash.new
    return content if ( folder.nil? || folder.Class != OL_FOLDER)
    content[:name] = folder.Name

    content[:folders] = Array.new
    1.upto folder.Folders.Count do |index|
      sub_folder_content = get_folder_content(folder.Folders(index))
      content[:folders] << sub_folder_content
    end

    content[:messages] = get_folder_messages(folder)

    content
  end

  # Regurns Outlook item defined by ID passed in
  # the argument during function call.
  def get_item(item_id)
    begin
      item = @mapi_namespace.GetItemFromID(item_id)
    rescue WIN32OLERuntimeError
      item = nil
    end

    return item unless item.nil?

    begin
      item = @outlook_app.Session.GetFolderFromID(item_id)
    rescue WIN32OLERuntimeError
      item = nil
    end

    item
  end

  # Loads .xnk file (got during drag n' drop of Outlook
  # folder) and extracts ID of the folder.
  def get_folder_from_xnk(path_to_xnk)
    puts("Processing #{path_to_xnk}...")
    xnk_size = File.size(path_to_xnk)
    puts("File size is #{xnk_size}")
    xnk_file = File.new(path_to_xnk)
    # Read last 28 characters
    xnk_file.pos = xnk_size - 28
    all_xnk = xnk_file.read(28).unpack('H*')
    # Entry ID is represented by first 48 bytes
    item_id = all_xnk[0][0..47]
    puts("Folder id: #{item_id}")
    folder = get_item(item_id)
  end
end

This simple yet powerful class can be used for lot of Outlook content handling. Although it uses only small number of OLE interfaces and is made for specific type of problem it can be easily extended to perform even more complex tasks. Naturally if you want to see what you can do with Outlook automation you should visit MSDN Outlook Object Model Reference.

Bottom line is that using Ruby (or some other scripting language) can really speed up a lot development of applications that use OLE objects. Experimenting with Outlook automation in C++ would be very time consuming. Just imagine all those implement-build-debug circles needed to find out that you used wrong OLE object. And even MSDN documentation looks quite detailed, once you dig into the problem you realize that lot of things are missing or poorly explained. Even more, most of their samples are written in VB and very few mention C++.

Just to avoid misunderstanding, I do not want to diminish power of C++ here. Personally I think that it is still one of the most powerful programming languages. At the end, my final implementation was in C++. The whole idea was to give a new perspective on the process of development which can be accelerated using some helper tools.

This is not the end of story. Ruby gives you a lot more possibilities for exploring OLE objects. Just to mention few:

# Returns array with names of all OLE methods...
my_ole_object.ole_methods
# ... or get properties...
my_ole_object.ole_get_methods
# ... and, of course, put properties
my_ole_object.old_put_methods

Further you can get Ruby’s WIN32OLE_METHOD from the object and explore its properties:

# Get WIN32OLE_METHOD
method = my_ole_object.ole_method(method_name)

# Now you can get its dispatch ID that is needed
# if you want to call it from C++ through InvokeHelper
disp_id = method.dispid

# Or get methods return_type...
ret_type = method.return_type

# ... number of parameters
param_no = method.size_params

# ... number of optional parameters
opt_param_no = method.size_opt_params

For further details you can look at Ruby’s win32ole library documentation.

For my mind it is good way to go. Using Ruby for exploring OLE object you are not familiar with will result in fully functional prototype much faster than if we try to do the same in C++. Once we have prototype in Ruby turning it in final C++ code is quite straightforward and much easier.

Although complete article is about Outlook, similar approach can be used with any of Office applications: Word, Excel or PowerPoint. It also shows that Ruby can be not only helper, but also very powerful Office automation tool.

Advertisements

1 Comment »

  1. All of us that has to deal with this automatise MS Office product projects can benefit from this kind of knowledge sharing.
    Not only the that we have to for fill the emptiness in documentation but also to be able to get hands on tools that can help us throughout development process. It’s common knowledge that rapid prototyping is secure way to successful product competition. This article state it very clear.

    Thank you, once again, for shedding light on this subject.

    Comment by Dejan Dimic — 2008-12-17 @ 22:12


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: