All scripts
Microsoft 365 90

Teams Calling Policy Report

Reports which Teams calling policy is assigned to every enabled user, and whether that assignment came from a direct assignment, a group policy assignment, or the global default. Exports to CSV so you can hand it to whoever owns the phone system without giving them admin access. Useful right before a Teams Phone rollout when you need to confirm nobody's on the wrong policy.

Get-TeamsCallingPolicyReport.ps1
<#
.SYNOPSIS
    Reports which Teams calling policy is assigned to each enabled user.

.DESCRIPTION
    Connects to the Microsoft Teams PowerShell module and pulls every
    enabled, licensed user along with the calling policy assigned to them,
    whether that assignment came from the global policy, a direct
    assignment, or a group-based policy assignment. Useful before a
    Teams Phone rollout, or when you need to confirm nobody's stuck on
    a restricted calling policy by accident. Exports the results to CSV
    so you can hand it to whoever owns the phone system without giving
    them admin access.

.AUTHOR
    Shehryar Hassan

.EXAMPLE
    .\Get-TeamsCallingPolicyReport.ps1 -OutputPath "C:\Reports\CallingPolicy.csv"

    Connects to Teams, pulls the policy assignment for every enabled user,
    and writes the report to the given CSV path.
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $false)]
    [string]$OutputPath = ".\TeamsCallingPolicyReport.csv"
)

if (-not (Get-Module -ListAvailable -Name MicrosoftTeams)) {
    Write-Error "The MicrosoftTeams module isn't installed. Run: Install-Module MicrosoftTeams -Scope CurrentUser"
    return
}

Import-Module MicrosoftTeams -ErrorAction Stop

try {
    Get-CsTenant -ErrorAction Stop | Out-Null
}
catch {
    Write-Host "Not connected to Teams. Connecting now..." -ForegroundColor Yellow
    Connect-MicrosoftTeams | Out-Null
}

Write-Host "Pulling enabled users from Entra ID..." -ForegroundColor Cyan
$users = Get-CsOnlineUser -Filter { AccountEnabled -eq $true } -ErrorAction Stop |
    Where-Object { $_.InterpretedUserType -match "Member" }

Write-Host "Found $($users.Count) enabled users. Checking calling policy assignments..." -ForegroundColor Cyan

$report = foreach ($user in $users) {

    $assignment = $null
    try {
        $assignment = Get-CsUserPolicyAssignment -Identity $user.UserPrincipalName -PolicyType TeamsCallingPolicy -ErrorAction Stop
    }
    catch {
        Write-Verbose "Could not read policy assignment for $($user.UserPrincipalName): $_"
    }

    $policySource = "Global (Org Wide Default)"
    $policyName = "Global"

    if ($assignment) {
        if ($assignment.PolicySource -eq "Direct") {
            $policySource = "Direct"
            $policyName = $assignment.PolicyName
        }
        elseif ($assignment.PolicySource -eq "Group") {
            $policySource = "Group ($($assignment.GroupId))"
            $policyName = $assignment.PolicyName
        }
    }

    [PSCustomObject]@{
        DisplayName       = $user.DisplayName
        UserPrincipalName = $user.UserPrincipalName
        EnterpriseVoice   = $user.EnterpriseVoiceEnabled
        CallingPolicy     = $policyName
        AssignmentSource  = $policySource
    }
}

$report | Sort-Object DisplayName | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8

Write-Host "Done. $($report.Count) users written to $OutputPath" -ForegroundColor Green

$summary = $report | Group-Object CallingPolicy | Sort-Object Count -Descending
Write-Host ""
Write-Host "Calling policy breakdown:" -ForegroundColor Cyan
$summary | ForEach-Object {
    Write-Host ("  {0,-30} {1} users" -f $_.Name, $_.Count)
}

Read it before you run it, and test in a safe tenant first.