Stop Using Save As — Create Excel Files Programmatically in 2 Real Ways

The first thing most people do when they need to create an Excel file programmatically is open Excel, paste data, and hit Save As. That’s not programmatically — that’s automation theater. You’re still relying on Excel’s UI, breaking headless workflows, and locking your script to Windows. Worse? It fails silently when Excel isn’t installed or a modal dialog pops up.

Quick Answer

Create Excel files programmatically by generating real .xlsx files without launching Excel — use openpyxl (Python) for full formatting control, or Export-Excel (PowerShell) for rapid tabular exports from CSV/objects. Both write native Office Open XML, work offline, and run on Linux/macOS (Python) or any Windows machine (PowerShell).

All the Methods

Method Steps Best For Limitations
openpyxl (Python) Install → import → Workbook() → ws.append() → ws['A1'].value = 'X' → wb.save() Complex layouts, formulas, charts, conditional formatting, cell styling No built-in chart rendering (requires openpyxl.chart extra setup)
Export-Excel (PowerShell) Install ImportExcel module → ConvertTo-Excel -Path → pipe objects or CSV Admin reports, log exports, quick pivot-ready tables from AD/SQL Windows-only; no direct cell-level formatting (e.g., A1 bold + red fill)
csvkit + rename Write CSV → rename .csv to .xlsx One-time manual sharing where Excel opens it 'fine' ❌ Not real Excel — breaks formulas, filters, sheets, macros. Excel shows warning every time.
COM Automation (VBA/PowerShell) New-Object -ComObject Excel.Application → Visible=$false → Workbooks.Add() Legacy Windows-only scripts needing ribbon commands or real-time interaction Fails if Excel isn’t licensed, hangs on dialogs, unstable under RDP

Method 1 Deep Dive

Let’s build a sales report using openpyxl — with headers, currency formatting, auto-fit columns, and a total row. This runs on macOS, Linux, or Windows. No Excel required.

First, install: pip install openpyxl. Then run this script:

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter

wb = Workbook()
ws = wb.active
ws.title = "Q1 Sales"

# Header row
headers = ["Sales Rep", "Region", "Amount", "Date"]
for col_num, header in enumerate(headers, 1):
    cell = ws.cell(row=1, column=col_num)
    cell.value = header
    cell.font = Font(bold=True, color="FFFFFF")
    cell.fill = PatternFill(start_color="1e3a5f", end_color="1e3a5f", fill_type="solid")
    cell.alignment = Alignment(horizontal="center")

# Sample data — real names, real amounts
data = [
    ["Sarah Chen", "APAC", 45200, "2024-03-15"],
    ["Diego Morales", "EMEA", 67800, "2024-03-18"],
    ["Priya Patel", "Americas", 52100, "2024-03-22"],
    ["Kenji Tanaka", "APAC", 39400, "2024-03-25"]
]

for row_num, row_data in enumerate(data, 2):
    for col_num, value in enumerate(row_data, 1):
        ws.cell(row=row_num, column=col_num).value = value

# Format Amount column as currency (B2:C5)
for row in ws["C2:C5"]:
    for cell in row:
        cell.number_format = '$#,##0.00'

# Add total row
ws["C6"] = "=SUM(C2:C5)"
ws["C6"].font = Font(bold=True)
ws["A6"] = "Total"
ws["A6"].font = Font(bold=True)

# Auto-fit columns A:D
for col in ['A', 'B', 'C', 'D']:
    ws.column_dimensions[col].width = 14

wb.save("q1_sales_report.xlsx")

The beauty of this approach is that ws["C6"] = "=SUM(C2:C5)" writes a real formula — open the resulting file in Excel and click C6: you’ll see =SUM(C2:C5) in the formula bar, recalculating if you change values. No CSV conversion. No guesswork.

Surprising tip: openpyxl doesn’t auto-recalculate formulas on save — but Excel does on open. So if you need pre-calculated values, assign ws["C6"].value = 204500 instead. Use .value for static numbers, = strings for live formulas.

Method 2 Deep Dive

PowerShell’s ImportExcel module skips the boilerplate. It’s ideal for sysadmins pulling data from Active Directory or SQL Server.

Install once: Install-Module ImportExcel -Force. Then run:

$salesData = @(
    [PSCustomObject]@{Rep="Sarah Chen"; Region="APAC"; Amount=45200; Date="2024-03-15"},
    [PSCustomObject]@{Rep="Diego Morales"; Region="EMEA"; Amount=67800; Date="2024-03-18"},
    [PSCustomObject]@{Rep="Priya Patel"; Region="Americas"; Amount=52100; Date="2024-03-22"}
)

$salesData | Export-Excel -Path "q1_powershell_report.xlsx" `
    -WorksheetName "Raw Data" `
    -TableName "SalesTable" `
    -AutoSize `
    -BoldTopRow `
    -IncludePivotTable `
    -PivotRows "Region" `
    -PivotData @{'Amount'='Sum'}

This creates a real .xlsx with three things in one go: a formatted table (A1:D4), an auto-sized header, and a pivot table summarizing region totals — all without opening Excel. The pivot table lives on a separate sheet named PivotTable1.

Keyboard shortcut tip: In Excel, press AltNV to open the PivotTable wizard — but here, it’s baked into the export. No mouse needed.

Note: This only works on Windows with PowerShell 5.1+ or PowerShell Core 7+. But unlike COM, it never launches Excel.exe — so it works inside Azure Functions or scheduled tasks.

Cheat Sheet

Task Python (openpyxl) PowerShell (ImportExcel)
Create new file from openpyxl import Workbook; wb = Workbook() $data | Export-Excel -Path "out.xlsx"
Write header in row 1 ws["A1"] = "Name"; ws["B1"] = "Value" Headers auto-generated from object properties
Apply currency format to C2:C10 for cell in ws["C2:C10"]: cell.number_format = "$#,##0.00" Not supported — use -AutoFilter + format after import
Add SUM formula in C11 ws["C11"] = "=SUM(C2:C10)" Use -IncludePivotTable or add via Set-ExcelColumn
Save to disk wb.save("report.xlsx") -Path "report.xlsx" parameter in Export-Excel
Tom Bradley

Tom Bradley

Tom has 15 years of experience in office management and supply chain optimization. He shares practical tips for running efficient workplaces.