3

How can I get a complete list of references to each running Excel application instance (regardless of their amount of workbooks and visibility state)?


I know that I can use the Windows API to find each Excel workbook window (which has the window class name EXCEL7), get their handles to use with the AccessibleObjectFromWindow function, then dispatch and get the application COM object.

Though that only works with the Excel application instances that have at least one workbook visible. How can I also get the Excel application instances that are hidden and/or don't have workbooks?

The Excel application instance window (which has the window class name XLMAIN) doesn't retrieve any accessible object.

I'm looking for an explanation, with or without pseudocode, or code of any programming language, as long as I'm able to understand and implement it myself (in Python).

user7393973
  • 2,270
  • 1
  • 20
  • 58
  • 1
    You could use [Excel.Application](https://learn.microsoft.com/en-us/office/vba/api/excel.application(object)), like: `ExcelApplication = GetObject(, "Excel.Application")`, then traverse `ExcelApplication.Workbooks` – Drake Wu Aug 25 '20 at 03:31
  • 1
    @DrakeWu-MSFT `GetObject` only returns the active instance. – user7393973 Aug 25 '20 at 07:04
  • You could list all processes and filter only those named as "Excel". Maybe this can help: https://stackoverflow.com/questions/26277214/vba-getting-program-names-and-task-id-of-running-processes – Foxfire And Burns And Burns Aug 27 '20 at 11:12
  • @FoxfireAndBurnsAndBurns I'm trying to access the COM objects. I don't think I can get them from the processes. – user7393973 Aug 27 '20 at 11:15

1 Answers1

7

I wanted to implement this in Python, though I asked the question without requiring the answer to be about Python since a general explanation (not having to be specific to a programming language) would probably be enough.

After looking at the source code of the GetObject VB function implemented in Python by the win32com.client module I noticed that it calls the Moniker function:

def Moniker(Pathname, clsctx = pythoncom.CLSCTX_ALL):
  """
    Python friendly version of GetObject's moniker functionality.
  """
  moniker, i, bindCtx = pythoncom.MkParseDisplayName(Pathname)
  dispatch = moniker.BindToObject(bindCtx, None, pythoncom.IID_IDispatch)
  return __WrapDispatch(dispatch, Pathname, clsctx=clsctx)

The MkParseDisplayName function lead me to the objbase.h header's functions where I found the GetRunningObjectTable function which I wasn't aware of.

After a while of searching multiple pieces of code about it and trying to put them together to do what I wanted without raising errors and making sure it only gets the Excel application instances (I added Microsoft Word to the code to show how to do it with other COM objects) without repeating, I put together the code below.

from pythoncom import CreateBindCtx as create_bind_context, GetRunningObjectTable as get_running_object_table, IID_IDispatch as dispatch_interface_iid
from win32com.client import Dispatch as dispatch

running_object_table = get_running_object_table()
bind_context = create_bind_context()
excel_application_class_clsid = '{00024500-0000-0000-C000-000000000046}'
word_application_class_clsid = '{000209FF-0000-0000-C000-000000000046}'
excel_application_clsid = '{000208D5-0000-0000-C000-000000000046}'
word_application_clsid = '{00020970-0000-0000-C000-000000000046}'
excel_applications = []

for moniker in running_object_table:
  name = moniker.GetDisplayName(bind_context, None)
  if all(clsid not in name for clsid in [excel_application_class_clsid, word_application_class_clsid]):
    continue
  unknown_com_interface = running_object_table.GetObject(moniker)
  dispatch_interface = unknown_com_interface.QueryInterface(dispatch_interface_iid)
  dispatch_clsid = str(dispatch_interface.GetTypeInfo().GetTypeAttr().iid)
  if dispatch_clsid not in [excel_application_clsid, word_application_clsid]:
    continue
  com_object = dispatch(dispatch=dispatch_interface)
  excel_application = com_object.Application
  if id(excel_application) not in [id(excel_application) for excel_application in excel_applications]:
    excel_applications.append(excel_application)

input(excel_applications)

The if checks was the way I found to filter out what I don't want, though I'm not sure if that's a good way to do it.

The pywin32 package's documentation (which contains the win32com module as well as the documentation of the pythoncom module) has helped me a lot and together with the Windows API documentation I have learned quite a bit more about COM.

It should be simple to see what's used in the code above for anyone wanting to do this in another programming language. Here's a list of the main things to help: GetRunningObjectTable function, CreateBindCtx function, IMoniker::GetDisplayName method, IRunningObjectTable::GetObject method, IUnknown::QueryInterface method, IDispatch::GetTypeInfo method and ITypeInfo::GetTypeAttr method.


Function specific for Excel instances without Word:

from pythoncom import (
  CreateBindCtx         as create_bind_context_com_interface,
  IID_IDispatch         as dispatch_com_interface_iid,
  GetRunningObjectTable as get_running_object_table_com_interface,
)
from win32com.client import (
  Dispatch as dispatch,
)

def get_excel_instances():
  '''
  Returns a list of the running Microsoft Excel application
  instances as component object model (COM) objects.
  '''
  running_object_table_com_interface = get_running_object_table_com_interface()
  bind_context_com_interface = create_bind_context_com_interface()
  excel_application_class_clsid = '{00024500-0000-0000-C000-000000000046}'
  excel_application_clsid = '{000208D5-0000-0000-C000-000000000046}'
  excel_instance_com_objects = []
  for moniker_com_interface in running_object_table_com_interface:
    display_name = moniker_com_interface.GetDisplayName(bind_context_com_interface, None)
    if excel_application_class_clsid not in display_name:
      continue
    unknown_com_interface = running_object_table_com_interface.GetObject(moniker_com_interface)
    dispatch_com_interface = unknown_com_interface.QueryInterface(dispatch_com_interface_iid)
    dispatch_clsid = str(object=dispatch_com_interface.GetTypeInfo().GetTypeAttr().iid)
    if dispatch_clsid != excel_application_clsid:
      continue
    excel_instance_com_object = dispatch(dispatch=dispatch_com_interface)
    excel_instance_com_objects.append(excel_instance_com_object)
  return excel_instance_com_objects

excel_instances = get_excel_instances()
input()
user7393973
  • 2,270
  • 1
  • 20
  • 58