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 methodsPROPERTY
– indicates that the method can be called using standard property-access or property value assignment syntaxPROPERTYGET
– indicates that the method is called using standard property-access syntaxPROPERTYPUT
– indicates that the method is called using standard property value assignment syntaxPROPERTYPUTREF
– indicates that the method is called using property reference assignment syntaxFUNC
–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
.