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 Alt → N → V 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 |