Connects to Microsoft Teams and exports detailed voice/phone configuration for users. It’s smart — if you provide
TeamsPhoneUserList.csv with a userPrincipalName column, it only checks those users. If that file isn’t there, it dumps the entire tenant.
Intent / Why you’d run this:
- Migration tracking — See who’s still on-prem vs Teams-only via
HostingProviderandInterpretedUserType. - License audits — Find users with Phone System licenses
AssignedPlanbut noLineURI= wasted money. - Troubleshooting — One CSV with
LineURI,DialPlan,OnlineVoiceRoutingPolicy,IsSipEnabledfor helpdesk. - Number inventory — Full list of DIDs assigned in
LineURIfor carrier porting or cleanup. - Compliance reporting — Track
WhenChanged,SoftDeletionTimeStampfor leavers and change control.
<#
.SYNOPSIS
Teams Phone System User Audit & Export
.DESCRIPTION
Pulls key Teams Voice configuration for users and exports to CSV.
If an input CSV with 'userPrincipalName' exists, it processes only those users.
If no input file, it exports all CsOnlineUsers in the tenant.
Intent: Audit, migrate, troubleshoot, and report on Teams Phone setup.
Use for license cleanup, migration tracking, number inventory, and helpdesk lookups.
.OUTPUTS
CSV with DisplayName, UserPrincipalName, LineURI, HostingProvider, OnlineVoiceRoutingPolicy,
DialPlan, Alias, AccountEnabled, AssignedPlan, Identity, InterpretedUserType, IsSipEnabled,
OnPremEnterpriseVoiceEnabled, OnPremSIPEnabled, LastSyncTimeStamp, WhenCreated, WhenChanged,
SoftDeletionTimeStamp
#>
# 1. Connect to Teams
Connect-MicrosoftTeams
# 2. Set file paths
$InputFile = "C:\Users\108959\Downloads\TeamsPhoneUserList.csv" # Leave as-is to use targeted list, or rename/delete to run full tenant
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$OutputFile = "$HOME\Downloads\TeamsPhoneAudit_$Timestamp.csv"
# 3. Define attributes to collect
$Properties = @(
'DisplayName','UserPrincipalName','LineURI','HostingProvider','OnlineVoiceRoutingPolicy',
'DialPlan','Alias','AccountEnabled','AssignedPlan','Identity','InterpretedUserType',
'IsSipEnabled','OnPremEnterpriseVoiceEnabled','OnPremSIPEnabled','LastSyncTimeStamp',
'WhenCreated','WhenChanged','SoftDeletionTimeStamp'
)
# 4. Get users - targeted or full tenant
if (Test-Path $InputFile) {
Write-Host "Input file found. Running targeted export for users in: $InputFile" -ForegroundColor Cyan
$Users = Import-Csv -Path $InputFile
$Report = foreach ($User in $Users) {
Write-Host "Processing $($User.userPrincipalName)"
try {
Get-CsOnlineUser -Identity $User.userPrincipalName -ErrorAction Stop | Select-Object $Properties
}
catch {
Write-Warning "Failed to get $($User.userPrincipalName): $_"
}
}
}
else {
Write-Host "No input file found. Running full tenant export. This may take a while..." -ForegroundColor Yellow
$Report = Get-CsOnlineUser -ResultSize Unlimited | Select-Object $Properties
}
# 5. Export + summary
$Report | Export-Csv -Path $OutputFile -NoTypeInformation
Write-Host "`nExport complete: $OutputFile" -ForegroundColor Green
Write-Host "Total records exported: $($Report.Count)" -ForegroundColor Green
This is your “one-click Teams Phone onboarding”. Feed it lists of users → AD gets updated, licenses assigned, numbers enabled, policies applied, and you get an email proof with logs.
<#
.SYNOPSIS
End-to-End Teams Phone Provisioning for M365
.DESCRIPTION
1. Updates AD OfficePhone from CSV
2. Sets UsageLocation + assigns licenses via Microsoft Graph
3. Enables Teams Phone, assigns LineURI, Voicemail, Routing Policy, Dial Plan
4. Exports results + emails report with transcript
Intent: Bulk onboard users to Teams Phone System. Use for new hires, migrations,
or site rollouts. Replaces legacy MSOnline + SkypeOnlineConnector modules.
.PREREQUISITES
Install-Module Microsoft.Graph -Scope CurrentUser
Install-Module MicrosoftTeams -Scope CurrentUser
Install-Module ActiveDirectory -Scope CurrentUser
#>
# ==================== CONFIG ====================
$TranscriptPath = "C:\Projects\SLG\EV-enabled\info-output_$(Get-Date -f yyyyMMdd_HHmm).txt"
$ADPhoneCSV = "C:\Users\0001\Downloads\code.csv" # Columns: name,OfficePhone
$LicenseCSV = "C:\Projects\Taylor\users-licenses.csv" # Columns: UserPrincipalName,usagelocation,SKU
$VoiceCSV = "C:\Projects\SLG\EV-enabled\VoiceEnable.csv" # Columns: UPN,Phone,CountryCode
$OutputCSV = "C:\Projects\SLG\EV-enabled\PhoneEnabledUsers_$(Get-Date -f yyyyMMdd_HHmm).csv"
$RoutingPolicy = "TaylorsVoiceRoutingPolicy"
$DialPlan = "Taylors Dial Plan"
$DefaultCountry = "+60" # Used if CSV doesn't have CountryCode column
$MailParams = @{
SmtpServer = 'smtp.office365.com'
Port = '587'
UseSSL = $true
From = 'voice.admin@taylors.edu.my'
To = 'thet.naing@softwareone.com', 'thetnaing@gmail.com'
Subject = "Teams Phone Provisioning Complete - $(Get-Date -Format g)"
Body = 'Provisioning run finished. See attached CSV and transcript for details.'
}
# ==================== START LOGGING ====================
Start-Transcript -Path $TranscriptPath
try {
# ==================== CONNECT ====================
Write-Host "Connecting to Microsoft Graph + Teams..." -ForegroundColor Cyan
Connect-MgGraph -Scopes "User.ReadWrite.All","Organization.Read.All" -NoWelcome
Connect-MicrosoftTeams
# ==================== 1. UPDATE AD PHONE NUMBERS ====================
if (Test-Path $ADPhoneCSV) {
Write-Host "Updating AD OfficePhone attributes..." -ForegroundColor Yellow
Import-Csv $ADPhoneCSV | ForEach-Object {
try {
Set-ADUser -Identity $_.name -OfficePhone $_.OfficePhone -ErrorAction Stop
Write-Host "AD updated: $($_.name) -> $($_.OfficePhone)"
}
catch { Write-Warning "AD update failed for $($_.name): $_" }
}
}
# ==================== 2. ASSIGN LICENSES + USAGE LOCATION ====================
if (Test-Path $LicenseCSV) {
Write-Host "Setting UsageLocation + Licenses..." -ForegroundColor Yellow
Import-Csv $LicenseCSV | ForEach-Object {
$upn = $_.UserPrincipalName
try {
# Set UsageLocation first - required for licensing
Update-MgUser -UserId $upn -UsageLocation $_.usagelocation -ErrorAction Stop
# Add license - check if already assigned
$user = Get-MgUser -UserId $upn -Property AssignedLicenses
if ($user.AssignedLicenses.SkuId -notcontains (Get-MgSubscribedSku | Where SkuPartNumber -eq $_.SKU).SkuId) {
Set-MgUserLicense -UserId $upn -AddLicenses @{SkuId = (Get-MgSubscribedSku | Where SkuPartNumber -eq $_.SKU).SkuId } -RemoveLicenses @() -ErrorAction Stop
Write-Host "License assigned: $upn -> $($_.SKU)"
}
else { Write-Host "License already present: $upn" }
}
catch { Write-Warning "Licensing failed for $upn : $_" }
}
}
# ==================== 3. ENABLE TEAMS PHONE ====================
if (Test-Path $VoiceCSV) {
Write-Host "Enabling Teams Phone + Policies..." -ForegroundColor Yellow
Import-Csv $VoiceCSV | ForEach-Object {
$user = $_.UPN
$countryCode = if ($_.CountryCode) { $_.CountryCode } else { $DefaultCountry }
$telUri = "tel:$countryCode$($_.Phone)"
try {
# Enable EV + LineURI + Voicemail
Set-CsPhoneNumberAssignment -Identity $user -PhoneNumber "$countryCode$($_.Phone)" -PhoneNumberType DirectRouting -ErrorAction Stop
# Wait for provisioning - poll instead of fixed sleep
$timeout = 120; $timer = 0
do {
Start-Sleep -Seconds 5
$timer += 5
$csUser = Get-CsOnlineUser -Identity $user
} while (-not $csUser.LineURI -and $timer -lt $timeout)
if (-not $csUser.LineURI) { throw "LineURI did not provision within $timeout seconds" }
# Assign policies
Grant-CsOnlineVoiceRoutingPolicy -Identity $user -PolicyName $RoutingPolicy -ErrorAction Stop
Grant-CsTenantDialPlan -Identity $user -PolicyName $DialPlan -ErrorAction Stop
Write-Host "Voice enabled: $user -> $telUri"
}
catch { Write-Warning "Teams Phone setup failed for $user : $_" }
}
}
# ==================== 4. EXPORT RESULTS ====================
Write-Host "Exporting enabled users..." -ForegroundColor Yellow
Get-CsOnlineUser -Filter {EnterpriseVoiceEnabled -eq $true} |
Select-Object DisplayName,UserPrincipalName,LineURI,OnlineVoiceRoutingPolicy,DialPlan |
Export-Csv -Path $OutputCSV -NoTypeInformation
Write-Host "Export complete: $OutputCSV" -ForegroundColor Green
# ==================== 5. EMAIL REPORT ====================
$MailParams.Attachments = $OutputCSV, $TranscriptPath
$MailParams.Credential = Get-Credential -Message "Enter SMTP credentials for $($MailParams.From)"
Send-MailMessage @MailParams
Write-Host "Email sent to $($MailParams.To -join ', ')" -ForegroundColor Green
}
catch {
Write-Error "Script failed: $_"
}
finally {
Stop-Transcript
Disconnect-MgGraph | Out-Null
Disconnect-MicrosoftTeams | Out-Null
}
