Guide
Run a Python Excel Script on Windows Task Scheduler
Register a Python Excel report with Windows Task Scheduler: a self-contained generator, schtasks /Create triggers, the venv interpreter, a .bat wrapper, and logging.
Windows has no cron, so the unattended scheduling you'd do on Linux happens through Task Scheduler instead. This page registers a Python report script to run on a fixed schedule using schtasks, the command-line interface to Task Scheduler, so you can version the command and recreate the task on any machine. It is the Windows companion to Scheduling Python Excel Scripts with Cron; the report logic is identical, only the trigger mechanism changes. By the end you'll have a task that runs whether or not anyone is logged on, writes daily_summary.xlsx, and logs every run.
Prerequisites
- Windows 10/11 or Windows Server with administrator access (needed to run a task while logged off).
- Python installed with a virtualenv. Create one and install the report dependencies:
python -m venv C:\reporting\venv
C:\reporting\venv\Scripts\pip install pandas openpyxl
Note the absolute path C:\reporting\venv\Scripts\python.exe. Task Scheduler does not activate virtualenvs, so you call that interpreter directly — exactly as you'd call the venv Python by absolute path under cron.
A self-contained report script
Save this as C:\reporting\generate_daily_report.py. It uses absolute paths, seeds sample data if none exists, writes daily_summary.xlsx, and exits non-zero on failure so the task's Last Run Result reflects an error:
"""Scheduled Excel report: build data, summarize, write daily_summary.xlsx."""
import sys
import logging
from datetime import datetime
from pathlib import Path
import pandas as pd
# Absolute paths: Task Scheduler's working directory is C:\Windows\System32.
BASE_DIR = Path(r"C:\reporting")
OUTPUT_DIR = BASE_DIR / "output"
LOG_DIR = BASE_DIR / "logs"
for d in (OUTPUT_DIR, LOG_DIR):
d.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
filename=LOG_DIR / f"report_{datetime.now():%Y%m%d}.log",
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
)
def main():
logging.info("Starting daily Excel report.")
try:
# Build sample data so the run is self-contained.
df = pd.DataFrame({
"region": ["North", "South", "North", "East", "South"],
"revenue": [1200.0, 980.5, 1450.0, 610.25, 980.5],
})
summary = df.groupby("region", as_index=False)["revenue"].sum()
out = OUTPUT_DIR / "daily_summary.xlsx"
summary.to_excel(out, index=False, engine="openpyxl")
logging.info("Wrote %d rows to %s", len(summary), out)
except Exception:
logging.exception("Report generation failed.")
sys.exit(1)
logging.info("Done.")
if __name__ == "__main__":
main()
Confirm it runs from a normal prompt first:
C:\reporting\venv\Scripts\python.exe C:\reporting\generate_daily_report.py
A .bat wrapper that logs stdout
Task Scheduler can run python.exe directly, but a small .bat wrapper gives you one place to capture stdout and stderr — the output that appears before your in-script logging starts (a missing module, a syntax error). Save this as C:\reporting\run_report.bat:
@echo off
REM Activate the venv, then run the report, appending all output to a log.
call C:\reporting\venv\Scripts\activate.bat
python C:\reporting\generate_daily_report.py >> C:\reporting\logs\stdout.log 2>&1
The >> ... 2>&1 redirect is the Windows equivalent of cron's >> logfile 2>&1: without it, interpreter-level errors go nowhere because Task Scheduler discards a task's console output.
Register the task with schtasks
Open an elevated Command Prompt (Run as administrator). The ^ is the line-continuation character in batch — keep it on Windows, or put the whole command on one line.
Daily at 06:00:
schtasks /Create /TN "DailyExcelReport" /SC DAILY /ST 06:00 ^
/TR "C:\reporting\run_report.bat" /RL HIGHEST /F
Weekdays only:
schtasks /Create /TN "DailyExcelReport" /SC WEEKLY /D MON,TUE,WED,THU,FRI /ST 07:30 ^
/TR "C:\reporting\run_report.bat" /RL HIGHEST /F
Every four hours:
schtasks /Create /TN "HourlyExcelReport" /SC HOURLY /MO 4 ^
/TR "C:\reporting\run_report.bat" /RL HIGHEST /F
/F overwrites an existing task with the same name, which makes the command safe to rerun when you redeploy.
Run whether or not the user is logged on
By default a task only fires while its user is interactively logged on. To run it regardless — the usual requirement for a server — supply credentials with /RU and /RP, and request the highest privileges with /RL HIGHEST:
schtasks /Create /TN "DailyExcelReport" /SC DAILY /ST 06:00 ^
/TR "C:\reporting\run_report.bat" ^
/RU "DOMAIN\svc_reports" /RP "the-password" /RL HIGHEST /F
Use a dedicated service account with a non-expiring password for /RU. If you omit /RP, Windows prompts for the password interactively.
schtasks /Create field reference
| Flag | Meaning | Example |
|---|---|---|
/TN | Task name (must be unique) | "DailyExcelReport" |
/TR | Task to run — the program or .bat | "C:\reporting\run_report.bat" |
/SC | Schedule type | DAILY, WEEKLY, HOURLY, MINUTE, ONLOGON |
/ST | Start time, 24-hour HH:MM | 06:00 |
/D | Days (with WEEKLY) | MON,TUE,WED,THU,FRI |
/MO | Modifier / interval | 4 (every 4 hours with HOURLY) |
/RU | Run-as user account | "DOMAIN\svc_reports" |
/RP | Run-as password | "the-password" |
/RL | Run level | HIGHEST or LIMITED |
/F | Force-overwrite an existing task | (no value) |
The Task Scheduler GUI, briefly
If you prefer the GUI, open Task Scheduler (taskschd.msc) → Create Task. On the Actions tab set Program/script to C:\reporting\run_report.bat. On Triggers add a daily/weekly schedule. On the General tab tick Run whether user is logged on or not and Run with highest privileges. The result is identical to the schtasks command above — the CLI is just reproducible.
Verify the task
List the task and read its scheduling and last result:
schtasks /Query /TN "DailyExcelReport" /V /FO LIST
The Last Run Result field is what tells you whether the run succeeded:
| Last Run Result | Meaning |
|---|---|
0x0 | Success |
0x1 | Incorrect function / the script exited non-zero (your sys.exit(1)) |
0x2 | File not found (check the /TR path) |
0x41301 | Task is currently running |
267011 | Task has not yet run |
Force an immediate run to test without waiting for the schedule:
schtasks /Run /TN "DailyExcelReport"
Then confirm C:\reporting\output\daily_summary.xlsx was written and check C:\reporting\logs\stdout.log.
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
FileNotFoundError for a file you can see | Working directory is System32, not your script folder | Use absolute paths for every read and write |
ModuleNotFoundError: pandas | Task ran the system Python, not the venv | Call C:\reporting\venv\Scripts\python.exe (or activate in the .bat) |
Last Run Result 0x2 | Path in /TR has spaces and isn't quoted | Wrap the whole /TR value in double quotes |
| Task "succeeds" but no file appears | Output and errors discarded | Redirect in the .bat with >> logfile 2>&1 |
| Runs only when you're logged in | No /RU / /RP supplied | Add a service account and /RL HIGHEST |
A note on quoting: paths with spaces (C:\Program Files\...) need quotes inside the already-quoted /TR string, which is awkward. Installing your project under a space-free path like C:\reporting avoids the problem entirely.
Frequently asked questions
Do I need the .bat wrapper, or can I point the task straight at python.exe?
You can set /TR "C:\reporting\venv\Scripts\python.exe C:\reporting\generate_daily_report.py" directly. The wrapper is worth it because it gives you one tidy place to capture stdout/stderr to a log and to activate the venv if other tooling expects it.
Why does the task run fine manually but fail on schedule?
Almost always the run-as context. A manual /Run uses your logged-on session; the scheduled run may use a service account with a different PATH and working directory. Use absolute paths and confirm the /RU account can read the script and write the output folder.
How do I delete or change a task?schtasks /Delete /TN "DailyExcelReport" /F removes it. To change it, rerun /Create with /F to overwrite, or use schtasks /Change to tweak a single setting like /ST.
Can Task Scheduler email the report after it runs? Not directly — its built-in email action is deprecated. Send mail from Python at the end of the job; see Emailing Excel Reports with smtplib.
Conclusion
Scheduling a Python Excel script on Windows comes down to four things: call the venv python.exe by absolute path, use absolute paths inside the script (the working directory is System32), redirect output to a log in a .bat wrapper, and add /RU//RP//RL HIGHEST so the task runs while logged off. Register it with schtasks /Create /F, verify with schtasks /Query /V, and key your monitoring off the Last Run Result code.
Where to go next
This is the Windows track of Scheduling Python Excel Scripts with Cron. If you'd rather schedule inside Python and stay cross-platform, see the sibling guide Schedule Recurring Excel Reports with APScheduler. To strengthen the report the task produces, see Writing DataFrames to Excel with pandas, and to deliver it, Emailing Excel Reports with smtplib.