1

I would like to use the Python win32com library (part of pywin32) to receive events from the local OneNote Application Interface (as documented here). Note this question does NOT relate to the newer online "REST" OneNote interface, but rather to the older COM-based local desktop interface.

When I try to use this simple example:

import time, pythoncom, win32com.client as COM

class OneNoteEvents:
    def OnHierarchyChange(self, PageID=pythoncom.Empty):
        print('Hierarchy has changed!!')

onevt = COM.DispatchWithEvents('OneNote.Application', OneNoteEvents)

while True: 
    pythoncom.PumpWaitingMessages()  # Non-blocking
    time.sleep(0.02)

I get the following error (some identifying file path elements redacted):

Traceback (most recent call last):
  File "C:\...\OneNote_event_compact.py", line 7, in <module>
    onevt = COM.DispatchWithEvents('OneNote.Application', OneNoteEvents)
  File "C:\Users\<XXXX>\AppData\Local\Programs\Python\Python313\Lib\site-packages\win32com\client\__init__.py", line 346, in DispatchWithEvents
    events_class.__init__(instance, instance)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\<XXXX>\AppData\Local\Temp\gen_py\3.13\0EA692EE-BB50-4E3C-AEF0-356D91732725x0x1x1.py", line 290, in __init__
    cookie=cp.Advise(win32com.server.util.wrap(self, usePolicy=EventHandlerPolicy))
pywintypes.com_error: (-2147467259, 'Unspecified error', None, None)

What I know so far:

  • The error occurs in code generated by win32com in the 'gen_py' folder, when the Advise() call is made.
  • I can otherwise use the non-event aspects of the OneNote COM interface perfectly well from Python, to retrieve the page hierarchy, etc.
  • I can also make a simple Event example for Excel, which also works perfectly well, and prints messages as the cursor moves around the sheet:
import time, pythoncom, win32com.client as COM

class ExcelEvents:
    def OnSheetSelectionChange(self, sh=None, Target=None):
        print(f'Selection Changed {sh.Name} {app.ActiveCell.Address}')

app = COM.DispatchWithEvents("Excel.Application", ExcelEvents)
app.Visible=True
while True: pythoncom.PumpWaitingMessages(); time.sleep(0.05)

The problem seems specific to the OneNote Application Event interface, causing a failure at the Advise() call for reasons I don't fully understand. Any hints for work-arounds, or approaches to determine the cause would be greatly appreciated.

1 Answer 1

0

The ultimate goal in asking the original question about receiving OneNote COM events was to notify a Python program of changes to OneNote content. Since the COM Event approach may be problematic, and since others may come across this question hoping to find a solution for getting OneNote update events, I am sharing here a work-around, but will not mark this answer as "accepted", since it does not address the original COM Event question, and since I am still interested in an answer to that question.

A completely different approach which may be sufficient is to use the Python watchdog library to monitor changes in the local OneNote cache folder. There is no need to attempt to interpret the content of the cache files, but to just use the update event to trigger the Python program, then to use the official COM interface to retrieve desired information about what was updated. This avoids having to use the COM interface to continuously poll for changes, and the cache updates seem to occur reliably and promptly, usually within at most a few seconds from a content update, in my casual observations.

The proof-of-concept script below reacts to the cache update event by simply printing the name of the current OneNote page, and its last update time (it's possible to instead retrieve update times for ALL pages with one.GetHierarchy('',4), to be sure nothing is missed). This was tested and works as-is with Python 3.13.1 on Windows 10, with OneNote desktop/365 running. You may need to change the MonFolder path to suit your installation.

import os
import win32com.client
import xml.etree.ElementTree as ET
from datetime import datetime
from dateutil import tz

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from threading import Event

# watchdog handler sets an event to notify main thread.
class FileUpdateHandler(FileSystemEventHandler):
    def __init__(self):
        self.E = Event()

    def on_modified(self, event):
        self.E.set() # Notify main thread

# Start watchdog on the OneNote cache folder.
MonFolder = os.path.expanduser(r'~\AppData\Local\Microsoft\OneNote\16.0\cache')
hdlr = FileUpdateHandler()
obs = Observer()
obs.schedule(hdlr, MonFolder)
obs.start()
print(f'Monitoring path {MonFolder}')

# Use non-Event facilites of official COM interface
# to get OneNote page information.
one = win32com.client.gencache.EnsureDispatch('OneNote.Application')

try:
    while True:
        # Wait for the Observer handler to set() the Event,
        # to indicate change to OneNote cache folder.
        if not hdlr.E.wait(timeout=0.1):  # Allows keyboard interrupt
            continue

        curpgid = one.Windows.CurrentWindow.CurrentPageId
        pg_xml = one.GetHierarchy(curpgid, 0)  # Get current page info
        pg = ET.fromstring(pg_xml)

        # Time is in UTC/Zulu - must convert to local time.
        lastmod = datetime.fromisoformat(pg.attrib['lastModifiedTime'])
        lastmod_local = lastmod.astimezone(tz.tzlocal())
        print(f' -- main loop - pg: {pg.attrib['name']}  '
              f'[{lastmod_local:%Y-%m-%d %H:%M:%S}]')

        hdlr.E.clear()

except KeyboardInterrupt:
    print('Stopping Observer...')
    obs.stop()

obs.join()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.