← Back to Workflow
Skill

COM Automation Skill

How to programmatically control Windows applications (Excel, Word, Outlook, PowerPoint, Access) via COM automation using pywin32. Use this for bulk data operations, formula evaluation, and document generation — NOT for UI navigation (use WinApp-MCP for that).

Skill: COM Automation (pywin32)

COM automation lets you control Windows applications through their programming interface, bypassing the UI entirely. Instead of clicking buttons and typing into fields, you talk directly to the application's engine.

When to Use What

| Task | Use This | |---|---| | Read/write thousands of cells, evaluate formulas, create charts | COM (this skill) | | Click a ribbon button, navigate menus, interact with dialogs | WinApp-MCP | | Simple read/write of .xlsx without Excel installed | openpyxl |

Prerequisites

  • pywin32 must be installed (pip install pywin32)
  • The target application must be installed (e.g., Microsoft Excel for Excel COM)
  • Verified installed on this machine: Excel, Word, Outlook, PowerPoint, Access

Critical Rules

  1. Always quit the application when done. COM objects hold the app open in the background even after your script ends. Always call app.Quit() in a finally block.
  2. Use Visible = False by default for speed. Set Visible = True only when the user wants to watch or when debugging.
  3. Wrap in try/finally. If your script crashes without calling Quit(), the app stays as a zombie process. Use try/finally to guarantee cleanup.

Boilerplate Pattern

Every COM script should follow this structure:

import win32com.client

app = None
try:
    app = win32com.client.Dispatch("PROG.ID.HERE")
    app.Visible = False  # True if user should see it
    
    # ... do work ...
    
finally:
    if app:
        app.Quit()

Excel — Excel.Application

Open and Read Calculated Values

import win32com.client

excel = None
try:
    excel = win32com.client.Dispatch("Excel.Application")
    excel.Visible = False
    wb = excel.Workbooks.Open(r"C:\path\to\file.xlsx")
    ws = wb.Sheets("Sheet1")
    
    # Read a cell's CALCULATED value (not the formula text)
    value = ws.Range("C5").Value
    
    # Read the formula itself
    formula = ws.Range("C5").Formula
    
    # Read a range into a 2D tuple
    data = ws.Range("A1:D100").Value  # returns tuple of tuples
    
    wb.Close(SaveChanges=False)
finally:
    if excel:
        excel.Quit()

Create a New Workbook with Formulas

import win32com.client

excel = None
try:
    excel = win32com.client.Dispatch("Excel.Application")
    excel.Visible = False
    wb = excel.Workbooks.Add()
    ws = wb.ActiveSheet
    ws.Name = "Summary"
    
    # Write values
    ws.Range("A1").Value = "Category"
    ws.Range("B1").Value = "Amount"
    ws.Range("A2").Value = "Premium"
    ws.Range("B2").Value = 125000
    
    # Write formulas
    ws.Range("B10").Formula = "=SUM(B2:B9)"
    
    # Auto-fill a formula down a column
    ws.Range("C2").Formula = "=B2*1.05"
    ws.Range("C2").AutoFill(Destination=ws.Range("C2:C100"))
    
    # Formatting
    ws.Range("A1:C1").Font.Bold = True
    ws.Range("B2:B100").NumberFormat = "$#,##0"
    ws.Columns("A:C").AutoFit()
    
    # Save
    wb.SaveAs(r"C:\path\to\output.xlsx", FileFormat=51)  # 51 = xlsx
    wb.Close()
finally:
    if excel:
        excel.Quit()

Key FileFormat Constants

| Format | Code | Extension | |---|---|---| | xlsx | 51 | .xlsx | | xlsm (with macros) | 52 | .xlsm | | csv | 6 | .csv | | PDF | 57 | .pdf |

Export to PDF

ws.ExportAsFixedFormat(0, r"C:\path\to\output.pdf")  # 0 = PDF

Word — Word.Application

Create a Document

import win32com.client

word = None
try:
    word = win32com.client.Dispatch("Word.Application")
    word.Visible = False
    doc = word.Documents.Add()
    
    # Add text
    rng = doc.Range()
    rng.Text = "This is the first paragraph."
    
    # Add a paragraph at the end
    doc.Paragraphs.Add()
    doc.Paragraphs.Last.Range.Text = "Second paragraph."
    
    # Formatting
    doc.Paragraphs(1).Range.Font.Bold = True
    doc.Paragraphs(1).Range.Font.Size = 16
    
    doc.SaveAs2(r"C:\path\to\output.docx", FileFormat=16)  # 16 = docx
    doc.Close()
finally:
    if word:
        word.Quit()

Read an Existing Document

doc = word.Documents.Open(r"C:\path\to\input.docx")
full_text = doc.Range().Text
doc.Close(SaveChanges=False)

Find and Replace

find = doc.Range().Find
find.Text = "old text"
find.Replacement.Text = "new text"
find.Execute(Replace=2)  # 2 = wdReplaceAll

Outlook — Outlook.Application

Send an Email

import win32com.client

outlook = win32com.client.Dispatch("Outlook.Application")
mail = outlook.CreateItem(0)  # 0 = MailItem
mail.To = "someone@example.com"
mail.Subject = "Test Subject"
mail.Body = "Plain text body"
# mail.HTMLBody = "<h1>HTML body</h1>"  # for HTML emails
# mail.Attachments.Add(r"C:\path\to\file.pdf")  # attach a file
mail.Send()
# Note: Outlook stays running as a system tray app, don't call Quit()

Read Inbox

outlook = win32com.client.Dispatch("Outlook.Application")
namespace = outlook.GetNamespace("MAPI")
inbox = namespace.GetDefaultFolder(6)  # 6 = Inbox
messages = inbox.Items
messages.Sort("[ReceivedTime]", True)  # newest first

for i in range(min(10, messages.Count)):
    msg = messages.Item(i + 1)  # 1-indexed
    print(f"From: {msg.SenderName}, Subject: {msg.Subject}")

PowerPoint — PowerPoint.Application

Create a Presentation

import win32com.client

ppt = None
try:
    ppt = win32com.client.Dispatch("PowerPoint.Application")
    ppt.Visible = True  # PowerPoint requires Visible=True to add slides
    pres = ppt.Presentations.Add()
    
    # Add a title slide (layout 1)
    slide = pres.Slides.Add(1, 1)
    slide.Shapes.Title.TextFrame.TextRange.Text = "Presentation Title"
    slide.Shapes.Placeholders(2).TextFrame.TextRange.Text = "Subtitle here"
    
    # Add a blank slide and insert a text box
    slide2 = pres.Slides.Add(2, 12)  # 12 = blank layout
    textbox = slide2.Shapes.AddTextbox(1, 50, 50, 600, 300)  # orientation, left, top, width, height
    textbox.TextFrame.TextRange.Text = "Content goes here"
    
    pres.SaveAs(r"C:\path\to\output.pptx")
    pres.Close()
finally:
    if ppt:
        ppt.Quit()

Access — Access.Application

Open and Query a Database

import win32com.client

access = None
try:
    access = win32com.client.Dispatch("Access.Application")
    access.Visible = False
    access.OpenCurrentDatabase(r"C:\path\to\database.accdb")
    
    # Run a query
    rs = access.CurrentDb().OpenRecordset("SELECT * FROM Customers WHERE State='FL'")
    while not rs.EOF:
        print(rs.Fields("Name").Value, rs.Fields("City").Value)
        rs.MoveNext()
    rs.Close()
    
    access.CloseCurrentDatabase()
finally:
    if access:
        access.Quit()

Troubleshooting

| Problem | Fix | |---|---| | Zombie Excel/Word process after crash | Open Task Manager → End EXCEL.EXE or WINWORD.EXE | | com_error: Operation unavailable | The app might have a dialog box open blocking automation. Set app.DisplayAlerts = False | | Slow performance | Make sure Visible = False and set excel.ScreenUpdating = False for bulk operations | | AttributeError on COM object | Use win32com.client.gencache.EnsureDispatch("Excel.Application") instead of Dispatch() for early-bound access with autocomplete |

This is used in: