← Back to articles Teams

Automate Teams Phone Holiday Schedules with PowerShell and the Nager.Date API

Automate Teams Phone Holiday Schedules with PowerShell and the Nager.Date API

If you manage Teams Phone at any scale, you already know the pain: every year you need to recreate holiday schedules for every country your Auto Attendants cover. Do it manually in the Teams Admin Center and you're clicking through the same dates repeatedly. Hardcode the dates in a script and you're maintaining that script forever.

There's a better way. This article walks you through two approaches — a self-contained PowerShell script that calculates French public holidays algorithmically, and a cleaner, fully reusable solution that pulls holiday data from the free Nager.Date public API. The API-based approach supports 90+ countries and requires zero date math on your end. By the end, you'll have a single function that can provision a decade's worth of holiday schedules across multiple countries in under a minute.

🗺️ Why Holiday Schedule Automation Matters

Teams Auto Attendants rely on CsOnlineSchedule objects with a FixedSchedule type to define holiday call routing. These schedules are year-specific — a schedule named FR-Holidays-2026 only covers 2026. If you want call routing to work correctly on 1 January 2027, you need a separate schedule already created and attached to your Auto Attendant before that date arrives.

In a multinational tenant with Auto Attendants covering France, the US, the UK, and Germany, that's 40+ schedule objects per decade. Doing that by hand is not a governance strategy — it's technical debt.

🔧 Prerequisites

You need the MicrosoftTeams PowerShell module and an account with at least the Teams Communications Administrator role (or Global Administrator). The New-CsOnlineSchedule and New-CsOnlineDateTimeRange cmdlets are part of that module.

# Install or update the module
Install-Module MicrosoftTeams -Force -AllowClobber

# Verify the installed version
Get-Module MicrosoftTeams -ListAvailable

# Import and connect
Import-Module MicrosoftTeams
Connect-MicrosoftTeams

Once connected, confirm you can query existing schedules before creating new ones:

Get-CsOnlineSchedule

📐 Approach 1: Algorithmic Script (France, No External Dependency)

If you operate in a locked-down environment where outbound API calls are restricted, or you only need to cover France, this self-contained approach calculates Easter using the Anonymous Gregorian algorithm and derives all moveable feasts from it. All 11 French public holidays are covered.

$year = 2026

function Get-EasterDate {
    param($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
    Get-Date -Year $year -Month $month -Day $day
}

$easter      = Get-EasterDate $year
$lundiPaques = $easter.AddDays(1)   # Easter Monday
$ascension   = $easter.AddDays(39)  # Ascension Thursday
$pentecote   = $easter.AddDays(50)  # Whit Monday

function Range($start) {
    $s = $start.ToString("yyyy-MM-ddT00:00:00")
    $e = $start.AddDays(1).ToString("yyyy-MM-ddT00:00:00")
    New-CsOnlineDateTimeRange -Start $s -End $e
}

$dates = @(
    Range (Get-Date "$year-01-01")  # New Year
    Range $lundiPaques
    Range (Get-Date "$year-05-01")  # Labour Day
    Range (Get-Date "$year-05-08")  # Victory in Europe Day
    Range $ascension
    Range $pentecote
    Range (Get-Date "$year-07-14")  # Bastille Day
    Range (Get-Date "$year-08-15")  # Assumption of Mary
    Range (Get-Date "$year-11-01")  # All Saints Day
    Range (Get-Date "$year-11-11")  # Armistice Day
    Range (Get-Date "$year-12-25")  # Christmas
)

New-CsOnlineSchedule `
    -Name "FR-Holidays-$year" `
    -FixedSchedule `
    -DateTimeRanges $dates

This creates a single schedule for the target year. It works, but you'd need to loop it manually for each year. That's where Approach 2is the clear winner.

🚀 Approach 2: API-Driven Script (Any Country, Multi-Year)

The Nager.Date API (https://date.nager.at) is a well-established, free, open-source public holiday API. It covers 90+ countries, stays maintained, and returns clean JSON. No API key required. This makes it ideal for automation scripts in enterprise environments — though you should validate the data against official government sources for any country where call routing accuracy is business-critical.

Supported Country Codes

Pass any ISO 3166-1 alpha-2 country code. Common ones for Teams Phone deployments:

# FR = France
# US = United States
# GB = United Kingdom
# DE = Germany
# ES = Spain
# IT = Italy
# NL = Netherlands
# BE = Belgium

The Function

function New-TeamsHolidaySchedule {
    param(
        [string]$CountryCode,
        [int]$StartYear = 2026,
        [int]$EndYear   = 2035
    )

    function Range($date) {
        $s = $date.ToString("yyyy-MM-ddT00:00:00")
        $e = $date.AddDays(1).ToString("yyyy-MM-ddT00:00:00")
        New-CsOnlineDateTimeRange -Start $s -End $e
    }

    for ($year = $StartYear; $year -le $EndYear; $year++) {

        Write-Host "Downloading holidays for $CountryCode $year..."

        $url      = "https://date.nager.at/api/v3/PublicHolidays/$year/$CountryCode"
        $holidays = Invoke-RestMethod -Uri $url

        $dates = @()
        foreach ($holiday in $holidays) {
            $date   = Get-Date $holiday.date
            $dates += Range $date
        }

        $scheduleName = "$CountryCode-Holidays-$year"
        Write-Host "Creating Teams schedule: $scheduleName"

        New-CsOnlineSchedule `
            -Name          $scheduleName `
            -FixedSchedule `
            -DateTimeRanges $dates
    }
}

Create Schedules for France

New-TeamsHolidaySchedule -CountryCode FR

This creates 10 schedule objects: FR-Holidays-2026 through FR-Holidays-2035.

Create Schedules for the US

New-TeamsHolidaySchedule -CountryCode US

Multi-Country Bulk Provisioning

For multinational tenants, pipe an array of country codes directly into the function. The following example provisions 40 schedule objects across four countries:

@('FR', 'US', 'GB', 'DE') | ForEach-Object {
    New-TeamsHolidaySchedule -CountryCode $_
}

Expect this to complete in roughly 20–30 seconds depending on your connection latency to the Teams backend.

✅ Verification

After running the script, confirm the schedules were created successfully. You can query them via PowerShell or check the Teams Admin Center under Voice > Holidays.

# List all holiday schedules
Get-CsOnlineSchedule | Where-Object { $_.Name -like "*Holidays*" } | Select-Object Name, Type

Expected output will show each schedule with Type: Fixed and names matching your country/year naming convention.

🏗️ Naming Convention and Auto Attendant Governance

Consistent naming is the difference between a tenant that's manageable at scale and one that becomes a support nightmare six months after the engineer who built it leaves. A recommended naming pattern for enterprise Teams Phone deployments:

# Schedule naming
FR-Holidays-2026
US-Holidays-2026
GB-Holidays-2026
DE-Holidays-2026

# Auto Attendant naming
AA-France-Main
AA-US-Main
AA-Global-HQ

The convention makes it immediately obvious which schedule maps to which Auto Attendant when you're troubleshooting a call routing issue at 9 PM. Attach each country's holiday schedule to the corresponding Auto Attendant's holiday call flow, and document the mapping in your runbook.

One important operational note: creating a CsOnlineSchedule object does not automatically attach it to any Auto Attendant. Attachment is a separate step via Set-CsAutoAttendant or through the Teams Admin Center UI. This script covers the provisioning of schedule objects only.

⚠️ Common Pitfalls

  • Duplicate schedule names: New-CsOnlineSchedule will throw an error if a schedule with the same name already exists. Add a check with Get-CsOnlineSchedule before creating, or wrap the call in a try/catch block if you're running the script idempotently.
  • API availability: Nager.Date is a community project. For production scripts in regulated environments, consider caching the API response locally or mirroring the data in your own storage.
  • Regional/state holidays: The Nager.Date API returns national public holidays. Sub-national holidays (e.g., Alsace-Moselle in France, state holidays in the US or Germany) are not included in the standard response. Check the API documentation for county filtering if you need regional granularity.
  • Time zone awareness: New-CsOnlineDateTimeRange treats the datetime strings as UTC. For most holiday schedule use cases this is fine since you're blocking full calendar days, but be aware of this if you ever switch to time-range-based schedules.

🔑 Key Takeaways

  • Teams Phone holiday schedules are year-scoped — automate their creation now to avoid gaps in call routing coverage.
  • The Nager.Date API eliminates manual date maintenance and supports 90+ countries with no API key required.
  • The New-TeamsHolidaySchedule function in this article provisions a full decade of schedules per country in seconds.
  • For air-gapped or restricted environments, the algorithmic Easter calculation approach covers all French public holidays without any external dependencies.
  • Creating schedule objects and attaching them to Auto Attendants are separate operations — don't forget the attachment step.
  • Consistent naming conventions (CC-Holidays-YYYY) are essential for managing holiday schedules at scale in multinational tenants.

🎓 Ready to go deeper?

Practice real MD-102 exam questions, get AI feedback on your weak areas, and fast-track your Intune certification.

Start Free Practice → Book a Session
Souhaiel Morhag
Souhaiel Morhag
Microsoft Endpoint & Modern Workplace Engineer

Souhaiel Morhag is a Microsoft Intune and endpoint management specialist with hands-on experience deploying and securing enterprise environments across Microsoft 365. He founded MSEndpoint.com to share practical, real-world guides for IT admins navigating Microsoft technologies — and built the MSEndpoint Academy at app.msendpoint.com/academy, a dedicated learning platform for professionals preparing for the MD-102 (Microsoft 365 Endpoint Administrator) certification. Through in-depth articles and AI-powered practice exams, Souhaiel helps IT teams move faster and certify with confidence.

Related Articles

Popular on MSEndpoint