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.

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:
Application.OnTime works, including how to schedule, reschedule, and cancel timed events correctlyThis 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.
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
True to schedule (default), False to cancel a previously scheduled eventHere'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.
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
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.
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.
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.
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
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 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
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 desktopxlApp.Run sMacroName uses the fully qualified name "Module1.MacroName" to avoid ambiguity when multiple modules existxlBook.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 resourcesWarning
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.
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.
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.
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.
Let's put everything together into a realistic project. You'll build a workbook that:
Create a new .xlsm workbook called DailyReport.xlsm saved to C:\Automation\. Add these sheets:
Add a standard module called Module1 and a ThisWorkbook module.
' 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.
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.
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:
Private — OnTime can't call private procedures. Make it Public or remove the access modifier"'DailyReport.xlsm'!Module1.RefreshData"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.
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.
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
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.
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).
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.
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.