How-to
Getting Started With Python Excel AutomationDeep dive

Print an Excel Workbook from Python with win32com

Send workbooks to a printer from a Python script on Windows: prepare the page setup so the printout is right (fit to width, landscape, repeated header rows, print area) with openpyxl or COM, list printers and pick one, call PrintOut for whole workbooks, single sheets or page ranges, print many files in a loop without dialogs, and fall back to LibreOffice or PDF when Excel is not available.

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

Bash
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:

Python
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.

The same sheet printed with and without fit-to-width Without page setup, twelve columns spill onto extra pages sideways and the header appears only on the first page. With landscape orientation, fit to one page wide and a repeated title row, every page shows all columns and the header. Default settings portrait, 100% scale last column on a separate page header only on page 1 no page numbers Prepared page setup landscape, fit to 1 page wide every column on every page row 1 repeated on each page Page x of y in the footer

Step 2 — List printers and pick one

Python
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

Python
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:

Python
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.

What happens when the script prints The Python script starts a private Excel instance, opens the prepared workbook read-only, calls PrintOut with the printer name, Excel renders the pages using the stored page setup and sends the job to the printer spooler, then the script closes the workbook and quits Excel. Python script Excel (COM) Print spooler 1. DispatchEx, Workbooks.Open 2. PrintOut(ActivePrinter=…) 3. pages using stored page setup 4. Close, Quit

Step 4 — Print a folder of reports without dialogs

Python
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:

Python
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:

Bash
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.

Choosing how to get the report onto paper On Windows with Excel, print with win32com PrintOut. On a server with LibreOffice and a printer queue, use soffice --pt. Where the printer is elsewhere or printing should be decoupled, export a PDF and print it from a print server or by the recipient. What is available where the script runs? Windows + Excel win32com PrintOut full control of printer and pages LibreOffice + printer queue soffice --pt uses the stored page setup printer elsewhere Export a PDF print where the paper is needed

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 ActivePrinter from configuration.
  • Orphan Excel processes. Quit in a finally block and use DispatchEx for a private instance.
  • Dialogs blocking a scheduled job. Set DisplayAlerts = False and open with UpdateLinks=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).

More on com & pywin32

More on com & pywin32 →