Teams Meeting Policy: Auto-Recording, Transcription, Downloads and Never-Expiring Recordings
What You Will Configure
| Requirement | Admin control | Where |
|---|
| Allow meeting recording | -AllowCloudRecording $true | Teams admin center or PowerShell |
| Allow transcription | -AllowTranscription $true | Teams admin center or PowerShell |
| Record and transcribe automatically | -AutoRecording Enabled | PowerShell only |
| Recordings never expire | -NewMeetingRecordingExpirationDays -1 | PowerShell only (or turn off "Recordings automatically expire" in the Teams admin center) |
| Participants can download recordings and transcripts | No per-user admin policy | Meeting options (organizer) + tenant download settings |
Key Facts
| Topic | Behaviour |
|---|
| AutoRecording policy | Only shows the Record and transcribe automatically toggle in Meeting options |
| Default toggle state | Off for meetings, On for webinars and town halls |
| Force auto-record on every meeting | Needs a Teams Premium meeting template or a sensitivity label |
| Custom meeting templates | Created in the Teams admin center only; no PowerShell cmdlet |
| Default recording expiry | 120 days |
| Expiry range | 1–99,999 days, or -1 (never) in PowerShell |
| Expiry change scope | Applies to new recordings only; existing recordings keep their expiry date |
| Recording storage | Organizer's OneDrive (non-channel meetings), channel SharePoint site (channel meetings) |
| Default download rights | Organizers and co-organizers can download; other invitees can only view |
| "Who has access to the recording and transcript" | Per-meeting option; needs Teams Premium or Copilot license |
| Tenant-wide download block | Set-SPOTenant -BlockDownloadFileTypePolicy with TeamsMeetingRecording overrides everything |
| Policy propagation | Can take several hours after assignment |
| Legal retention | Use Microsoft Purview retention policies, not the expiry setting |
Prerequisites
| Item | Detail |
|---|
| Roles | Teams Administrator; SharePoint Administrator (download check); Graph User.Read.All (license check) |
| Modules | MicrosoftTeams, Microsoft.Online.SharePoint.PowerShell, Microsoft.Graph.Users |
| Best practice | Never edit the Global policy; duplicate it and assign the copy to the target users |
Implementation Steps
- Connect to Microsoft Teams PowerShell.
- Duplicate the Global meeting policy into a new custom policy.
- Enable recording, transcription and auto-recording, and set expiry to never.
- Assign the custom policy to the user.
- Verify the policy values and the assignment.
- Check the tenant does not block downloads of meeting recordings.
- Check for a Teams Premium license if auto-record must be enforced.
- Test with a short meeting after the policy has propagated.
Complete Script
<#
.SYNOPSIS
Copies the Global Teams meeting policy into a custom policy, enables auto-recording,
transcription and never-expiring recordings, assigns it to a user and runs checks.
.NOTES
Replace the placeholder values before running.
#>
# ---------------- Variables ----------------
$sourcePolicy = "Global"
$newPolicy = "MeetingPolicy-AutoRecord-NoExpiry"
$userUpn = "user@contoso.com"
$spoAdminUrl = "https://contoso-admin.sharepoint.com"
# ---------------- 1. Connect ----------------
Connect-MicrosoftTeams
# ---------------- 2. Copy Global into a new policy ----------------
$source = Get-CsTeamsMeetingPolicy -Identity $sourcePolicy -ErrorAction Stop
if (Get-CsTeamsMeetingPolicy -Identity $newPolicy -ErrorAction SilentlyContinue) {
Write-Host "Policy '$newPolicy' already exists - syncing settings from '$sourcePolicy'." -ForegroundColor Yellow
} else {
New-CsTeamsMeetingPolicy -Identity $newPolicy -Description "Copy of Global + auto-record + no recording expiry" -ErrorAction Stop | Out-Null
Write-Host "Created policy '$newPolicy'." -ForegroundColor Green
}
$target = Get-CsTeamsMeetingPolicy -Identity $newPolicy
$skip = @('Identity', 'Description', 'Tenant', 'Force', 'InMemory')
$common = [System.Management.Automation.PSCmdlet]::CommonParameters + [System.Management.Automation.PSCmdlet]::OptionalCommonParameters
$params = (Get-Command Set-CsTeamsMeetingPolicy).Parameters.Keys | Where-Object { $_ -notin $skip -and $_ -notin $common }
$copied = 0
$failed = @()
foreach ($p in $params) {
if ($source.PSObject.Properties.Name -notcontains $p) { continue }
if ("$($source.$p)" -eq "$($target.$p)") { continue }
try {
$splat = @{ Identity = $newPolicy; $p = $source.$p; ErrorAction = 'Stop' }
Set-CsTeamsMeetingPolicy @splat
$copied++
} catch {
$failed += [pscustomobject]@{ Setting = $p; Value = "$($source.$p)"; Error = $_.Exception.Message }
}
}
Write-Host "Copied $copied setting(s) from '$sourcePolicy'." -ForegroundColor Green
if ($failed) {
Write-Host "Settings not copied (usually retired parameters):" -ForegroundColor Yellow
$failed | Format-Table -AutoSize
} else {
Write-Host "No copy failures." -ForegroundColor Green
}
# ---------------- 3. Apply required settings ----------------
Set-CsTeamsMeetingPolicy -Identity $newPolicy `
-AllowCloudRecording $true `
-AllowTranscription $true `
-AutoRecording Enabled `
-NewMeetingRecordingExpirationDays -1
# ---------------- 4. Assign to the user ----------------
Grant-CsTeamsMeetingPolicy -Identity $userUpn -PolicyName $newPolicy
# ---------------- 5. Verify ----------------
Get-CsTeamsMeetingPolicy -Identity $newPolicy |
Select-Object Identity, AllowCloudRecording, AllowTranscription, AutoRecording, NewMeetingRecordingExpirationDays
Get-CsUserPolicyAssignment -Identity $userUpn -PolicyType TeamsMeetingPolicy
# ---------------- 6. Check tenant-wide recording download block ----------------
Connect-SPOService -Url $spoAdminUrl
$spo = Get-SPOTenant | Select-Object BlockDownloadFileTypePolicy, BlockDownloadFileTypeIds
if ($spo.BlockDownloadFileTypePolicy -and ($spo.BlockDownloadFileTypeIds -contains "TeamsMeetingRecording")) {
Write-Host "Recording downloads are BLOCKED tenant-wide - participants cannot download." -ForegroundColor Red
} else {
Write-Host "No tenant-wide block on meeting recording downloads." -ForegroundColor Green
}
# ---------------- 7. Check Teams Premium (needed to enforce auto-record) ----------------
Connect-MgGraph -Scopes "User.Read.All" -NoWelcome
$premium = Get-MgUserLicenseDetail -UserId $userUpn |
ForEach-Object { $_.ServicePlans } |
Where-Object { $_.ServicePlanName -like "TEAMSPRO*" -and $_.ProvisioningStatus -eq "Success" }
if ($premium) {
Write-Host "Teams Premium found - a locked meeting template can enforce auto-record." -ForegroundColor Green
} else {
Write-Host "No Teams Premium - organizer must switch the toggle on per meeting." -ForegroundColor Yellow
}
# ---------------- Rollback (run only if needed) ----------------
# Grant-CsTeamsMeetingPolicy -Identity $userUpn -PolicyName $null # revert user to Global
# Remove-CsTeamsMeetingPolicy -Identity $newPolicy # delete custom policy
Organizer Actions After Policy Applies
| Goal | Action |
|---|
| Auto-record a meeting | Meeting options → Record and transcribe automatically → On (per meeting or per recurring series) |
| Let attendees download | Add them as co-organizers, or use Who has access to the recording and transcript (Premium/Copilot) |
| Share with others | Share the .mp4 / transcript from OneDrive with the required permission |
| Change expiry on an old recording | Open the file details in OneDrive and edit the expiration date |
Validation Checklist
Troubleshooting
| Symptom | Cause | Fix |
|---|
| Toggle not visible | Policy not propagated or not assigned | Wait a few hours; sign out and in; check Get-CsUserPolicyAssignment |
| Toggle visible but meeting not recorded | Toggle is Off by default | Organizer switches it On per meeting |
| Auto-record cannot be enforced | No Teams Premium | Use a sensitivity label or license Teams Premium |
| Recording still shows an expiry date | Recording created before the change | Edit expiry on the file manually |
| Participants cannot download | Default view-only rights or tenant download block | Make co-organizers / share file / review BlockDownloadFileTypePolicy |
| A setting failed to copy | Retired or read-only parameter | Review and ignore if not relevant |
References