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
pywin32must 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
- 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 afinallyblock. - Use
Visible = Falseby default for speed. SetVisible = Trueonly when the user wants to watch or when debugging. - Wrap in try/finally. If your script crashes without calling
Quit(), the app stays as a zombie process. Usetry/finallyto 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 |