Guide
Automating Reporting WorkflowsDeep dive

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.

How Windows Task Scheduler runs a Python Excel report A daily or weekly time trigger registered with schtasks tells Task Scheduler to launch the virtualenv python.exe by absolute path, which runs the report script and writes daily_summary.xlsx. Trigger Daily / Weekly e.g. 07:00 Register schtasks /Create Run venv python.exe absolute path Output daily_ summary .xlsx Task Scheduler owns the schedule and fires whether or not anyone is logged on. A .bat wrapper captures stdout and stderr to a log on 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:
Bat
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:

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

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

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:

Bat
schtasks /Create /TN "DailyExcelReport" /SC DAILY /ST 06:00 ^
  /TR "C:\reporting\run_report.bat" /RL HIGHEST /F

Weekdays only:

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

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

This is also the reason a task can run perfectly from a manual /Run yet fail on schedule: the two invocations execute in different contexts. The diagram below contrasts them — the fix is to make the scheduled run behave like the manual one, with absolute paths, an explicit /RU account, and /RL HIGHEST.

Manual run context versus scheduled run context A manual schtasks /Run uses your logged-on session, the folder you launched from, your interactive PATH with the venv active, so it appears to work. A scheduled run uses the /RU service account or SYSTEM, the C:\Windows\System32 working directory, and a minimal PATH with no venv, so it raises FileNotFoundError or ModuleNotFoundError unless the script uses absolute paths and the venv interpreter. Manual /Run Scheduled run (logged off) Account Working dir PATH / venv Outcome Your logged-on session Folder you launched from Interactive PATH, venv active Appears to work /RU account or SYSTEM C:\Windows\System32 Minimal PATH, no venv FileNotFound / ModuleNotFound Absolute paths, /RU, and /RL HIGHEST make the scheduled context behave like the manual one.

The full command with a service account looks like this:

Bat
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

FlagMeaningExample
/TNTask name (must be unique)"DailyExcelReport"
/TRTask to run — the program or .bat"C:\reporting\run_report.bat"
/SCSchedule typeDAILY, WEEKLY, HOURLY, MINUTE, ONLOGON
/STStart time, 24-hour HH:MM06:00
/DDays (with WEEKLY)MON,TUE,WED,THU,FRI
/MOModifier / interval4 (every 4 hours with HOURLY)
/RURun-as user account"DOMAIN\svc_reports"
/RPRun-as password"the-password"
/RLRun levelHIGHEST or LIMITED
/FForce-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:

Bat
schtasks /Query /TN "DailyExcelReport" /V /FO LIST

The Last Run Result field is what tells you whether the run succeeded:

Last Run ResultMeaning
0x0Success
0x1Incorrect function / the script exited non-zero (your sys.exit(1))
0x2File not found (check the /TR path)
0x41301Task is currently running
267011Task has not yet run

Force an immediate run to test without waiting for the schedule:

Bat
schtasks /Run /TN "DailyExcelReport"

Then confirm C:\reporting\output\daily_summary.xlsx was written and check C:\reporting\logs\stdout.log.

Common pitfalls

SymptomCauseFix
FileNotFoundError for a file you can seeWorking directory is System32, not your script folderUse absolute paths for every read and write
ModuleNotFoundError: pandasTask ran the system Python, not the venvCall C:\reporting\venv\Scripts\python.exe (or activate in the .bat)
Last Run Result 0x2Path in /TR has spaces and isn't quotedWrap the whole /TR value in double quotes
Task "succeeds" but no file appearsOutput and errors discardedRedirect in the .bat with >> logfile 2>&1
Runs only when you're logged inNo /RU / /RP suppliedAdd 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.

Performance and scale notes

Spawning a fresh python.exe on every trigger costs a fraction of a second of interpreter start-up — negligible for a report that runs a few times a day, and the reason a schtasks job is lighter than keeping a resident scheduler process alive. If you need per-minute cadence or in-process job management, that trade-off flips toward Schedule Recurring Excel Reports with APScheduler instead.

A few things to watch as the workload grows:

  • Overlapping runs. Tasks created with schtasks default to the Do not start a new instance policy, so a slow report that overruns its next trigger is simply skipped rather than doubled up — the Windows equivalent of guarding a cron job with flock. To change that behaviour you must edit the task's XML (<MultipleInstancesPolicy>), not a schtasks flag.
  • Long-running jobs. schtasks has no flag for an execution time limit, so an inherited task keeps the GUI default of stopping after 3 days. Export the task with schtasks /Query /TN "DailyExcelReport" /XML, set <ExecutionTimeLimit> (for example PT1H), and re-import with schtasks /Create /TN "DailyExcelReport" /XML task.xml /F.
  • Log growth. The per-day report_YYYYMMDD.log from the script rotates itself, but stdout.log in the .bat wrapper grows unbounded because of the append (>>). Either switch to a dated name (stdout_%date:~-4%%date:~4,2%%date:~7,2%.log) or prune old logs periodically.
  • Fleet deployment. That same /XML export/import pair is the clean way to replicate one verified task across many servers — version the XML alongside the script rather than retyping the schtasks /Create line on each box.
  • Heavy workbooks. Building the workbook, not scheduling it, dominates runtime. If to_excel on a large frame is the bottleneck, see Writing DataFrames to Excel with pandas for the faster write paths.

Three settings that decide success

Task Scheduler settings that matter Start in must be the project directory, the action should call the virtual environment's python directly, and the task should run whether or not the user is logged on. Start in the project folder relative paths work most common omission the interpreter .venv\Scripts\python.exe no activation needed absolute path run when logged off needs stored credentials or the task never fires test it once

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.