Windows Home Server — Part 16: Replacing Paid Cron & Event Schedulers (Cronitor, AWS EventBridge)
Cloud scheduling tools like Cronitor, AWS EventBridge, and Google Cloud Scheduler charge based on job executions, and charge extra for features like alerting.
We can run complex scheduled tasks on bare-metal Windows for free. We will use the built-in Windows Task Scheduler combined with a custom PowerShell wrapper script. This wrapper executes any scheduled script, logs its console output, monitors its runtime duration, and sends a Telegram notification if the script fails.
1. The Wrapper Architecture
A common issue with schedulers is silent failures. If a script crashes, the scheduler reports it, but you are not notified. Our PowerShell wrapper captures these exceptions:
[Task Scheduler] ──> [run-task.ps1] ──> Executes: [target-script.ps1]
│
Did it exit with Code 0?
/ \
(Yes) (No)
/ \
[Log Success] [Send Telegram Alert]
2. Implementing the PowerShell Task Wrapper
Create C:\Server\bin\run-task.ps1. This script wraps the execution of your target scripts, checks their exit codes, and alerts you on failures:
param (
[string]$TaskName,
[string]$ScriptPath,
[string]$Arguments = ""
)
# Configurations
$LogFile = "C:\Server\logs\tasks.log"
$TelegramToken = "YOUR_TELEGRAM_BOT_TOKEN"
$TelegramChatId = "YOUR_TELEGRAM_CHAT_ID"
function Send-TelegramAlert ($msg) {
$body = @{
chat_id = $TelegramChatId
text = "🚨 Task Failure: $msg"
} | ConvertTo-Json
$uri = "https://api.telegram.org/bot$TelegramToken/sendMessage"
Invoke-RestMethod -Uri $uri -Method Post -Body $body -ContentType "application/json" | Out-Null
}
$StartTime = Get-Date
Add-Content -Path $LogFile -Value "[$StartTime] START task '$TaskName' executing '$ScriptPath'"
try {
# Start the target script, capturing output and exit codes
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.FileName = "powershell.exe"
$processInfo.Arguments = "-ExecutionPolicy Bypass -File `"$ScriptPath`" $Arguments"
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.UseShellExecute = $false
$processInfo.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
$process.Start() | Out-Null
$stdout = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
$ExitCode = $process.ExitCode
$EndTime = Get-Date
$Duration = ($EndTime - $StartTime).TotalSeconds
if ($ExitCode -ne 0) {
# Task failed
Add-Content -Path $LogFile -Value "[$EndTime] FAILED task '$TaskName' (Exit Code: $ExitCode) in $Duration seconds`nErrors: $stderr"
Send-TelegramAlert "Task '$TaskName' failed with exit code $ExitCode in $Duration seconds.`nErrors: $stderr"
} else {
# Task succeeded
Add-Content -Path $LogFile -Value "[$EndTime] SUCCESS task '$TaskName' in $Duration seconds."
}
} catch {
# Wrapper level crash
$EndTime = Get-Date
Add-Content -Path $LogFile -Value "[$EndTime] CRITICAL EXCEPTION running '$TaskName': $_"
Send-TelegramAlert "Critical error running task '$TaskName': $_"
}
3. Scheduling a Task via Command Line
We can schedule our wrapper to run tasks using PowerShell's built-in scheduling cmdlets.
Let's schedule our Database Backup Script (from Part 10) to run daily at 03:00 AM:
# Define action pointing to our wrapper, passing the script parameters
$Action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File C:\Server\bin\run-task.ps1 -TaskName 'DatabaseBackup' -ScriptPath 'C:\Server\bin\backup-databases.ps1'"
# Define daily trigger
$Trigger = New-ScheduledTaskTrigger -Daily -At "3:00 AM"
# Define execution settings (run under SYSTEM account so it runs headlessly)
$Principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
# Register the task
Register-ScheduledTask `
-TaskName "Server-Database-Backup" `
-Action $Action `
-Trigger $Trigger `
-Principal $Principal
4. Alternative: Node-Cron in PM2
If you are a Node.js developer, you can schedule tasks directly in your JavaScript applications and manage them using PM2.
Install node-cron inside your Node directory:
cd C:\Server\apps\
npm install node-cron
Create a script C:\Server\apps\cron-job.js:
const cron = require('node-cron');
const { exec } = require('child_process');
// Run every hour
cron.schedule('0 * * * *', () => {
console.log('Running hourly logs rotation...');
exec('powershell.exe -File C:\\Server\\bin\\rotate-logs.ps1', (err, stdout, stderr) => {
if (err) {
console.error(`Cron job failed: ${err.message}`);
}
});
});
Using Windows Task Scheduler for system-level scripts and PM2 with Node-Cron for application tasks gives you a robust, zero-cost scheduling solution, replacing paid SaaS monitors for free.
In the next part, we will replace paid link-in-bio and URL shortener tools.
Proceed to Part 17: Replacing Paid Link Shorteners & Bio-Links (Linktree, Bitly) →
Comments
Comments are powered by giscus. Set
PUBLIC_GISCUS_REPO_IDandPUBLIC_GISCUS_CATEGORY_IDin your environment to enable them.