Decide which jobs are due
Make a reproducible scheduling decision from a supplied manifest without creating an operating-system task.
Separate a decision from a scheduler
A scheduled tool needs a clear rule for when work is due. This lesson evaluates a fictional interval-based manifest using an explicit Now. It does not create Task Scheduler entries, cron jobs, PowerShell background jobs, or a waiting loop. PSScheduledJob is not shipped with PowerShell 7.
JSON preserves Enabled as a boolean here. Disabled jobs are Disabled with no next run. Enabled jobs need a positive interval; otherwise their state is InvalidConfig. A job with no successful run is Due now.
Work with instants and exact boundaries
For a valid previous success, add IntervalMinutes to its timestamp. A job is Due when Now is at or after that next-run instant, including equality. Preserve timestamps as strings during JSON import, then parse them deliberately as DateTimeOffset.
[DateTimeOffset]::Parse('2026-10-31T09:45:00Z').AddMinutes(15)
A success timestamp in the future produces InvalidHistory. Do not allow it to postpone a job indefinitely without surfacing the inconsistency. Disabled, invalid-configuration, and invalid-history results all use null for NextRunUtc.
This is a decision model, not a full scheduling system. It does not claim a lock, prevent overlapping runs, recover every missed interval, manage credentials, or record a successful execution. Those concerns belong to an actual runner. The browser can still test the decision function on boundary times without sleeping or depending on today's clock.
Go deeper
Get-Content '/home/user/jobs.json' -Raw | ConvertFrom-Json -DateKind String | Select-Object Name, Enabled, IntervalMinutes
Put it into practice.
Define Get-JobDecision -Jobs -Now. Emit Name, State, NextRunUtc for each job in input order. Disabled jobs → Disabled/null. Enabled non-positive intervals → InvalidConfig/null. Missing LastSuccessUtc → Due with Now. Future history → InvalidHistory/null. Otherwise NextRunUtc is last success plus interval, in UTC ISO format; State is Due when Now >= next run, else NotDue. Accept empty input. Try jobs.json at 2026-10-31T10:00:00Z.
See an explained solution
function Get-JobDecision {
param([object[]]$Jobs, [DateTimeOffset]$Now)
foreach ($job in $Jobs) {
$next = $null
if (-not $job.Enabled) { $state = 'Disabled' }
elseif ($job.IntervalMinutes -le 0) { $state = 'InvalidConfig' }
elseif (-not $job.LastSuccessUtc) { $state = 'Due'; $next = $Now }
else {
$last = [DateTimeOffset]::Parse($job.LastSuccessUtc)
if ($last -gt $Now) { $state = 'InvalidHistory' }
else {
$next = $last.AddMinutes($job.IntervalMinutes)
$state = if ($Now -ge $next) { 'Due' } else { 'NotDue' }
}
}
[pscustomobject]@{ Name=$job.Name; State=$state; NextRunUtc=$(if ($null -ne $next) { $next.ToUniversalTime().ToString('o') } else { $null }) }
}
}
$jobs = @(Get-Content '/home/user/jobs.json' -Raw | ConvertFrom-Json -DateKind String)
Get-JobDecision -Jobs $jobs -Now ([DateTimeOffset]::Parse('2026-10-31T10:00:00Z'))
Explicit time input makes the calculation reproducible. State selection keeps disabled jobs and invalid history distinct from work that is genuinely due, including the exact next-run instant.