Python

Automate MS Office Applications using Python win32com module

In this post I am going to demonstrate how to connect to an existing i.e. already opened MS Office application(Word / Excel / PowerPoint / Outlook) and check if the opened document / sheet / slide / message has any embedded file(s) in it.

We will also discuss how to get around a bug in Microsoft’s Visual Basic and Visual C++ library due to which a running MS Office applications does not get registered with the Running Object Table (ROT) and hence no operation can be done on it unless you loose focus from the application and reactivate it. More details about the bug can be found here.

Once you understand this simple program, it can be used as a reference to automate your own use-case whatever it might be. Also on a separate note there are already some python modules that do the same thing, but they don’t support all operations. So in that case we need to use win32com class of pywin32 module to create our own logic.

Here’s is the brief idea behind the implementation.

Step 1

Get around the above mentioned bug by loosing focus from the already running application and reactivate it. For this we will make use of autoit module.

Here’s the code to loose focus and reactivate any window.

from time import sleep
import autoit
from autoit.autoit import properties

# Forces the provided window to loose focus by minimizing it and then reactivating the window.
# If no window title is provided, acts on the active window.
def __Loose_Focus_And_ReActivate_Window(self, winTitle="[ACTIVE]"):
  winHandle = autoit.win_get_handle(winTitle)

  # Minimize window.
  autoit.win_set_state_by_handle(winHandle, properties.SW_MINIMIZE)
  autoit.win_wait_not_active_by_handle(winHandle, timeoutWinStateChange)
  sleep(1)

  # Reactivate window.
  autoit.win_activate_by_handle(winHandle)
  autoit.win_wait_active_by_handle(winHandle, timeoutWinStateChange)
  sleep(1)

This will make our application to register itself with ROT and hence can now be used for further operations.

Step 2

Using win32com module we will get a reference to the already opened MS Office application. Following code gets a reference to Word application.

import win32com.client as win32

wordApp = win32.gencache.EnsureDispatch('Word.Application')

Please note that the above command launches a new Word instance if it is not already running.

Step 3

Now we can browse through the Word application’s object model and get a list of embedded objects it contains.

wordDoc = wordApp.ActiveDocument
inlineShapes = wordDoc.InlineShapes

InlineShapes represent all the embedded objects that are present in the text layer of the word document.

Step 4

Now let’s iterate through each of the inline shapes one by one and check if there are any OLE objects.

from win32com.client import constants as win32consts

for shape in inlineShapes:
  if shape.Type in [win32consts.wdInlineShapeEmbeddedOLEObject, win32consts.wdInlineShapeLinkedOLEObject, win32consts.wdInlineShapeOLEControlObject]:
    return True
return False

Here’s the COMPLETE CODE for Word application.

import win32com.client as win32
from win32com.client import constants as win32consts
from time import sleep
import autoit
from autoit.autoit import properties

# Returns a boolean value indicating if the active Word document contains any OLE object.
def IsOLEObjectPresentInWordApp(self, winTitle="[ACTIVE]"):
  # Minimize the active Word window to ensure it is registered in the Running Object Table (ROT) and then reactivate it.
  # More details can be found at: https://support.microsoft.com/en-in/help/238610/getobject-or-getactiveobject-cannot-find-a-running-office-application
  self.__Loose_Focus_And_ReActivate_Window(winTitle)

  wordApp = win32.gencache.EnsureDispatch('Word.Application')
  wordDoc = wordApp.ActiveDocument
  inlineShapes = wordDoc.InlineShapes
  for shape in inlineShapes:
    if shape.Type in [win32consts.wdInlineShapeEmbeddedOLEObject, win32consts.wdInlineShapeLinkedOLEObject, win32consts.wdInlineShapeOLEControlObject]:
      return True
  return False

# Forces the provided window to loose focus by minimizing it and then reactivating the window.
# If no window title is provided, acts on the active window.
def __Loose_Focus_And_ReActivate_Window(self, winTitle="[ACTIVE]"):
  timeoutWinStateChange = 10
  winHandle = autoit.win_get_handle(winTitle)

  # Minimize window.
  autoit.win_set_state_by_handle(winHandle, properties.SW_MINIMIZE)
  autoit.win_wait_not_active_by_handle(winHandle, timeoutWinStateChange)
  sleep(1)

  # Reactivate window.
  autoit.win_activate_by_handle(winHandle)
  autoit.win_wait_active_by_handle(winHandle, timeoutWinStateChange)
  sleep(1)

Similar code can easily be written for Excel / Powerpoint / Outlook applications by studying their object models.

Excel Code:

# MS Office Constants For OLE Shape Types
msoEmbeddedOLEObject = 7
msoLinkedOLEObject = 10
msoOLEControlObject = 12

# Returns a boolean value indicating if the active Excel sheet contains any OLE object.
def IsOLEObjectPresentInExcelApp(self, winTitle="[ACTIVE]"):
  self.__Loose_Focus_And_ReActivate_Window(winTitle)

  excelApp = win32.gencache.EnsureDispatch('Excel.Application')
  excelSheet = excelApp.ActiveSheet
  shapes = excelSheet.Shapes
  for shape in shapes:
    if shape.Type in [msoEmbeddedOLEObject, msoLinkedOLEObject, msoOLEControlObject]:
      return True
  return False

Powerpoint Code:

# Returns a boolean value indicating if the active PowerPoint slide contains any OLE object.
def IsOLEObjectPresentInPowerpointApp(self, winTitle="[ACTIVE]"):
  self.__Loose_Focus_And_ReActivate_Window(winTitle)

  pptApp = win32.gencache.EnsureDispatch('Powerpoint.Application')
  pptSlide = pptApp.ActiveWindow.View.Slide
  shapes = pptSlide.Shapes
  for shape in shapes:
    if shape.Type in [msoEmbeddedOLEObject, msoLinkedOLEObject, msoOLEControlObject]:
      return True
  return False

Outlook Code:

# Returns a boolean value indicating if the active Outlook email contains any OLE object.
def IsOLEObjectPresentInOutlookApp(self, winTitle="[ACTIVE]"):
  self.__Loose_Focus_And_ReActivate_Window(winTitle)

  outlookApp = win32.gencache.EnsureDispatch('Outlook.Application')
  objMail = outlookApp.ActiveInspector().CurrentItem
  objMailDocument = objMail.GetInspector.WordEditor
  inlineShapes = objMailDocument.InlineShapes
  for shape in inlineShapes:
    if shape.Type in [win32consts.wdInlineShapeEmbeddedOLEObject, win32consts.wdInlineShapeLinkedOLEObject, win32consts.wdInlineShapeOLEControlObject]:
      return True
  return False

Please let me know in the comments section in case you need any help or facing difficulty in understanding any of the mentioned concepts.

Leave a comment