Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Microsoft Excel

Scheduling and Running VBA Macros Automatically with Windows Task Scheduler and Application.OnTime

Learn how to build truly hands-free Excel automation by combining VBA's Application.OnTime with Windows Task Scheduler. This lesson covers everything from self-rescheduling macros to VBScript launchers, complete with production-grade error handling and logging.

⚡ Practitioner23 min readOct 6, 2026Updated Oct 6, 2026
Scheduling and Running VBA Macros Automatically with Windows Task Scheduler and Application.OnTime
On this page
  • Introduction
  • Prerequisites
  • How Application.OnTime Works
  • Scheduling Relative Delays
  • Building a Self-Repeating Macro
  • Wiring OnTime to Workbook Events
  • Building a Macro That Can Run Unattended
  • The Core Design Principles
  • Building a Persistent Log
  • Windows Task Scheduler: The Architecture
  • Writing the VBScript Bridge
  • Adding Error Handling to the VBScript
  • Configuring Windows Task Scheduler
  • Combining OnTime and Task Scheduler
  • Hands-On Exercise: A Self-Refreshing Morning Report
  • Project Structure
  • The Main Unattended Procedure
  • The VBScript Launcher
  • Common Mistakes & Troubleshooting
  • OnTime: "Macro Not Found" Error
  • Task Scheduler: Task Runs but Nothing Happens
  • The Workbook Stays Open After the Task Runs
  • OnTime Events Accumulating
  • Macro Takes Longer Than the Schedule Interval
  • Windows Sleep Interrupting OnTime
  • Real-World Extension: Multi-Step Pipeline with Dependency Checking
  • Summary & Next Steps
  • Scheduling and Running VBA Macros Automatically with the Windows Task Scheduler and Application.OnTime for Hands-Free Workbook Automation

    Introduction

    Picture this: every morning at 7:45 AM, your manager walks in expecting a fresh sales summary, updated inventory counts, and flagged exceptions from the overnight data feed. You could open Excel, run a few macros, and have it ready by 8:00 — or you could have already been drinking your coffee for an hour while Excel handled all of it automatically. That's the promise of scheduled automation, and it's entirely achievable without purchasing specialized software or learning a new tool.

    Excel VBA gives you two distinct mechanisms for scheduling work. The first — Application.OnTime — is an internal Excel clock that fires a macro at a specific time while Excel is already running. The second — Windows Task Scheduler — is an operating system-level trigger that can wake Excel from scratch, run your macro, and close it again, all without you touching a keyboard. Knowing when to use each approach, and how to build them correctly, separates workbooks that need babysitting from workbooks that run themselves.

    By the end of this lesson, you'll have built a complete, production-ready scheduled automation system. We'll start with Application.OnTime for in-session scheduling, graduate to Task Scheduler for true unattended operation, handle errors properly so failures don't leave orphaned processes, and end with a realistic project — a self-refreshing daily report that emails itself out.

    What you'll learn:

    • How Application.OnTime works, including how to schedule, reschedule, and cancel timed events correctly
    • How to write a VBA-launchable macro that saves, closes, and handles its own errors without human intervention
    • How to configure Windows Task Scheduler to open Excel and trigger a specific macro on a schedule
    • How to build a wrapper VBScript that makes Task Scheduler + Excel reliable across different machine configurations
    • How to combine both techniques for complex multi-step automation workflows
    • Best practices for logging, error handling, and making scheduled macros resilient in production environments

    Prerequisites

    This lesson assumes you're comfortable with VBA fundamentals — writing Subs and Functions, working with workbooks and worksheets programmatically, and navigating the Visual Basic Editor. If you need a refresher on procedure scope and how Subs interact with each other, the article on writing VBA procedures and functions is worth reviewing. You should also understand basic error handling patterns; we'll extend them here, but if On Error GoTo is unfamiliar territory, spend a few minutes with Error Handling and Debugging VBA Code Like a Pro first.

    You'll need Windows (Task Scheduler is Windows-only), Excel 2016 or later, and permissions to create scheduled tasks on your machine or server.


    How Application.OnTime Works

    Application.OnTime is Excel's built-in mechanism for scheduling a procedure to run at a specific clock time — or after a specified delay — within an active Excel session. Think of it as setting an alarm inside Excel itself.

    The full syntax is:

    Application.OnTime EarliestTime, Procedure, LatestTime, Schedule
    
    • EarliestTime: A Date/Time value specifying when to fire the macro
    • Procedure: A string — the exact name of the procedure to run
    • LatestTime (optional): If Excel is busy at EarliestTime, how long to keep trying before giving up
    • Schedule (optional): True to schedule (default), False to cancel a previously scheduled event

    Here's the simplest working example:

    Sub ScheduleRefresh()
        Application.OnTime TimeValue("08:00:00"), "RefreshDailyReport"
    End Sub
    
    Sub RefreshDailyReport()
        ' Your actual automation logic lives here
        ThisWorkbook.Sheets("Data").Range("A1").Calculate
        ThisWorkbook.Save
        MsgBox "Report refreshed at " & Now()
    End Sub
    

    Call ScheduleRefresh and Excel will fire RefreshDailyReport at exactly 8:00 AM — as long as Excel stays open until then.

    Scheduling Relative Delays

    You don't always want to schedule for an absolute clock time. Sometimes you want to run something 5 minutes from now, or repeat on a cycle. Use Now + TimeValue() for relative scheduling:

    Sub ScheduleInFiveMinutes()
        Application.OnTime Now + TimeValue("00:05:00"), "RefreshDailyReport"
    End Sub
    

    Building a Self-Repeating Macro

    The real power emerges when a macro reschedules itself. This creates a polling loop — useful for monitoring a data source, refreshing a dashboard, or checking for new files in a folder. The key is that the macro calls the scheduler again at the end of its own execution:

    ' Store the next run time so we can cancel it later
    Private dtNextRun As Date
    
    Sub StartHourlyRefresh()
        dtNextRun = Now + TimeValue("01:00:00")
        Application.OnTime dtNextRun, "HourlyRefreshCycle"
    End Sub
    
    Sub HourlyRefreshCycle()
        On Error GoTo ErrorHandler
        
        ' Do the actual work
        Call RefreshDataConnections
        Call UpdateKPISummary
        Call LogRefreshEvent("Hourly refresh completed successfully")
        
        ' Reschedule for the next hour
        dtNextRun = Now + TimeValue("01:00:00")
        Application.OnTime dtNextRun, "HourlyRefreshCycle"
        Exit Sub
        
    ErrorHandler:
        Call LogRefreshEvent("ERROR in HourlyRefreshCycle: " & Err.Description)
        ' Still reschedule even on error, so the cycle doesn't die permanently
        dtNextRun = Now + TimeValue("01:00:00")
        Application.OnTime dtNextRun, "HourlyRefreshCycle"
    End Sub
    
    Sub StopHourlyRefresh()
        On Error Resume Next  ' Prevents crash if no event was scheduled
        Application.OnTime dtNextRun, "HourlyRefreshCycle", , False
        On Error GoTo 0
    End Sub
    

    Key insight

    The Private dtNextRun As Date variable at module level is essential. When you cancel an OnTime event, you must pass the exact same time value that was used to schedule it. Without storing it, you have no way to cancel cleanly. Many tutorials skip this step — then their "stop" buttons don't work.

    Wiring OnTime to Workbook Events

    For a production setup, you want the schedule to start automatically when the workbook opens and cancel cleanly when it closes. That means putting your scheduler calls in the ThisWorkbook module:

    ' In ThisWorkbook module
    Private Sub Workbook_Open()
        Call StartHourlyRefresh
    End Sub
    
    Private Sub Workbook_BeforeClose(Cancel As Boolean)
        Call StopHourlyRefresh
    End Sub
    

    This pattern — combined with the event-driven approach explored in Building a Custom VBA Event-Driven Framework — gives you a workbook that manages its own schedule lifecycle without any manual intervention.

    Warning

    Application.OnTime only fires while Excel is open and not in a suspended state. If the user closes Excel, the scheduled event disappears. If the machine goes to sleep, the event may be delayed or skipped entirely. For true unattended overnight automation, you need Windows Task Scheduler.


    Building a Macro That Can Run Unattended

    Before you configure any scheduler, you need to make sure your macro is actually capable of running without a human watching it. A macro that throws an unhandled error, displays a dialog box, or hangs waiting for user input will silently fail at 3 AM and nobody will know.

    The Core Design Principles

    1. No interactive dialogs. Replace MsgBox calls with log entries. Never prompt the user during an unattended run.

    2. Suppress Excel's own prompts. Excel itself will sometimes interrupt with "Do you want to save?" or "This file contains macros." Suppress these programmatically.

    3. Catch and log every error. Don't let On Error Resume Next swallow problems silently — log them to a worksheet or a text file.

    4. Always save and close cleanly. If you're running unattended, the workbook needs to reach a known state even when something goes wrong.

    Here's a production-grade wrapper template you can adapt:

    Sub RunUnattended()
        ' Suppress Excel prompts
        Application.DisplayAlerts = False
        Application.ScreenUpdating = False
        
        On Error GoTo CleanupAndExit
        
        ' === YOUR ACTUAL WORK GOES HERE ===
        Call RefreshAllDataSources
        Call RebuildSummaryTables
        Call FormatAndExportReport
        Call SendReportByEmail
        ' ==================================
        
        Call WriteToLog("SUCCESS", "Unattended run completed at " & Now())
        
    CleanupAndExit:
        If Err.Number <> 0 Then
            Call WriteToLog("ERROR", Err.Number & " - " & Err.Description)
        End If
        
        ' Always restore Excel settings and save
        Application.DisplayAlerts = True
        Application.ScreenUpdating = True
        ThisWorkbook.Save
    End Sub
    

    Building a Persistent Log

    A log worksheet is your single most important diagnostic tool for scheduled automation. When something goes wrong at 2 AM, you need a paper trail.

    Sub WriteToLog(LogLevel As String, Message As String)
        Dim wsLog As Worksheet
        Dim nextRow As Long
        
        On Error Resume Next
        Set wsLog = ThisWorkbook.Sheets("AutoLog")
        On Error GoTo 0
        
        ' Create the log sheet if it doesn't exist yet
        If wsLog Is Nothing Then
            Set wsLog = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
            wsLog.Name = "AutoLog"
            wsLog.Range("A1:D1").Value = Array("Timestamp", "Level", "MachineName", "Message")
            wsLog.Range("A1:D1").Font.Bold = True
        End If
        
        nextRow = wsLog.Cells(wsLog.Rows.Count, 1).End(xlUp).Row + 1
        
        wsLog.Cells(nextRow, 1).Value = Now()
        wsLog.Cells(nextRow, 1).NumberFormat = "yyyy-mm-dd hh:mm:ss"
        wsLog.Cells(nextRow, 2).Value = LogLevel
        wsLog.Cells(nextRow, 3).Value = Environ("COMPUTERNAME")
        wsLog.Cells(nextRow, 4).Value = Message
        
        ' Color-code by level for easier scanning
        If LogLevel = "ERROR" Then
            wsLog.Cells(nextRow, 2).Interior.Color = RGB(255, 199, 206)
        ElseIf LogLevel = "SUCCESS" Then
            wsLog.Cells(nextRow, 2).Interior.Color = RGB(198, 239, 206)
        End If
        
        ThisWorkbook.Save
    End Sub
    

    Tip

    Adding the machine's computer name to each log entry becomes critical if you're running the same workbook across multiple machines or servers. It helps you immediately identify which environment generated a given log line.


    Windows Task Scheduler: The Architecture

    Windows Task Scheduler is an OS-level service that can trigger programs, scripts, and commands on a schedule — even when no user is logged in interactively (with the right configuration). The challenge is that Task Scheduler doesn't know what a "VBA macro" is. It can only run executables and scripts. So you need a bridge.

    The architecture looks like this:

    Task Scheduler
         ↓  (triggers)
    VBScript (.vbs file)
         ↓  (launches and controls)
    Excel.Application (COM object)
         ↓  (opens workbook and)
         ↓  (runs your VBA macro)
    Excel closes
    

    Writing the VBScript Bridge

    Create a plain text file with a .vbs extension. This script uses late-binding COM automation to drive Excel without Excel being visible:

    ' RunExcelMacro.vbs
    ' Place this file in a stable path, e.g., C:\Automation\RunExcelMacro.vbs
    
    Option Explicit
    
    Dim xlApp
    Dim xlBook
    Dim sWorkbookPath
    Dim sMacroName
    
    sWorkbookPath = "C:\Automation\DailyReport.xlsm"
    sMacroName    = "Module1.RunUnattended"
    
    On Error GoTo 0
    
    ' Create a hidden Excel instance
    Set xlApp = CreateObject("Excel.Application")
    xlApp.Visible = False
    xlApp.DisplayAlerts = False
    
    ' Open the workbook
    Set xlBook = xlApp.Workbooks.Open(sWorkbookPath, False, False)
    
    ' Run the target macro
    xlApp.Run sMacroName
    
    ' Save and close cleanly
    xlBook.Save
    xlBook.Close False
    xlApp.Quit
    
    ' Release COM objects
    Set xlBook = Nothing
    Set xlApp = Nothing
    

    A few important details about this script:

    • xlApp.Visible = False keeps Excel hidden — no flashing windows on the server desktop
    • xlApp.Run sMacroName uses the fully qualified name "Module1.MacroName" to avoid ambiguity when multiple modules exist
    • The explicit xlBook.Close and xlApp.Quit are non-negotiable. If you skip these, Excel processes will accumulate in memory every time the task runs until the machine runs out of resources

    Warning

    If your VBA macro itself calls Application.Quit or ThisWorkbook.Close, you'll create a conflict with the VBScript cleanup code. Let the VBScript own the open/close lifecycle and let the macro focus only on the work.

    Adding Error Handling to the VBScript

    The bare version above will silently fail if the workbook path is wrong or Excel is already open with a conflicting lock. A more robust version:

    ' RunExcelMacro_Robust.vbs
    Option Explicit
    
    Dim xlApp
    Dim xlBook
    Dim sWorkbookPath
    Dim sMacroName
    Dim sLogPath
    Dim fso
    Dim logFile
    
    sWorkbookPath = "C:\Automation\DailyReport.xlsm"
    sMacroName    = "Module1.RunUnattended"
    sLogPath      = "C:\Automation\Logs\TaskScheduler_" & Year(Now) & Month(Now) & Day(Now) & ".txt"
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    Set logFile = fso.OpenTextFile(sLogPath, 8, True)  ' 8 = Append
    
    logFile.WriteLine Now() & " | Starting Excel automation"
    
    On Error Resume Next
    
    Set xlApp = CreateObject("Excel.Application")
    If Err.Number <> 0 Then
        logFile.WriteLine Now() & " | FATAL: Could not create Excel instance. " & Err.Description
        logFile.Close
        WScript.Quit 1
    End If
    Err.Clear
    
    xlApp.Visible = False
    xlApp.DisplayAlerts = False
    
    Set xlBook = xlApp.Workbooks.Open(sWorkbookPath, False, False)
    If Err.Number <> 0 Then
        logFile.WriteLine Now() & " | FATAL: Could not open workbook. " & Err.Description
        xlApp.Quit
        logFile.Close
        WScript.Quit 1
    End If
    Err.Clear
    
    xlApp.Run sMacroName
    If Err.Number <> 0 Then
        logFile.WriteLine Now() & " | ERROR in macro: " & Err.Description
    End If
    
    xlBook.Save
    xlBook.Close False
    xlApp.Quit
    
    Set xlBook = Nothing
    Set xlApp = Nothing
    
    logFile.WriteLine Now() & " | Automation complete"
    logFile.Close
    Set logFile = Nothing
    Set fso = Nothing
    

    This version maintains its own date-stamped text log alongside whatever logging your VBA macro does. When both logs agree, you know exactly what happened.

    Configuring Windows Task Scheduler

    Now wire up Task Scheduler to run this script on a schedule. Here's the step-by-step:

    Step 1: Open Task Scheduler Press the Windows key, type "Task Scheduler," and open it. In the right-hand Actions panel, click "Create Task" (not "Create Basic Task" — you want the full dialog).

    Step 2: General tab Give the task a clear name like "Daily Report Automation - 7:45 AM." Set the description to include the workbook path and macro name for future reference.

    In the "Security options" section, choose "Run whether user is logged on or not" if you want true headless execution. Select "Run with highest privileges" to avoid permission issues with network paths. Choose your Windows account or a dedicated service account.

    Key insight

    "Run whether user is logged on or not" requires you to enter your Windows password when saving the task. This is how Windows stores credentials to run the job on your behalf. If you change your Windows password later, you must update the task's credentials or it will silently fail.

    Step 3: Triggers tab Click "New" to add a trigger. Choose "Daily" and set the start time to 7:45 AM. Set "Recur every 1 days." If you want it to run only on weekdays, you'll need to either use a "Weekly" trigger with Mon-Fri checked, or add conditional logic inside your VBScript.

    Step 4: Actions tab Click "New." Set Action to "Start a program."

    In the "Program/script" field, enter:

    wscript.exe
    

    In the "Add arguments" field, enter the full path to your VBScript in quotes:

    "C:\Automation\RunExcelMacro_Robust.vbs"
    

    In the "Start in" field, enter the directory containing your script:

    C:\Automation\
    

    Step 5: Conditions tab Uncheck "Start the task only if the computer is on AC power" if this is a laptop that might be on battery. Check "Wake the computer to run this task" if the machine might be in sleep mode.

    Step 6: Settings tab Set "If the task is already running, then the following rule applies" to "Do not start a new instance" — this prevents overlapping runs if your macro takes longer than expected. Set a reasonable "Stop the task if it runs longer than" value — 30 minutes is a good default for most reporting macros.

    Click OK and enter your credentials when prompted.

    Testing the task before relying on it: Right-click the newly created task and choose "Run." Watch Task Scheduler's Last Run Result column — a value of 0x0 means success. Any other code means failure. Check your text log file to see what happened.


    Combining OnTime and Task Scheduler

    These two tools aren't mutually exclusive — they solve different problems and can work together in a layered architecture.

    Consider a scenario where you run a heavy data refresh at 6:00 AM via Task Scheduler (which opens Excel fresh), but then want the workbook to re-refresh every 30 minutes throughout the business day while a team member has it open. Task Scheduler handles the overnight heavy lift; Application.OnTime handles the daytime polling:

    ' In ThisWorkbook module
    Private Sub Workbook_Open()
        Dim currentHour As Integer
        currentHour = Hour(Now())
        
        ' Only start the polling cycle during business hours
        If currentHour >= 8 And currentHour < 18 Then
            Call StartThirtyMinutePolling
            Call WriteToLog("INFO", "Workbook opened during business hours. Polling started.")
        Else
            Call WriteToLog("INFO", "Workbook opened outside business hours. No polling scheduled.")
        End If
    End Sub
    
    Private Sub Workbook_BeforeClose(Cancel As Boolean)
        Call StopThirtyMinutePolling
    End Sub
    

    This kind of layered thinking — understanding which tool owns which part of the schedule — is what makes automation genuinely robust rather than fragile.

    Tip

    If your macro connects to a database or external API, combine the scheduling patterns here with the patterns from Connecting Excel to External Databases with VBA and Integrating Excel VBA with REST APIs. The scheduling mechanics are identical; only the data retrieval code changes.


    Hands-On Exercise: A Self-Refreshing Morning Report

    Let's put everything together into a realistic project. You'll build a workbook that:

    1. Gets triggered at 7:00 AM by Task Scheduler
    2. Pulls updated data from a CSV file dropped by an upstream system
    3. Refreshes a summary table
    4. Saves a dated export copy
    5. Emails the report to a distribution list
    6. Logs every step
    7. Closes Excel cleanly

    Project Structure

    Create a new .xlsm workbook called DailyReport.xlsm saved to C:\Automation\. Add these sheets:

    • RawData — where the CSV import lands
    • Summary — the calculated summary table
    • AutoLog — the automation log

    Add a standard module called Module1 and a ThisWorkbook module.

    The Main Unattended Procedure

    ' Module1
    Option Explicit
    
    Private Const SOURCE_CSV As String = "C:\Automation\DataDrop\daily_feed.csv"
    Private Const EXPORT_FOLDER As String = "C:\Automation\Exports\"
    
    Sub RunMorningReport()
        Application.DisplayAlerts = False
        Application.ScreenUpdating = False
        Application.Calculation = xlCalculationManual
        
        On Error GoTo ErrorHandler
        
        Call WriteToLog("INFO", "Morning report started")
        
        ' Step 1: Import fresh CSV data
        Call ImportCSVData
        Call WriteToLog("INFO", "CSV import complete")
        
        ' Step 2: Recalculate summary
        Application.Calculation = xlCalculationAutomatic
        Application.CalculateFullRebuild
        Application.Calculation = xlCalculationManual
        Call WriteToLog("INFO", "Summary recalculated")
        
        ' Step 3: Export a dated copy
        Call ExportDatedCopy
        Call WriteToLog("INFO", "Export saved")
        
        ' Step 4: Email the report
        Call EmailReport
        Call WriteToLog("SUCCESS", "Morning report completed and emailed")
        
        GoTo Cleanup
        
    ErrorHandler:
        Call WriteToLog("ERROR", "RunMorningReport failed at step. Error " & _
                       Err.Number & ": " & Err.Description)
    
    Cleanup:
        Application.DisplayAlerts = True
        Application.ScreenUpdating = True
        Application.Calculation = xlCalculationAutomatic
        ThisWorkbook.Save
    End Sub
    
    Sub ImportCSVData()
        Dim wsRaw As Worksheet
        Dim wbCSV As Workbook
        Dim wsCSV As Worksheet
        
        Set wsRaw = ThisWorkbook.Sheets("RawData")
        wsRaw.Cells.Clear
        
        ' Check the file exists before trying to open it
        If Dir(SOURCE_CSV) = "" Then
            Err.Raise vbObjectError + 1001, "ImportCSVData", _
                      "Source file not found: " & SOURCE_CSV
        End If
        
        ' Open CSV as a separate workbook and copy the data across
        Set wbCSV = Workbooks.Open(SOURCE_CSV)
        Set wsCSV = wbCSV.Sheets(1)
        
        wsCSV.UsedRange.Copy wsRaw.Range("A1")
        
        wbCSV.Close False  ' Close without saving
        
        Set wsCSV = Nothing
        Set wbCSV = Nothing
    End Sub
    
    Sub ExportDatedCopy()
        Dim exportPath As String
        Dim dateSuffix As String
        
        dateSuffix = Format(Now(), "YYYY-MM-DD")
        exportPath = EXPORT_FOLDER & "DailyReport_" & dateSuffix & ".xlsm"
        
        ' Create the export folder if it doesn't exist
        If Dir(EXPORT_FOLDER, vbDirectory) = "" Then
            MkDir EXPORT_FOLDER
        End If
        
        ThisWorkbook.SaveCopyAs exportPath
    End Sub
    
    Sub EmailReport()
        ' Requires a reference to or late-binding of Outlook
        Dim olApp As Object
        Dim olMail As Object
        Dim dateSuffix As String
        
        dateSuffix = Format(Now(), "YYYY-MM-DD")
        
        Set olApp = CreateObject("Outlook.Application")
        Set olMail = olApp.CreateItem(0)  ' 0 = olMailItem
        
        With olMail
            .To = "reporting-team@yourcompany.com"
            .Subject = "Daily Report - " & dateSuffix
            .Body = "Good morning," & vbCrLf & vbCrLf & _
                    "Please find today's automated daily report attached." & vbCrLf & _
                    "Generated at: " & Now() & vbCrLf & vbCrLf & _
                    "This is an automated message. Do not reply."
            .Attachments.Add EXPORT_FOLDER & "DailyReport_" & dateSuffix & ".xlsm"
            .Send
        End With
        
        Set olMail = Nothing
        Set olApp = Nothing
    End Sub
    

    The emailing pattern used here connects naturally to the broader topic covered in Automating Email and File Operations with VBA — that article goes deeper on attachment handling, distribution lists, and HTML-formatted email bodies.

    The VBScript Launcher

    Save this as C:\Automation\RunMorningReport.vbs:

    Option Explicit
    
    Dim xlApp, xlBook
    Dim sPath : sPath = "C:\Automation\DailyReport.xlsm"
    Dim sMacro : sMacro = "Module1.RunMorningReport"
    
    On Error Resume Next
    
    Set xlApp = CreateObject("Excel.Application")
    xlApp.Visible = False
    xlApp.DisplayAlerts = False
    
    Set xlBook = xlApp.Workbooks.Open(sPath, False, False)
    xlApp.Run sMacro
    
    xlBook.Save
    xlBook.Close False
    xlApp.Quit
    
    Set xlBook = Nothing
    Set xlApp = Nothing
    

    Then create a Task Scheduler task pointing to this VBScript, triggered daily at 7:00 AM. Run the task manually once to confirm everything works, then check the AutoLog sheet in your workbook.


    Common Mistakes & Troubleshooting

    OnTime: "Macro Not Found" Error

    This is the most common Application.OnTime failure. The procedure name you pass must be an exact string match, and it must be accessible from the workbook's project. Common causes:

    • The procedure is Private — OnTime can't call private procedures. Make it Public or remove the access modifier
    • The module name is misspelled or the procedure was renamed
    • The macro is in a different open workbook — prefix with the workbook name: "'DailyReport.xlsm'!Module1.RefreshData"

    Task Scheduler: Task Runs but Nothing Happens

    First check: is your VBScript writing to its log file? If it isn't, the script itself is failing to start. Check that wscript.exe is the program and the VBS path is in double quotes in the arguments field.

    Second check: look at Task Scheduler's "Last Run Result." 0x1 often means the script ran but exited with an error. 0x41301 means the task is already running. 0x41306 means the task was terminated.

    Third check: Excel trust settings. When Excel is opened by an external process, macros may be disabled by Trust Center settings. You need to either trust the folder containing your workbook, or use a self-signed certificate. In the Trust Center (File > Options > Trust Center > Trust Center Settings > Trusted Locations), add C:\Automation\ as a trusted location.

    Warning

    Never set "Trust access to the VBA project object model" unless you specifically need VBA code to manipulate other VBA projects. This setting opens a significant security surface area. For scheduled macros, trusted locations alone are sufficient.

    The Workbook Stays Open After the Task Runs

    This means xlApp.Quit in your VBScript didn't execute — usually because an unhandled error in the VBA macro halted the VBScript before it reached cleanup. Check both the VBScript text log and the AutoLog worksheet. Use Task Manager to kill the orphaned EXCEL.EXE process manually, then fix the underlying error before the next scheduled run.

    OnTime Events Accumulating

    If you call StartHourlyRefresh multiple times without canceling the previous event, you end up with multiple independent OnTime events all trying to run HourlyRefreshCycle — each one then schedules another event. Within a few hours you can have dozens of overlapping runs. The fix is to always cancel before re-scheduling, or add a guard variable:

    Private bPollingActive As Boolean
    
    Sub StartHourlyRefresh()
        If bPollingActive Then Exit Sub  ' Already running, don't double-schedule
        bPollingActive = True
        dtNextRun = Now + TimeValue("01:00:00")
        Application.OnTime dtNextRun, "HourlyRefreshCycle"
    End Sub
    

    Macro Takes Longer Than the Schedule Interval

    If your macro takes 20 minutes to run and Task Scheduler fires it every 15, you'll start accumulating Excel processes. The "Do not start a new instance" setting in Task Scheduler's Settings tab prevents a second instance from starting, but it won't extend the first run's time limit. Make sure your "Stop the task if it runs longer than" value gives enough headroom, and consider whether your schedule interval is realistic.

    Tip

    Add timing instrumentation to your log during development. Record Now() at the start and end of each major step. You'll quickly see which operations are taking longer than expected and can optimize them using techniques from Excel Performance Optimization.

    Windows Sleep Interrupting OnTime

    If a machine enters sleep mode between an OnTime schedule and its trigger time, the event may fire late or not at all. Either disable sleep on the automation machine, or configure Task Scheduler to wake the computer (which is more reliable than relying on OnTime for multi-hour gaps).


    Real-World Extension: Multi-Step Pipeline with Dependency Checking

    Production automation rarely involves a single macro running in isolation. More often you have a pipeline: data arrives, gets validated, gets transformed, gets reported. Here's a pattern for chaining steps with dependency checks — each step only runs if the previous one succeeded:

    Sub RunPipeline()
        Dim bSuccess As Boolean
        
        bSuccess = RunStep("Step 1: Validate Source File", AddressOf ValidateSourceFile)
        If Not bSuccess Then GoTo PipelineFailed
        
        bSuccess = RunStep("Step 2: Import Data", AddressOf ImportCSVData)
        If Not bSuccess Then GoTo PipelineFailed
        
        bSuccess = RunStep("Step 3: Transform Data", AddressOf TransformRawData)
        If Not bSuccess Then GoTo PipelineFailed
        
        bSuccess = RunStep("Step 4: Rebuild Summary", AddressOf RebuildSummary)
        If Not bSuccess Then GoTo PipelineFailed
        
        bSuccess = RunStep("Step 5: Export and Email", AddressOf ExportAndEmail)
        If Not bSuccess Then GoTo PipelineFailed
        
        Call WriteToLog("SUCCESS", "Full pipeline completed successfully")
        Exit Sub
    
    PipelineFailed:
        Call WriteToLog("ERROR", "Pipeline halted. Check preceding log entries.")
        Call SendAlertEmail("AUTOMATION ALERT: Pipeline failed on " & Format(Now(), "YYYY-MM-DD"))
    End Sub
    
    Function RunStep(stepName As String, stepProc As LongPtr) As Boolean
        On Error GoTo StepFailed
        Call WriteToLog("INFO", "Starting: " & stepName)
        
        ' Note: In practice, replace this pattern with direct Sub calls
        ' or a Select Case dispatcher — VBA doesn't support function pointers natively
        ' This is pseudocode to illustrate the pattern
        
        Call WriteToLog("INFO", "Completed: " & stepName)
        RunStep = True
        Exit Function
        
    StepFailed:
        Call WriteToLog("ERROR", stepName & " failed: " & Err.Description)
        RunStep = False
    End Function
    

    The pipeline pattern pairs naturally with Building an Automated Reporting System with VBA, which covers the full architecture of multi-output report generation in detail. For more sophisticated data transformations in the middle of the pipeline, see Building a Self-Updating Excel Report with Power Query, VBA, and Scheduled Refresh.


    Summary & Next Steps

    You now have a complete toolkit for hands-free Excel automation. Let's consolidate what you've built:

    Application.OnTime is your tool for in-session scheduling — timed events, repeating cycles, and business-hours polling. Its key characteristics: it requires Excel to be open, uses the Workbook_Open/Workbook_BeforeClose pair to manage the lifecycle, and must store the scheduled time in a module-level variable so you can cancel it cleanly.

    Windows Task Scheduler + VBScript is your tool for true unattended automation — opening Excel from scratch, running a macro, and closing it again. Its key requirements: macros must suppress all interactive prompts, the VBScript must own the open/close lifecycle, Excel's trusted locations must include your workbook folder, and Task Scheduler credentials must be kept current.

    Production resilience depends on three things: comprehensive logging (both VBScript-level and VBA-level), error handling that recovers gracefully and never leaves Excel orphaned, and performance awareness so macro run times stay well within schedule intervals.

    For your next steps, consider combining this scheduling infrastructure with the dashboard automation covered in Building a Dynamic Excel Dashboard with VBA — a dashboard that refreshes itself every morning and is ready for the team when they arrive is a genuinely powerful deliverable. If you're distributing this solution across multiple users or machines, packaging it as an Excel Add-In using the patterns in Building Excel Add-Ins with VBA will make deployment and updates far more manageable.

    The goal was always automation that runs itself. You're there.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Advanced Excel & VBA

    Previous

    VBA UserForm Controls Deep Dive: ComboBoxes, ListBoxes, and MultiPage Widgets for Professional Data Entry Applications

    Related Insights

    Microsoft ExcelPractitioner

    Mastering Excel's IFERROR-Free Approach: Building Robust Lookup Formulas with ISBLANK, ISNUMBER, and ISTEXT for Professional Data Validation

    20 min
    Microsoft ExcelFoundation

    VBA UserForm Controls Deep Dive: ComboBoxes, ListBoxes, and MultiPage Widgets for Professional Data Entry Applications

    17 min
    Microsoft ExcelFoundation

    Understanding Excel Workbook Structure: Worksheets, Cells, Rows, and Columns for Data Professionals

    17 min

    On this page

    • Introduction
    • Prerequisites
    • How Application.OnTime Works
    • Scheduling Relative Delays
    • Building a Self-Repeating Macro
    • Wiring OnTime to Workbook Events
    • Building a Macro That Can Run Unattended
    • The Core Design Principles
    • Building a Persistent Log
    • Windows Task Scheduler: The Architecture
    • Writing the VBScript Bridge
    • Adding Error Handling to the VBScript
    • Configuring Windows Task Scheduler
    • Combining OnTime and Task Scheduler
    • Hands-On Exercise: A Self-Refreshing Morning Report
    • Project Structure
    • The Main Unattended Procedure
    • The VBScript Launcher
    • Common Mistakes & Troubleshooting
    • OnTime: "Macro Not Found" Error
    • Task Scheduler: Task Runs but Nothing Happens
    • The Workbook Stays Open After the Task Runs
    • OnTime Events Accumulating
    • Macro Takes Longer Than the Schedule Interval
    • Windows Sleep Interrupting OnTime
    • Real-World Extension: Multi-Step Pipeline with Dependency Checking
    • Summary & Next Steps