Ruby and OLE

Object Linking and Embedding (OLE) is technology that enables an application to create compound documents. Using OLE any document can contain visual information from various sources. OLE-enabled application, without knowing internal data structure, is capable to display spreadsheet table, video, sound and numerous other formats. Moreover these objects preserve their properties and if user wants to change them, Windows will activate originating application. OLE was introduced to overcome problems with traditional cut and paste approach which used data transforming in a way that host application understands and can display.

Later, technology evolved to OLE 2 based on Component Object Model (COM), binary-interface standard for inter-process communication and dynamic object creation. COM was intended for use by various scripting languages. Nowadays it can be used from almost all languages that run on Windows: C, C++, Visual Basic and, of course, Ruby.

Using OLE in Ruby scripts is managed through win32ole extension. We'll start exploring Ruby and OLE objects through couple one-liners which will give us good starting insight in OLE objects that exist in operating system. Later on we will see what additional data about existing OLE objects we can collect from Ruby. Finally we will learn how to automate few Windows applications through Ruby scripts.

Let's start with one simple one-liner which will print out all OLE objects registered in the system:

c:\>ruby -rwin32ole -e "puts WIN32OLE_TYPE.typelibs.sort"
AP Client 1.0 HelpPane Type Library
AP Client 1.0 Type Library
ATL 2.0 Type Library
ATLContactPicker 1.3 Type Library
ATLEntityPicker 1.0 Type Library
AccessibilityCplAdmin 1.0 Type Library
Active DS Type Library
ActiveMovie control type library
AgControl 5.1 Type Library
AppIdPolicyEngineApi 1.0 Type Library
Assistance Platform Client 1.0 Data Services Type Library
BCS Launcher 2.0 Type Library
BdeUISrv 1.0 Type Library
BinaryInfo 1.0 Type Library
Bined package 11.0 Type Library
Bined package 12.0 Type Library
Bined package 8.0 Type Library
:    :     :

What we did with this one-liner? We told ruby to load win32ole extension (-r win32ole) and to print array of names of all type libraries registered in the system (-e “puts WIN32OLE_TYPE.typelibs.sort”). Type libraries contain metadata about COM types. Ruby reads this data with the help of another win32ole class – WIN32OLE_TYPELIB. This class' internal method reads entries from the registry key HKCR\TypeLib and collects all properties of registered type libraries like name, version and path on the disk. WIN32OLE_TYPE extracts these type libraries names and stores them in the array. Knowing this we can use WIN32OLE_TYPELIB class to display more information about existing COM objects:

c:\>ruby -rwin32ole -e 'puts "#{WIN32OLE_TYPELIB.typelibs[0].name}:  #{WIN32OLE_TYPELIB.typelibs[0].path}"'

Microsoft ActiveX Data Objects 2.0 Library:  C:\Program Files (x86)\Common Files\System\ado\msado20.tlb

This time we displayed not only the name but also the path of the type library on the file system. Information obtained through WIN32OLE_TYPELIB is not of much use, but it can be good starting point for exploring OLE objects that exist in the system.

Each type library usually contains more classes. List of classes are returned by ole_classes, WIN32OLE_TYPE's class method which accepts one argument, name of type library:

c:\>ruby -rwin32ole -e "puts WIN32OLE_TYPE.ole_classes('Microsoft Scripting Runtime')"
CompareMethod
IOMode
Tristate
FileAttribute
:    :    :
FileSystemObject
Drive
Drives
Folder
Folders
File
Files
TextStream
IScriptEncoder
Encoder

Method ole_classes returns array of WIN32OLE_TYPE objects but Ruby's puts method, when called with array as an argument, iterates over all objects in the array and calls to_s method for each member of array. WIN32OLE_TYPE objects return name of the OLE type as a result of to_s method. But there is much more information that WIN32OLE_TYPE object contains then just a name as you can see from the irb session displayed below.

C:\>irb -r win32ole
irb(main):001:0> WIN32OLE_TYPE.ole_classes("Microsoft Scripting Runtime")[22].name
=> "FileSystemObject"
irb(main):002:0> WIN32OLE_TYPE.ole_classes("Microsoft Scripting Runtime")[22].guid
=> "{0D43FE01-F093-11CF-8940-00A0C9054228}"
irb(main):003:0> WIN32OLE_TYPE.ole_classes("Microsoft Scripting Runtime")[22].progid
=> "Scripting.FileSystemObject"

As you can see name of the OLE type is exactly the one displayed in the command prompt when we printed array of classes in Microsoft Scripting Runtime type library. Further, we see this class' GUID and ProgID. Globally unique identifier (GUID) is 32 character hexadecimal string which uniquely identifies OLE type. ProgID is string that corresponds to GUID and also identifies OLE type. Both of these values can be used when we instantiate object of particular OLE type.

Let's create new OLE object now and check what data about OLE type we can get from it.

c:\>irb -rwin32ole
irb(main):001:0> fso = WIN32OLE_TYPE.new("Microsoft Scripting Runtime", "FileSystemObject")
=> #<WIN32OLE_TYPE:FileSystemObject>
irb(main):002:0> fso.ole_methods.length
=> 34
irb(main):003:0> fso.ole_methods[17].invoke_kind
=> "FUNC"
irb(main):004:0> fso.ole_methods[17].return_type
=> "BOOL"
irb(main):005:0> fso.ole_methods[17].name
=> "FileExists"
irb(main):006:0> fso.ole_methods[17].helpstring
=> "Check if a file exists"
irb(main):007:0> params = fso.ole_methods[17].params
=> [#<WIN32OLE_PARAM:FileSpec>]
irb(main):008:0> params[0].input?
=> true
irb(main):009:0> params[0].ole_type
=> "BSTR"
irb(main):010:0> params[0].name
=> "FileSpec"
irb(main):011:0> params[0].default
=> nil

In the first statement we are creating WIN32OLE_TYPE object, FileSystemObject, from Microsoft Scripting Runtime type library. In the next step we are reading number of OLE methods that are defined in the OLE object. Statements that follow display various properties of one particular OLE method.

The first one shows that this OLE method is actually a function. Possible values that can be returned from invoke_kind are:

  • UNKNOWN – for undefined OLE methods
  • PROPERTY – indicates that the method can be called using standard property-access or property value assignment syntax
  • PROPERTYGET – indicates that the method is called using standard property-access syntax
  • PROPERTYPUT – indicates that the method is called using standard property value assignment syntax
  • PROPERTYPUTREF – indicates that the method is called using property reference assignment syntax
  • FUNC –indicates that the method is called using standard function invocation syntax

Later commands display method's name, return type and help string. Finally, information about method's parameters are examined.

We can now extend our Ruby on Rails application to display information about registered OLE objects. Since we are gathering data directly from the system we will not need new database model. Go to the rwin_book application directory and invoke one of Rails generators that will create new controller with index method only:

c:\projects\rwin_book>rails generate controller OleExplorer index
      create  app/controllers/ole_explorer_controller.rb
       route  get 'ole_explorer/index'
      invoke  erb
      create    app/views/ole_explorer
      create    app/views/ole_explorer/index.html.erb
      invoke  test_unit
      create    test/controllers/ole_explorer_controller_test.rb
      invoke  helper
      create    app/helpers/ole_explorer_helper.rb
      invoke    test_unit
      create      test/helpers/ole_explorer_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/ole_explorer.js.coffee
      invoke    scss
      create      app/assets/stylesheets/ole_explorer.css.scss

The generator logs everything to console. From this point we can proceed in several ways. Code that extracts OLE information can all be part of the new controller. We can also put it in the ole_explorer_helper.rb file or, as we will do, create new library and put complete OLE related code there.

Create new file in the lib directory within your application's directory and put following code in it:

require 'win32ole'

module WinOle
  class OleExplorer
    def typelibs
      @typelibs ||= WIN32OLE_TYPELIB.typelibs.select {|tl| tl.name unless tl.name.length == 0}
    end

    def ole_classes(typelib)
      begin
        WIN32OLE_TYPE.ole_classes(typelib).collect {|oc| oc.name}.sort
      rescue
        []
      end
    end

    def ole_members(typelib, klass)
      begin
        ot = WIN32OLE_TYPE.new(typelib, klass)
        members = (ot.ole_methods + ot.variables).collect{|om| om.name}.sort
        {:class=>ot, :members=>members}
      rescue
        {:members=>[]}
      end
    end

    def ole_member(typelib, klass, member)
      begin
        ot = WIN32OLE_TYPE.new(typelib, klass)
        (ot.ole_methods + ot.variables).find {|mem| mem.name == member}
      rescue
      end
    end
  end
end

We created new WinOle module with class OleExplorer in it. The first method typelibs returns an array of names of all type libraries found in the system. The second one returns an array with names of all classes defined in a type library whose name is passed as an argument. Next one returns hash which has two keys if requested class is found in the type library. First key, :class, has WIN32OLE_TYPE object as a value, and the second, :members, contains an array with names of all members of the class. When requested class is not found method returns only empty array in the :members key. Finally the last method returns member of the class or nil. This member can be WIN32OLE_METHOD or WIN32OLE_VARIABLE object.

With this library in place our OleExplorerController can use WinOle::OleExplorer class to collect OLE data and display them to the user in the view. This trivial example will use three drop-down select boxes for choosing type library, class and member of the class. In the controller we will use OleExplorer class to fetch all type libraries registered in the system.

require "ole_explorer"

class OleExplorerController < ApplicationController
  def index
    @typelibs = ole_explorer.typelibs.collect{|tl| tl.name}.sort
  end

  def ole_explorer
    @ole_explorer ||= WinOle::OleExplorer.new
  end
end

In the template file index.html.erb we will use instance variable @typelibs to display all type libraries in the select box.

<h1>OleExplorer</h1>
<%= select "typelib", "typelib", @typelibs, {:prompt=>"Select Type Library"}  %>

If you now start rails server and load http://localhost:3000/ole_explorer/index page you will see select box filled with all type libraries registered in the system (Figure 12).

""

Next we want to read all classes in a type library whenever user changes selected type library. We will do that through Ajax request. Let's add JavaScript change event handler on our select box. To keep things simple we will put all JavaScript code directly in application.js file.

$(document).ready(function() {
  $("#typelib_typelib").bind("change", function(event) {
    $.ajax({
      method: 'get',
      url: '/ole_explorer/update_classes',
      data: { typelib: $(this).val() },
      dataType: 'script'
    });
  });
});

New JavaScript method will send GET request to the OleExplorer controller, call update_classes method and pass the name of the selected type library. We are still missing update_classes method in the controller so let's add it now:

def update_classes
  @ole_classes = WinOle::OleExplorer.new.ole_classes(params[:typelib])
end

New method will collect names of all classes in the type library. Since we are sending Ajax request Rails will automatically search for update_classes.js.erb view file which it will render. This file can be used to execute JavaScript on the client and to replace HTML code on the page. Create new view file update_classes.js.erb and add following code to it:

$("#ole_classes").html('<%= escape_javascript(render 'ole_classes') %>');
$("#ole_class_ole_class").bind("change", function(event) {
  $.ajax({
    method: 'get',
    url: '/ole_explorer/update_members',
    data: { ole_class: $(this).val(), typelib: $('#typelib_typelib').val() },
    dataType: 'script'
  });
});

Before we can check our new code we have to create partial template _ole_classes.html.erb in the app/views/ole_explorer directory and add new route to routes.rb file. Go ahead and create new partial and add following code to it.

<%= select "ole_class", "ole_class", @ole_classes, {:prompt=>"Select OLE class"} %>

Finally change routes.rb:

RwinBook::Application.routes.draw do
  get "ole_explorer/index"
  get "ole_explorer/update_classes"

  resources :ruby_win_sources
end

change index method in OleExplorerController:

def index
  @typelibs = ole_explorer.typelibs.collect{|tl| tl.name}.sort
  @ole_classes = []
end

and add the following lines to the end of the index.html.erb file:

<div id="ole_classes">
  <%= render :partial => 'ole_classes', :object => @ole_classes %>
</div>

Now reload the page in the browser and new select box will be displayed, filled with new names of classes whenever you change selected type library.

In the next iteration of improving this quite trivial Rails example we want to display general information about selected class, its members and information about selected member within the class. Let's first add two new partial templates. The first one, _ole_members.html.erb, will display general class information and select box with names of all members of selected class.

<% if @ole_members[:class] %>
  <h3>Class Info</h3>

  <p>Name: <b><%= @ole_members[:class].name %></b></p>
  <p>GUID: <b><%= @ole_members[:class].guid %></b></p>
  <p>ProgID: <b><%= @ole_members[:class].progid %></b></p>
  <p>Desc: <b><%= @ole_members[:class].helpstring %></b></p>
<% end %>
<%= select("ole_member", "ole_member", @ole_members[:members],
           {:prompt=>"Select OLE member"}) %>

Again we have to handle selected member change so we need to add following code to update_members.js.erb file:

$("#ole_members").html('<%= escape_javascript(render 'ole_members') %>');
$("#ole_member_ole_member").bind("change", function(event) {
  $.ajax({
    method: 'get',
    url: '/ole_explorer/member_info',
    data: { member: $(this).val(),
            ole_class: $('#ole_class_ole_class').val(),
            typelib: $('#typelib_typelib').val() },
    dataType: 'script'
  });
});

Below the heading we display class name, GUID, ProgID and help string. After that we fill select box with all members of selected class. Similarly to previous two select boxes we are using onchange event to send Ajax request to the server whenever selected member is changed. This time we are sending three values in the request: names of member, class and type library.

Second partial, _member_info.html.erb, displays information about requested member:

<% unless @member.nil? %>
  <h3>Member info</h3>
  <% if @member.is_a? WIN32OLE_METHOD %>
    <% if @member.event? %>
      <p><i>Event</i></p>
      <p>Event interface: <b><%= @member.event_interface %></b></p>
    <% end %>
    <p>Name: <b><%= @member.name %></b></p>
    <p>Invoke: <b><%= @member.invoke_kind %></b></p>
    <p>Returns: <b><%= @member.return_type %></b></p>
    <p>Dispatch ID: <b><%= @member.dispid %></b></p>
    <p>Help string: <b><%= @member.helpstring %></b></p>
    <p><i>Arguments</i></p>
    <table border="1">
      <tr><th>Name</th><th>Type</th><th>Usage</th></tr>
      <% @member.params.each do |param| %>
        <tr>
          <td><%= param.name %></td>
          <td><%= param.ole_type %></td>
          <% parinf = [] %>
          <% parinf << "In" if param.input? %>
          <% parinf << "Out" if param.output? %>
          <% parinf << "Optional" if param.optional? %>
          <td><%= parinf.join(',') %></td>
        </tr>
      <% end -%>
    </table>
  <% else %>
    <p><i>Variable</i></p>
    <p>Name: <b><%= @member.name %></b></p>
    <p>Kind: <b><%= @member.variable_kind %></b></p>
    <p>Type: <b><%= @member.ole_type %></b></p>
  <% end %>
<% end %>

On the server-side, values of parameters from Ajax request are extracted and used to read information about class' member. Full listing of code in the controller is given below.

require "ole_explorer"

class OleExplorerController < ApplicationController
  def index
    @typelibs = WinOle::OleExplorer.new.typelibs.collect{|tl| tl.name}.sort
    @ole_classes = []
    @ole_members = {:members=>[]}
    @member = nil
  end

  def update_classes
    @ole_members = { members: [] }
    @ole_classes = WinOle::OleExplorer.new.ole_classes(params[:typelib])
  end

  def update_members
    @ole_members = WinOle::OleExplorer.new.ole_members(params[:typelib],params[:ole_class])
  end

  def member_info
    @member = WinOle::OleExplorer.new.ole_member(params[:typelib],
      params[:ole_class],
      params[:member])
  end
end

Finally we have to add new partial templates to our index page:

<h1>OleExplorer</h1>
<%= select "typelib", "typelib", @typelibs, {:prompt=>"Select Type Library"}  %>

<div id="ole_classes">
  <%= render :partial => 'ole_classes', :object => @ole_classes %>
</div>

<div id="ole_members">
  <%= render :partial => 'ole_members' %>
</div>

<div id="member_info">
  <%= render :partial => 'member_info', :object => @member %>
</div>

handle Ajax call to member_info method (file member_info.js.erb)

$("#member_info").html('<%= escape_javascript(render partial: 'member_info') %>');

and to add two new routes:

get "ole_explorer/update_members"
get "ole_explorer/member_info"

Now we have full-featured page for exploring all registered OLE types in the system. Start Rails application and load the page. Output should look similar to the one shown in Figure 13.

""

Complete Rails code for this chapter can be found in RWin Book Code on the GitHub in the branch ole_explorer.