04 / TESTING AND SCHEDULED AUTOMATION · LESSON 15

Decide which jobs are due

Make a reproducible scheduling decision from a supplied manifest without creating an operating-system task.

12 min · learn at your pace

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

Read the official reference.

Try an example.ps1
Get-Content '/home/user/jobs.json' -Raw | ConvertFrom-Json -DateKind String | Select-Object Name, Enabled, IntervalMinutes
YOUR TURN

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
One way to solve it.ps1
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.