Print an Excel Workbook from Python with win32com
Some reports still end on paper: delivery notes for the warehouse, a daily schedule pinned to a wall, monthly packs for a board that reads in print. When a script already produces the workbooks, sending them to the printer is the last step to automate. On Windows with Excel installed, the COM interface gives Python the same printing Excel's own menu uses — choose a printer, print the whole workbook or one sheet, set copies and page ranges — without any dialog popping up. The part people get wrong is not the print call but the page setup: a sheet that is one column too wide prints on twice as many pages. This page covers both, plus what to do on a machine without Excel. It belongs to Automating Excel with COM and pywin32.
Prerequisites
pip install pywin32 openpyxl
Printing through COM requires Windows with Excel installed and a configured printer; those steps are marked as not run by our build, which runs on Linux. The page-setup step uses openpyxl and runs anywhere. The example file deliveries.xlsx is a 120-row delivery route with twelve columns — too wide for a portrait page — that should print landscape, one page wide, with the header row repeated on every page.
Step 1 — Get the page setup right first
Print settings are stored in the workbook, so they can be prepared by the script that builds the report — on any operating system — and Excel uses them when it prints:
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
wb = load_workbook("deliveries.xlsx")
ws = wb["Route"]
ws.page_setup.orientation = "landscape"
ws.page_setup.paperSize = ws.PAPERSIZE_A4
ws.sheet_properties.pageSetUpPr.fitToPage = True
ws.page_setup.fitToWidth = 1 # one page wide
ws.page_setup.fitToHeight = 0 # as many pages tall as needed
ws.print_title_rows = "1:1" # repeat the header on every page
ws.print_area = f"A1:{get_column_letter(ws.max_column)}{ws.max_row}"
for col in range(1, ws.max_column + 1):
ws.column_dimensions[get_column_letter(col)].width = 14
ws.oddFooter.center.text = "Page &P of &N"
ws.print_options.gridLines = True
wb.save("deliveries_print.xlsx")
check = load_workbook("deliveries_print.xlsx")["Route"]
print(check.page_setup.orientation, check.page_setup.fitToWidth, check.page_setup.fitToHeight,
check.print_title_rows, check.print_area)
fitToPage must be switched on for the fit-to settings to apply; fitToHeight = 0 means "no limit", so long tables flow onto as many pages as they need while never spilling sideways. Repeating row 1 keeps every page readable on its own, and the footer numbers the pages. These settings are covered in detail in Set the Print Area and Page Setup with openpyxl.
Step 2 — List printers and pick one
import win32print
default = win32print.GetDefaultPrinter()
flags = win32print.PRINTER_ENUM_LOCAL | win32print.PRINTER_ENUM_CONNECTIONS
printers = [p[2] for p in win32print.EnumPrinters(flags)]
print("default:", default)
for name in printers:
print(" ", name)
EnumPrinters with the local and connections flags lists printers installed on the machine and shared network printers; element [2] of each entry is the printer name. A job running unattended should not rely on the default printer, which a user can change at any time — read the intended printer's name from configuration and check that it is in this list before printing.
Step 3 — Print with PrintOut
import os
import win32com.client as win32
excel = win32.DispatchEx("Excel.Application")
excel.Visible = False
excel.DisplayAlerts = False
try:
wb = excel.Workbooks.Open(os.path.abspath("deliveries_print.xlsx"), ReadOnly=True)
wb.PrintOut(Copies=1, ActivePrinter="Warehouse Laser", Collate=True)
wb.Close(SaveChanges=False)
finally:
excel.Quit()
Workbook.PrintOut prints every sheet with its own page setup; ActivePrinter chooses the printer by name for this job. DispatchEx starts a separate Excel instance so the script does not interfere with a workbook the user has open, ReadOnly=True avoids locking the file, and the finally block makes sure Excel quits even if printing fails — a stray invisible EXCEL.EXE left behind is the classic COM problem, discussed in Close Excel Cleanly and Avoid Orphan COM Processes.
To print part of a workbook, call PrintOut on a sheet instead, with a page range:
sheet = wb.Worksheets("Route")
sheet.PrintOut(From=1, To=2, Copies=2, ActivePrinter="Warehouse Laser")
print("pages in the printout:", sheet.PageSetup.Pages.Count)
From and To are page numbers of the printout, not rows. PageSetup.Pages.Count asks Excel how many pages the sheet will produce with its current settings — a quick check that the fit-to-width setup did its job before paper is used.
Step 4 — Print a folder of reports without dialogs
from pathlib import Path
def print_folder(folder, printer, pattern="*.xlsx"):
excel = win32.DispatchEx("Excel.Application")
excel.Visible = False
excel.DisplayAlerts = False
printed, failed = [], []
try:
for path in sorted(Path(folder).glob(pattern)):
if path.name.startswith("~$"):
continue
try:
wb = excel.Workbooks.Open(str(path.resolve()), ReadOnly=True, UpdateLinks=0)
wb.PrintOut(ActivePrinter=printer)
wb.Close(SaveChanges=False)
printed.append(path.name)
except Exception as exc: # keep going; report at the end
failed.append((path.name, str(exc)))
finally:
excel.Quit()
return printed, failed
One Excel instance prints every file, which is much faster than starting Excel per file. UpdateLinks=0 stops the "update external links?" prompt, DisplayAlerts = False suppresses most others, and lock files (~$…) are skipped. Collecting failures instead of stopping means one corrupt workbook does not block the other forty delivery notes; log them, as in Handle COM Errors and Excel Dialog Prompts in Python.
Step 5 — Proof-print to PDF before using paper
Before a script prints a hundred copies, render one to PDF and count the pages — the cheapest possible test of the page setup. LibreOffice does it headlessly from the same file, so the check can run in a test on any machine:
import subprocess
from pypdf import PdfReader
for name in ("deliveries.xlsx", "deliveries_print.xlsx"):
subprocess.run(["soffice", "--headless", "--convert-to", "pdf", "--outdir", "proof", name],
check=True, capture_output=True)
pages = len(PdfReader(f"proof/{name.replace('.xlsx', '.pdf')}").pages)
print(f"{name:24} {pages} pages")
The unprepared file renders to six pages — the twelve columns do not fit across a portrait page, so every block of rows is printed twice, once for the left columns and once for the right — while the prepared file needs three, with every column on every page and the header repeated. A test that asserts the expected page count catches the day someone adds a sixth column and the printout quietly doubles in length. On Windows, Excel's own wb.ExportAsFixedFormat(0, path) produces the same proof with Excel's rendering, as in Convert an Excel File to PDF with Python.
Step 6 — Without Excel: LibreOffice or a PDF
On servers without Excel, LibreOffice can print directly, using the same page setup stored in the file:
soffice --headless --pt "Warehouse_Laser" deliveries_print.xlsx
--pt prints to the named printer (as the operating system knows it — on Linux, the CUPS queue name) and exits; -p prints to the default printer. Often the better design is not to print from the server at all: export a PDF and let the printing happen where the printer is — a print server, a shared folder that a printer polls, or the person who needs the paper. PDF export is covered in Convert Excel to PDF on Linux with LibreOffice, and the PDF can be printed later by any device.
Common pitfalls
- Printing before fixing the page setup. Set landscape, fit-to-width and print titles first; check
PageSetup.Pages.Count. - Relying on the default printer. Pass
ActivePrinterfrom configuration. - Orphan Excel processes. Quit in a
finallyblock and useDispatchExfor a private instance. - Dialogs blocking a scheduled job. Set
DisplayAlerts = Falseand open withUpdateLinks=0. - Columns sized too wide. Fit-to-width scales the whole page down to fit; one overly wide column makes everything tiny, so set sensible widths first.
- Printer names differing between machines. Network printers may appear as
\\server\queue; list the names on the machine that prints.
Conclusion
Prepare the page setup in the workbook — landscape, fit to one page wide, repeated header rows, print area, page numbers — with openpyxl on any platform. On Windows, print through COM: list printers with win32print, open the file read-only in a private Excel instance, call PrintOut with ActivePrinter (or on a sheet with From and To), and quit in a finally block. Without Excel, print with soffice --pt or, better, export a PDF and print it where the printer is.
Frequently asked questions
How do I print an Excel file from Python? On Windows with Excel, open it with win32com through excel.Workbooks.Open and call the workbook's PrintOut method, passing the printer name as ActivePrinter. Without Excel, LibreOffice can print with soffice --headless --pt "Printer name" file.xlsx.
How do I choose which printer Python prints to? List the installed printers with win32print.EnumPrinters and pass the chosen name as ActivePrinter to PrintOut. Avoid relying on the default printer for unattended jobs.
How do I make an Excel sheet print on one page wide? Set the sheet's page setup to fit to one page wide and unlimited pages tall. With openpyxl, switch on fitToPage in the sheet properties' page setup section, then set page_setup.fitToWidth to 1 and page_setup.fitToHeight to 0.
How do I print only some pages or one sheet? Call PrintOut on the worksheet object instead of the workbook, and pass From and To with the page numbers, for example sheet.PrintOut(From=1, To=2).
Related
- Up one level: Automating Excel with COM and pywin32 — controlling Excel on Windows.
- Fit an Excel Sheet to One Page in PDF — the same layout problem for PDFs.
- Insert Page Breaks Between Groups with openpyxl — one group per printed page.
- Run Excel COM Automation from Task Scheduler — printing on a schedule.
More on com & pywin32
- How-toTest win32com and xlwings Code Without Excel
- How-toExport an Excel Chart as an Image with Python
- How-toAutomate Excel on macOS with Python
- How-toClose Excel Cleanly and Avoid Orphan COM Processes
- FixHandle COM Errors and Excel Dialog Prompts in Python
- How-toRead and Write Cell Ranges with win32com