locked
The Outlook Object Model and the Running Object Table RRS feed

  • Question

  • I'm using python to drive some outlook tests. I need the full outlook UI for the tests I'm doing. So from python, I start outlook with a call like

        subprocess.Popen(".../outlook.exe..")

    Then get an instance of an Outlook Object Model application with the call

        application = win32com.client.Dispatch("Outlook.Application")

    If there's sufficient time between the two calls, everything works great. But if there's not, OOM is not "ready" and a second instance of outlook is started with the "/embedding" option. And that breaks my tests.

    I've tried a variety of ways to wait long enough for OOM to be ready but nothing was working. Finally I tried the Running Object Table and it seems to work well except for one problem. It seems like for outlook 2007, 2010 and 2013, it takes a window focus change to get outlook to register itself in the ROT. For example, clicking on the desktop removes focus from outlook and at that point, the ROT is updated.

    Two questions:

    1) Is there a robust way to know that OOM is ready and can be accessed from a non-outlook process?

    2) Any idea how to get outlook to update the ROT without a focus change?

    Tuesday, April 7, 2015 8:30 PM

Answers

  • When outlook starts, it does not register itself in the running object table. That's different than other Office apps like word and excel. Those apps immediately register themselves. What outlook does, or doesn't do, sounds like a bug. It does register itself when there's some kind of UI focus change. For example, clicking on the desktop.

    I tried simulating a mouse click or keyboard entry to cause a focus change but it was unreliable.

    To solve the problem, I created a simple outlook add-in that does what outlook doesn't do - register the Outlook::Application in the ROT. The Outlook::Application is passed to the add-in in its implementation of OnConnection. Then in OnStartupComplete, I did the following:

       CLSID clsid;
       CLSIDFromProgID(L"Outlook.Application", &clsid); 
       DWORD registrationId;
       RegisterActiveObject(m_Application, clsid, ACTIVEOBJECT_STRONG, &registrationId);

    It works! I could probably done the RegisterActiveObject in OnConnection but thought I'd wait for other add-ins to be loaded. If the Outlook::Application is already registered, RegisterActiveObject returns with an error but that's fine. So, python launches outlook with its UI, sits is a loop waiting for Outlook to appear in the ROT then creates the instance of Outlook::Application that it needs. This happens almost immediately, does not require a UI focus change and does not create a second instance of Outlook.

    • Marked as answer by Chuck Bohling Saturday, April 18, 2015 1:42 AM
    Saturday, April 18, 2015 1:42 AM

All replies

  • Hello Chuck,

    Only one Outlook instance can be run at the same time.

    1. I bielive the simplest way is to call any member from the Outlook object model. If you get an exception in the code - well, it is not a convenient time :)

    2. You can use Windows API functions to switch forth and back. The Outlook object model doesn't provide anything for that.

    You may find the Automating Outlook from Other Office Applications article helpful.

    Tuesday, April 7, 2015 8:40 PM
  • Thanks for your reply

    The problem is that before you can call anything in OOM you must first make the call:

        application = win32com.client.Dispatch("Outlook.Application")

    If there's already an instance of outlook started by

        subprocess.Popen(".../outlook.exe..")

    i.e. CreateProcess, and outlook's not "ready", a second instance is created to handle the "Outlook.Application". Outlook will sometimes show an error dialog saying "you can't do that" and the application creation fails. And at that point, it's too late.

    Tuesday, April 7, 2015 9:33 PM
  • Chuck,

    Does python have a sleep function that you can invoke in order to give Outlook time to initialize?

    Tuesday, April 7, 2015 10:01 PM
  • yes, but I'm trying to avoid that. If I do a sleep(5), everything works. Of course the problem is how long to wait. That's why I'm looking for the outlook event "I'm ready for OOM".
    Tuesday, April 7, 2015 10:16 PM
  • The Outlook object model doesn't provide anything for that. You need to use sleep statements. Or handle exceptions and try anew after the sleep statement. 
    Wednesday, April 8, 2015 6:26 AM
  • Hi Chuck Bohling,

    According to the description, there is an execption when you get the Com object which is not ready for using.

    Based on my understanding, we can start an Outlook applicaiton using Outlook object instead of staring the process from .exe program.

    For example, here is the code using VBA to start the Outlook applicaiton:

    Set outlookApp = CreateObject("Outlook.Application", "")

    Since I am not familiar with Python, I am not suer whether there is a corresponding way to this method. I would suggest that you get more effective response from Python.

    If not, we may consider writing a loop to check the status of whether Outlook is started and if not then start it again.

    Hope it is helpful.

    Regards & Fei


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Thursday, April 9, 2015 2:11 AM
  • This isn't really an answer. You're basically doing the same thing with VB that I'm doing with python. Outlook must be started from some other process (Python for example) so that the process can test our add-in using Outlook's UI. After starting outlook, the process will then get an instance of "Outlook.Application". Since outlook has not registered itself in the running object table, creating the instance causes a second instance of outlook to be started. And that then results in errors.

    I have a solution to this and will post it in the another reply.

    Saturday, April 18, 2015 1:23 AM
  • When outlook starts, it does not register itself in the running object table. That's different than other Office apps like word and excel. Those apps immediately register themselves. What outlook does, or doesn't do, sounds like a bug. It does register itself when there's some kind of UI focus change. For example, clicking on the desktop.

    I tried simulating a mouse click or keyboard entry to cause a focus change but it was unreliable.

    To solve the problem, I created a simple outlook add-in that does what outlook doesn't do - register the Outlook::Application in the ROT. The Outlook::Application is passed to the add-in in its implementation of OnConnection. Then in OnStartupComplete, I did the following:

       CLSID clsid;
       CLSIDFromProgID(L"Outlook.Application", &clsid); 
       DWORD registrationId;
       RegisterActiveObject(m_Application, clsid, ACTIVEOBJECT_STRONG, &registrationId);

    It works! I could probably done the RegisterActiveObject in OnConnection but thought I'd wait for other add-ins to be loaded. If the Outlook::Application is already registered, RegisterActiveObject returns with an error but that's fine. So, python launches outlook with its UI, sits is a loop waiting for Outlook to appear in the ROT then creates the instance of Outlook::Application that it needs. This happens almost immediately, does not require a UI focus change and does not create a second instance of Outlook.

    • Marked as answer by Chuck Bohling Saturday, April 18, 2015 1:42 AM
    Saturday, April 18, 2015 1:42 AM
  • Chuck,

    I can't say if there will be any negative side effects from your solution, but you should be aware of the following from the MSDN documentation of the ROT --

    "Registering a second object with the same moniker, or re-registering the same object with the same moniker, creates a second entry in the ROT. In this case, Register returns MK_S_MONIKERALREADYREGISTERED. Each call to Register must be matched by a call to IRunningObjectTable::Revoke because even duplicate entries have different pdwRegister identifiers. A problem with duplicate registrations is that there is no way to determine which object will be returned if the moniker is specified in a subsequent call to IRunningObjectTable::IsRunning."

    "Objects registered in the ROT must be explicitly revoked when the object is no longer running or when its moniker changes. This revocation is important because there is no way for the system to automatically remove entries from the ROT. You must cache the identifier that is written through pdwRegister and use it in a call to IRunningObjectTable::Revoke to revoke the registration. For a strong registration, a strong reference is released when the objects registration is revoked."

    It might be overkill, but you could create your own moniker and register it in the ROT to avoid duplication. 

    Perhaps some folks with more COM/Automation expertise could shed additional light on the duplication risk.

    Out of curiosity I did create my own moniker and used it for ROT registration in an add-in.  Following are the C++ snippets I included in the add-in and a small console program I used as proof of the concept.

    Registering in the ROT during add-in Startup:

        CComPtr<IMoniker>    pMon;
        CComPtr<IRunningObjectTable> pRot;

        //m_pApp is the Outlook _Application interface pointer received in OnConnection

        hr = GetRunningObjectTable(0, &pRot);

        hr = CreateItemMoniker(L"!", L"MyOutlookMoniker", &pMon);

        
        if( pRot && pMon )
            hr = pRot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, m_pApp, pMon, &m_dwRegister);

    Revoking the ROT Registration during addin shutdown:

        if (m_dwRegister != 0)
        {
            CComPtr<IRunningObjectTable> pRot;

            HRESULT    hr = GetRunningObjectTable(0, &pRot);

            hr = pRot->Revoke(m_dwRegister);

            m_dwRegister = 0;
        }

    Console app as proof of concept:

    // RotTester.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"

    using namespace Outlook;
    using namespace std;


    int _tmain(int argc, _TCHAR* argv[])
    {
        HRESULT    hr = E_FAIL;
        int    x;

        HINSTANCE hInst = ShellExecute(NULL, L"open", L"C:\\Program Files\\Microsoft Office\\Office15\\Outlook.exe", NULL, NULL, SW_SHOWNORMAL);
        
        hr = CoInitialize(NULL);

        if (SUCCEEDED(hr))
        {
            CComPtr<IRunningObjectTable>    pRot;
            CComPtr<IMoniker>    pMon;
            CComPtr<IUnknown> pUnk;
            CComQIPtr<_Application> pApp;
            int    count = 0;

            hr = CreateItemMoniker(L"!", L"MyOutlookMoniker", &pMon);

            hr = GetRunningObjectTable(0, &pRot);

            do
            {
                hr = pRot->GetObject(pMon, &pUnk);
                count++;
                Sleep(500);
            } while (!pUnk && count < 1000);

            pApp = pUnk;

            if (pApp)
            {
                CComBSTR    bstrVersion;

                hr = pApp->get_Version(&bstrVersion);

                if (bstrVersion)
                    wcout << L"Loop count was " << count << L" and Outlook Version is " << (wchar_t*)bstrVersion << endl;
            }
            else
                wcout << L"Failed to connect to Outlook" << endl;


        }

        CoUninitialize();

        wcout << L"Press 0 to close" << endl;

        wcin >> x;

        return 0;
    }


    • Edited by RLWA32 Sunday, April 19, 2015 4:04 AM
    Saturday, April 18, 2015 1:09 PM
  • Thanks for the info. This is great!!!

    The problem I'm having is with an automated python test suite which is not production code. And that means I can be a little less than perfect. I did add the check for MK_S_MONIKERALREADYREGISTERED and to RevokeActiveObject in that case. But for now, as you said, registering my own moniker is overkill. If I run into any problems with the test suite, then I'll implement my own moniker.

    Thanks again.

    Wednesday, April 29, 2015 5:16 PM
  • Hi

    We have COM marshal failures regularly with Outlook 365 and Office 16.0 object library in a Winforms vb application.

    Your code could prove useful for a solution!

    Where is the OnStartupComplete event ? Is that unmanaged code above ?

    Thursday, November 23, 2017 3:51 PM
  • Hi

    We have COM marshal failures regularly with Outlook 365 and Office 16.0 object library in a Winforms vb application.

    Your code could prove useful for a solution!

    Where is the OnStartupComplete event ? Is that unmanaged code above ?

    What I posted was native C++ code using ATL in connection with an unmanaged COM add-in.  See the IDTExtensibility2 interface for OnStartupComplete.
    Thursday, November 23, 2017 4:30 PM