How-to
Getting Started With Python Excel AutomationDeep dive

Run Excel COM Automation from Task Scheduler Without a Logged-In User

Why a pywin32 script that drives Excel works when you run it by hand and fails or hangs from Task Scheduler — session 0, the missing systemprofile Desktop folder, DCOM identity, dialogs nobody can click — and the setups that work, plus how to avoid needing Excel at all.

A script that opens Excel through pywin32 runs perfectly when you start it from a terminal. Scheduled with Run whether user is logged on or not, it fails with a cryptic COM error, writes nothing, or — worst — never finishes, leaving an invisible EXCEL.EXE running until someone reboots the machine. Nothing is wrong with the code. Excel is an interactive desktop application, and a scheduled task running with nobody logged on does not give it the environment it expects. This guide, part of Automating Excel with COM and pywin32, explains the causes, the configurations that work, and how to make the job robust — or remove Excel from it.

The same script in an interactive session and in a scheduled background session Run by hand, the script executes in the user's interactive session with a desktop, a loaded profile and a place to show dialogs, and Excel works. Run by Task Scheduler with nobody logged on, it executes in a non-interactive session without a normal desktop or profile, where Excel can fail to open or save files and dialogs appear where nobody can see them. Run by hand interactive desktop user profile loaded dialogs can be answered Excel behaves normally Scheduled, nobody logged on non-interactive session no normal desktop folder invisible dialogs block forever COM errors or silent hangs

What actually goes wrong

Three separate things break, and a failing job usually hits more than one:

  1. No interactive desktop. A task set to run whether the user is logged on or not runs in a non-interactive session. Excel starts, but anything that assumes a visible desktop — dialogs, some add-ins, printing — misbehaves.
  2. Missing profile folders. Under system or service-like accounts, Excel looks for a Desktop folder in the system profile that does not exist, and opening or saving files fails. The classic symptom is Workbooks.Open returning None or SaveAs raising a COM error that says the file cannot be accessed.
  3. Dialogs nobody can see. A "file is locked", "update links?" or "repair this file?" prompt appears in a session with no screen. Excel waits for an answer that never comes, and the job hangs.

Microsoft's own guidance is that Office applications are not supported for unattended server-side automation, precisely because of issues like these. The configurations below make it work in practice; they do not make it supported.

Option 1 — Run the task only when the user is logged on

The most reliable way to drive Excel on a schedule is to give it the environment it was designed for. In the task's properties, choose Run only when user is logged on, and use an account that stays logged in on that machine — typically a dedicated automation account on a dedicated machine or virtual desktop, with the screen locked rather than signed out. Excel then runs in a real interactive session, exactly as when you tested by hand.

The task definition can be created from Python with schtasks, which keeps the setup reproducible:

Python
import subprocess

subprocess.run([
    "schtasks", "/Create", "/TN", "Reports\\RefreshSalesWorkbook",
    "/TR", r'"C:\reports\.venv\Scripts\python.exe" "C:\reports\refresh.py"',
    "/SC", "DAILY", "/ST", "06:00",
    "/IT",                      # interactive: run only when the user is logged on
    "/F",
], check=True)

/IT is the command-line form of Run only when user is logged on. The general Task Scheduler setup — working directory, virtual environment, logging — is covered in Run a Python Excel Script on Windows Task Scheduler.

Option 2 — The systemprofile Desktop folder workaround

When the task must run with nobody logged on, the long-standing workaround is to create the Desktop folders Excel looks for in the system profile. On 64-bit Windows there are two, one for 64-bit and one for 32-bit Office:

Python
from pathlib import Path

for folder in (r"C:\Windows\System32\config\systemprofile\Desktop",
               r"C:\Windows\SysWOW64\config\systemprofile\Desktop"):
    Path(folder).mkdir(parents=True, exist_ok=True)   # needs an elevated prompt

Run it once, as administrator. With the folders present, Open and SaveAs generally work in non-interactive sessions. Some environments also need the DCOM configuration for Microsoft Excel Application (in dcomcnfg) set to run as a specific user rather than The launching user. Treat both as fragile: an Office update or a policy change can undo them, which is why the robustness steps below matter even when the workaround succeeds.

Choosing how to schedule a job that uses Excel If the job only reads and writes workbook files, remove Excel and use openpyxl, xlsxwriter or pandas. If it needs the Excel application and a logged-on session is possible, run the task only when the user is logged on. Otherwise use the systemprofile Desktop workaround with a watchdog and cleanup. Does the job need the Excel application itself? no Remove Excel openpyxl, xlsxwriter, pandas yes, a session can stay logged on Interactive task run only when logged on yes, nobody logged on Workaround + watchdog Desktop folders

Make the script safe to run unattended

Whichever option you choose, write the COM code so it cannot hang and cannot leave Excel behind. Turn off everything that can raise a dialog, open files in a way that never prompts, and quit in a finally block:

Python
import logging

import pythoncom
import win32com.client as win32

log = logging.getLogger("refresh")

def refresh_workbook(path: str) -> None:
    pythoncom.CoInitialize()
    excel = win32.DispatchEx("Excel.Application")
    try:
        excel.Visible = False
        excel.DisplayAlerts = False
        excel.AskToUpdateLinks = False
        excel.EnableEvents = False
        wb = excel.Workbooks.Open(path, UpdateLinks=0, ReadOnly=False, IgnoreReadOnlyRecommended=True)
        if wb is None:
            raise RuntimeError(f"Excel could not open {path} (non-interactive session?)")
        try:
            wb.RefreshAll()
            excel.CalculateUntilAsyncQueriesDone()
            wb.Save()
            log.info("refreshed %s", path)
        finally:
            wb.Close(SaveChanges=False)
    finally:
        excel.Quit()
        del excel
        pythoncom.CoUninitialize()

Checking wb is None turns the silent failure mode of a non-interactive session into a clear error in the log. CalculateUntilAsyncQueriesDone waits for background data connections before saving, which is the usual reason a scheduled refresh saves stale data. More on COM error handling in Handle COM Errors and Excel Dialog Prompts in Python.

Add a watchdog so a hang cannot block tomorrow's run

Even with alerts off, some prompts — a corrupt-file repair, a credential dialog from a data connection — can still stall Excel. Run the COM work in a child process with a time limit, and kill any Excel it leaves behind if the limit is exceeded:

Python
import subprocess
import sys

def run_with_timeout(script: str, workbook: str, seconds: int = 900) -> int:
    proc = subprocess.Popen([sys.executable, script, workbook])
    try:
        return proc.wait(timeout=seconds)
    except subprocess.TimeoutExpired:
        proc.kill()
        # Excel started by the killed process keeps running; clean it up.
        subprocess.run(["taskkill", "/F", "/IM", "EXCEL.EXE", "/FI", "USERNAME eq %USERNAME%"],
                       capture_output=True)
        log.error("timed out after %s s on %s", seconds, workbook)
        return 1

Only kill Excel processes belonging to the automation account — on a shared machine, a blanket taskkill /IM EXCEL.EXE closes other people's workbooks. Set Task Scheduler's Stop the task if it runs longer than as a second line of defence, and have the job report failure — an email or chat alert — rather than relying on someone to notice a missing file; see Send an Alert When an Excel Report Job Fails.

Layers of protection for a scheduled Excel COM job The scheduled task starts a parent Python process, which runs the COM work in a child process with a timeout. The child turns off alerts, checks every call and quits Excel in a finally block. If the child hangs, the parent kills it and its Excel process and raises an alert, and Task Scheduler's own time limit backs this up. Task Scheduler time limit, run as automation account Parent process timeout, cleanup, alert on failure Child process alerts off, check every call, quit in finally

The better fix: do not use Excel

Most scheduled jobs that drive Excel do not need the application. They open a workbook, write values, format some cells, save, and maybe export a PDF — all things Python libraries do without Excel, faster and without any of the problems above. openpyxl edits existing workbooks, xlsxwriter builds new formatted ones, pandas reads and writes data, and LibreOffice converts to PDF headlessly. Such jobs run on Windows, Linux or in containers, under any scheduler.

Keep Excel only for the few things that genuinely need it: refreshing Power Query or OLE DB connections that only Excel can run, recalculating workbooks that use functions no other engine supports, running existing VBA macros. Even then, consider moving the data refresh out of Excel — query the source from Python — so Excel is not needed at all. The comparison of routes is laid out in xlwings vs pywin32 for Excel Automation and Choosing a Python Excel Library.

Common pitfalls

  • Testing only by hand. A job that works from your terminal proves nothing about the scheduled context. Test by triggering the task itself, with the same account and settings it will use.
  • Mapped drives. Drive letters mapped at logon do not exist in a background session. Use UNC paths such as \\server\share\reports.
  • Blanket process kills. Killing every EXCEL.EXE on a shared machine closes other users' work. Filter by user.
  • Assuming the workaround is permanent. Office updates can change behaviour. Monitor the job and alert on failure.

Conclusion

Excel expects an interactive desktop, a user profile and someone to answer dialogs, and a task running with nobody logged on provides none of them. The dependable setup is a task that runs only when a dedicated account is logged on; the systemprofile Desktop folders make background runs work in many cases but remain an unsupported workaround. Either way, disable every prompt, check each COM call, quit Excel in finally, and wrap the job in a watchdog with alerts — and wherever the job only reads and writes files, replace Excel with Python libraries and the problem disappears.

Frequently asked questions

Why does my Excel script work manually but not from Task Scheduler? When a task runs whether the user is logged on or not, it runs in a non-interactive session without a normal user desktop and profile. Excel expects both, so Workbooks.Open or SaveAs can fail with a COM error, return None, or wait on an invisible dialog.

What is the Desktop folder fix? Creating an empty Desktop folder inside C:\Windows\System32\config\systemprofile (and SysWOW64\config\systemprofile on 64-bit Windows) lets Excel open and save files when it runs under a system or non-interactive account. It is a long-standing workaround, not a supported configuration.

Does Microsoft support running Excel unattended on a server? No. Microsoft does not recommend or support server-side automation of Office applications, because they were designed for interactive use. Use an interactive session if you must drive Excel, or generate files without Excel.

What is the most reliable fix? Remove Excel from the job. If the task only reads and writes workbooks, openpyxl, xlsxwriter and pandas do it without Excel and run happily in any scheduler. Keep COM only for things that genuinely need the application, such as refreshing data connections or running macros.

More on com & pywin32

More on com & pywin32 →