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-CsOnlineSchedulewill throw an error if a schedule with the same name already exists. Add a check withGet-CsOnlineSchedulebefore creating, or wrap the call in atry/catchblock 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
countyfiltering if you need regional granularity. - Time zone awareness:
New-CsOnlineDateTimeRangetreats 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-TeamsHolidaySchedulefunction 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.