Automating .NET Executables using COM

Background

In one of my projects I needed to script a class inside a .NET executable. .NET has very good support for in-proc COM components: you create a class library, add a class, put [ComVisible] attribute on it, and voila - you've got yourself a COM object. Scripts would instantiate this component via a CreateObject call, which would locate the DLL and load it into the client's address space.

My scenario, however, was slightly different. I did not want a new instance of my component to be created inside the client process. I already had an instance in my own process, and I wanted to script it.

Sample Application and Quick Start

Sample application (26KB zip) demonstrates how to script a class defined directly in the executable and in a class library. All required binary files are in the bin directory. To use the sample

  1. Unzip the archive.
  2. Open Visual Studio Command prompt (run as Administrator on Vista)
  3. Navigate to the bin\Debug directory.
  4. regasm AutomationLib.dll
  5. regasm AutomateClassFromExe.exe
  6. Run AutomateClassFromExe.exe
  7. Run AutomateClassFromExe.vbs to script the object defined in the executable.
  8. Repeat for AutomateClassFromLibrary.exe

Important: on Vista make sure you run both the executable and the script either as administrator or not as administrator. If you mix modes, scripting will not work.

Choice of Scripting Technology

The scripts were supposed to be very short, so I wanted to use the simplest thing that works. Besides COM, one can communicate with a .NET executable via remoting, or a WCF-based technology (think web services). In this particular scenario COM beats the competitors hands down, because it takes care of almost all the plumbing. All you need is a class GUID and a prog ID, and you're golden. You don't have to worry about the ports, the IP addresses, the endpoint behavior, and so on, and so forth. WCF may provide security, flexibility, and cross-platform interoperability, but these are definitely not an issue, at least not for the price of mile-long configuration files.

How To

To expose an existing .NET object to the outside world, do the following:

  1. Make object's class COM Visible, give it a GUID and a Prog ID.
  2. Write code to put the object in the Running Objects Table (ROT) using provided RotRegistration class.
  3. Register the assembly containing the object for COM Interop using regasm tool.
  4. Run the application.
  5. Use GetObject call in your scripts to access the object.

Important: only one instance per class type is scriptable in this manner. If you want to script multiple instances of the same class, it also can be done using ROT and monikers, but this is beyond the scope of this article.

Making Your Object COM Visible

[ComVisible(true)]
[ProgId("Your.Prog.Id")]
[Guid("57B5039C-7C19-4ad1-A4E8-1EE77F448635")]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class Your_Class

Keep in mind that:

  • The class must be public.
  • GUIDs and Prog IDs cannot be reused!. You must create a new one for each class. Use Tools → Create GUID from Visual Studio menu.

Putting Your Object in ROT

By putting your object in Running Objects Table you declare it as an "active object" for the given COM class. There may be only one active object instance per class. While your object is in the ROT, it is accessible for scripting. Use RotRegistration class to put your object in the ROT.

using (new RotRegistration(myObject))
{
    // object is accessible for automation here
}

Register the Assembly Using Regasm

You must register the assembly that contains the scripted class. It is not necessary to register the main executable if the scripted class is in a referenced assembly. Regasm tool is supplied with Visual Studio. You can invoke it from Visual Studio command line. Running regasm requires administrative privileges - on Vista you must specifically run Visual Studio command line as administrator. Regasm can register both EXEs and DLLs.

In Visual Studio you can check "Register for COM Interop" checkbox on the "Build" tab of the project properties. However, this option is available only for DLLs. For EXEs you must use Regasm manually. Also, this option is not recommended on Vista for any kind of project (see below).

Run the application

Well, I am sure this one does not require an explanation.

Script Your Object

In VB Script you can use GetObject call to access your object by Prog ID. Your script may look as follows:

Dim obj 
Set obj = GetObject(, "Your.Prog.Id")
obj.MyMethod param1, param2

Notes for Windows Vista

On Windows Vista by default administrative actions such as registering an assembly with regasm require elevated privileges (even when invoked by the admin user) and will fail by default. In particular, if you check "Register for COM Interop" in Visual Studio, your build will fail unless you are specifically running Visual Studio in administrator mode.

You must also remember that appliations launched "as administrator" have their own security context, and a separate running objects table (ROT). Objects created in the administrator mode cannot be scripted from (and will not be visible to) the scripts running in normal mode.

Errors

If GetObject call cannot find the object in the ROT, it will return error 0x800A01AD. If you get this error, verify that the application you want to script is running and the object is registered. Visual Studio 6 shipped with utility called ROT viewer: it still works and is helpful, but unfortunately later versions of Visual Studio don't seem to include it.

Scope and Applicability

This technique is useful for quick scripting of your application for debugging or other internal purposes, when you control both the server and the client, and it can be very powerful as such. I would not recommend it as an enterprise-scale solution, for at least these reasons:

  1. It cannot span security contexts: both the application and the script must be run by the same user on the same machine.
  2. Registration for COM interop requires administrative privileges.
  3. This solution works best when both the application and the script are manually invoked by the user. The script will work only if the application is already running, and only one instance of the application can be scripted. This makes it difficult to build a really robust automatic script that would always work.

For more robust scripting of your .NET application, I would consider using the DLR, WMI, or exposing a web service.