Starting an Application on Weekday Logon Only, Skipping Holidays

AI;DR – This blog post was mostly generated by Claude (via Claude Code) as was the scripts which are described here. If you don’t want to read “AI slop”, stop reading now.

This morning, for the second time I forgot to start Webex when I powered on my PC to work from home (I wfh 100%, living 300 km away from my employer’s office now). Not having Webex running meant that nobody could reach me by phone. And to make this even more embarrassing, I had a call from a customer scheduled for some time today. At 11:00 I started wondering why they hadn’t called and checked my email to find that they actually had and couldn’t reach me. That was when I noticed my mistake.

Being a programmer I wanted to automate this problem away. Yes, I could have simply configured Webex to start with Windows, but I don’t want to be available on that number on the weekend or on public holidays. So I wanted Webex (CiscoCollabHost.exe) to start automatically only every weekday morning when I log in to Windows, but not on weekends or public holidays. Here is what I ended up with.

First attempt: the Startup folder

The simplest approach is a small launcher in shell:startup. It calls a PowerShell script that checks the day of the week and the date against a list of public holidays, and only then starts the executable. To avoid maintaining the holiday list every year, the script computes Easter Sunday with the Gauss algorithm and derives the movable holidays (Karfreitag, Ostermontag, Christi Himmelfahrt, Pfingstmontag, Fronleichnam) from there. The fixed dates (Neujahr, Tag der Arbeit, Tag der Deutschen Einheit, Allerheiligen, both Christmas days) are hardcoded.

Here is the full script (WebexStartup.ps1):

# Launches Webex on NRW workdays only (skips weekends and NRW public holidays).

function Get-EasterSunday {
    param([int]$Year)
    $a = $Year % 19
    $b = [Math]::Floor($Year / 100)
    $c = $Year % 100
    $d = [Math]::Floor($b / 4)
    $e = $b % 4
    $f = [Math]::Floor(($b + 8) / 25)
    $g = [Math]::Floor(($b - $f + 1) / 3)
    $h = (19 * $a + $b - $d - $g + 15) % 30
    $i = [Math]::Floor($c / 4)
    $k = $c % 4
    $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7
    $m = [Math]::Floor(($a + 11 * $h + 22 * $l) / 451)
    $month = [Math]::Floor(($h + $l - 7 * $m + 114) / 31)
    $day = (($h + $l - 7 * $m + 114) % 31) + 1
    return (Get-Date -Year $Year -Month $month -Day $day).Date
}

function Get-NrwHolidays {
    param([int]$Year)
    $easter = Get-EasterSunday -Year $Year
    return @(
        (Get-Date -Year $Year -Month 1  -Day 1 ).Date,  # Neujahr
        $easter.AddDays(-2),                            # Karfreitag
        $easter.AddDays(1),                             # Ostermontag
        (Get-Date -Year $Year -Month 5  -Day 1 ).Date,  # Tag der Arbeit
        $easter.AddDays(39),                            # Christi Himmelfahrt
        $easter.AddDays(50),                            # Pfingstmontag
        $easter.AddDays(60),                            # Fronleichnam (NRW)
        (Get-Date -Year $Year -Month 10 -Day 3 ).Date,  # Tag der Deutschen Einheit
        (Get-Date -Year $Year -Month 11 -Day 1 ).Date,  # Allerheiligen (NRW)
        (Get-Date -Year $Year -Month 12 -Day 25).Date,  # 1. Weihnachtstag
        (Get-Date -Year $Year -Month 12 -Day 26).Date   # 2. Weihnachtstag
    )
}

$today = (Get-Date).Date

if ($today.DayOfWeek -eq 'Saturday' -or $today.DayOfWeek -eq 'Sunday') {
    exit 0
}

if ((Get-NrwHolidays -Year $today.Year) -contains $today) {
    exit 0
}

Start-Process -FilePath "$env:LOCALAPPDATA\Programs\Cisco Spark\CiscoCollabHost.exe"

A tiny WebexStartup.cmd shim next to the .ps1 launches it with -WindowStyle Hidden so there is no console flash at logon:

@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0WebexStartup.ps1"

Both files go into shell:startup (which expands to %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup). The .cmd uses %~dp0 to find the .ps1 next to it, so they must live in the same folder.

The hibernate gotcha

It turns out that items in the Startup folder only run on a real logon. Resuming from hibernate (or sleep) restores the existing session, so the script never fires. The lock screen you see after resuming is just re-authentication, not a fresh logon. I usually hibernate the machine overnight instead of shutting it down, so the Startup folder approach does not work for me most of the time.

Better: one Task Scheduler task, two triggers

The cleaner solution is to drop the Startup folder entry entirely and create a single Task Scheduler task with two triggers:

  • At log on covers cold boots, restarts, and sign-in events.
  • On workstation unlock covers resumes from hibernate and sleep (Windows treats the re-authentication after resume as an unlock).

To set it up:

  1. Run taskschd.msc.
  2. Right-click Task Scheduler Library, choose Create Task (not “Create Basic Task”, which does not expose the unlock trigger).
  3. On the Triggers tab, add both triggers.
  4. On the Actions tab, point to WebexStartup.cmd.
  5. On the Conditions tab, uncheck “Start the task only if the computer is on AC power” if you are on a laptop.

The same PowerShell script handles the day-of-week and holiday gating, so weekends and holidays still get skipped. Webex itself is single-instance, so the occasional double-firing (unlock right after logon) is a no-op.

One task, one place to look, one place to change.