Scheduling travel access in Pia: schedule_for_later doesn't wait

Travelers need to be in their company's travel-allowed group while they're away. Someone has to add them on the day they leave and remove them the night they get back, and that someone used to be an engineer with a calendar reminder. The first version of this automation was hardcoded to one customer. This one works for any client tenant, and the hard part turned out to be the scheduling step.

The date-handling bug is its own post: Time zone bugs in scheduled actions. This one covers the rest.

What the engineer sees

Only list groups Graph can actually change

The user list is every enabled member account. Guests and disabled leavers are left out.

The group list is anything with "travel" in the name, as long as Graph can add members to it. That means cloud security groups (security-enabled, not mail-enabled) and Microsoft 365 groups. Dynamic groups, groups synced from on-premises AD, and mail-enabled security groups are skipped, because the group-add step would fail on them. Better to never offer a group than to fail on it three days later.

If the tenant has only one travel group, it comes pre-selected. Both lists page through Graph 999 at a time.

schedule_for_later doesn't wait. It forks.

This one took me the longest. I assumed schedule_for_later paused the run until the date. It doesn't. The live run carries straight on past it, and a separate copy of the run resumes later, on the scheduled date, with running_schedule set to true.

So without a guard, the step right after the schedule, adding the user to the group, runs immediately on the live run, days early. Then the scheduled copy runs it again on the right day.

The fix is a small check after every schedule step that decides whether this copy should keep going:

$skipped = ([string]$skippedSchedule).Trim('"') -eq 'true'
$running = ([string]$runningSchedule).Trim('"') -eq 'true'
$shouldProceed = $skipped -or $running
$stopCondition = if ($shouldProceed) { "False" } else { "True" }

Keep going if scheduling was skipped, because the date was today and there was nothing to wait for. Keep going if this is the scheduled copy, because the date has arrived. Otherwise this is the live run that just set up the wait, and it stops. A stop task right after the check does the stopping.

The same check guards the removal. Chat messages after the group-add only go out on the same-day path, since nobody is watching the scheduled copy when it runs.

One more detail: the skip on the schedule step is the literal string "true" or "false", built in PowerShell. That's the only thing Pia's skip condition accepts.

Checks before anything changes

All of that runs before the setup note is posted, so a bad date never leaves half a plan on the ticket.

The takeaway

In Pia, a scheduled step is a second copy of your run, not a pause. Guard everything after it, or it happens twice: once now and once on time.

All writing