I’ve been assigning phone numbers in Teams for pretty much five years now. I remember when it all started for me with Direct Routing in 2018.
Numbers were assigned by using something like this:
Set-CsUser -Identity "user@domain.com" -OnPremLineUri "tel:+12345678900" -EnterpriseVoiceEnabled $true
. Later on, OnPremLineUri
was replaced by the LineUri
attribute before Microsoft finally introduced the new Set-CsPhoneNumberAssignment
cmdlet.
I don’t know how many phone numbers I’ve assigned in Teams over the years but one thing I know for sure is that I’ve had my fair share of assignment failures. Granted, it’s gotten way better and more reliable in recent years, but sadly, I still run into number assignment issues occasionally.
By assignment issues I mean that after a number has been assigned to users, they either don’t receive inbound calls, can’t make outbound calls or both. The nasty thing about this is that in Teams Admin Center or PowerShell everything looks ok. I can see that the users in question indeed have a phone number assigned.
In other words, you can’t always trust that a number is successfully assigned when you can see it in TAC or in PowerShell.
But when you check a user’s calls app in Teams or ask them to send you a screenshot of it, you notice that they don’t have a work phone number listed beneath the dial pad.
Now obviously there are legit scenarios where users don’t have their own LineUri but in most cases, each user gets assigned a phone number.
This is how it’s supposed to look after a number has been assigned.
This is very much undocumented, but I believe that the Teams client and TAC/PowerShell are reading/pulling the number from different systems. It has to be this way since they both display different information.
If you’re setting up Teams phone for a bunch of users at once, it’s not like we can just go and ask each user if they can see the number in their Teams client. That would make IT look bad. On the other hand, trying to assign the number a few more times until it hopefully sticks doesn’t seem like an ideal solution either.
Reverse Number Lookup (RNL) to validate the assignment
As you might know, I run a Discord Server for Teams Phone Admins. When the issue reoccurred for me earlier this year, I decided to ask the Discord community if they have a clever way of remotely checking if a phone number assignment has been successful?
Our member Ashish Mishra suggested to type in the number in my own Teams client to see if anything comes up on the RNL (reverse number lookup). As it turns out, this is a genius idea.
Last week, I stumbled over this issue again when a few users who have recently gotten a new Teams phone number reported to me that outbound calls were not working. Luckily, there were only a handful, and I was able to ask them directly if they could see the phone number in Teams. They couldn’t.
I then went on to my Teams client and entered some of the numbers to see if they return anything or not.
If the number is not successfully (or not at all) assigned, only the number but no name will be returned.
As soon as the number is successfully assigned, the name of the user will be returned.
Because I’ve encountered the issue before, I didn’t even bother to check the M365 service health dashboard. But apparently, there was indeed an issue related to numbers assigned during a time frame which matched my errors.
Title: Users can’t make or receive Public Switched Telephone Network (PSTN) calls in Microsoft Teams
User impact: Users couldn’t make or receive any PSTN calls from any connection method of Microsoft Teams.
More info: This only impacted users who had their phone numbers assigned between Monday, August 14, 2023 at 10:30 PM GMT+2 , and Wednesday, August 16, 2023 at 4:00 AM GMT+2.
In that particular case, I was able to fix the issue by reassigning the numbers. But that didn’t stop me from looking for a more practical, permanent solution.
Reverse engineering
Once again, we go back to Microsoft Edge’s dev tools to see what’s going on when RNL is done. All it does is to send a request to the following endpoint:
https://teams.microsoft.com/api/mt/emea/beta/phone/numbers/+41438833079/teamsidentity
If there is a user found for the phone number, a user object Id will be returned.
If no users are found, the API returns an error.
It’s noteworthy that only numbers assigned in a user’s own tenant can be checked. You can’t use this API to scrape numbers of other tenants.
If we want to send these kinds of requests using PowerShell, we obviously need to get a valid bearer token first. For this, we can reuse much of the code I’ve already written for this blog post. We’re using the same function to either create or read our encrypted credentials which we then use to get a token using the AADInternals PowerShell module/functions.
Please note that you don’t need the credentials of a Teams admin user. Since any Teams enterprise voice enabled user can dial a number and trigger RNL, non-admin credentials of any Teams phone enabled user work just fine.
What’s interesting is that doing RNL through the API also worked using the bearer token of a user which isn’t even enterprise voice enabled.
Please bear in mind that this means that a user’s password and possibly an OTP secret (albeit both encrypted) are saved to your local disk. Treat the files with care and remove them once you don’t need them anymore!
The Script/Functions
Finally, here’s the PowerShell code to make it all happen.
Please make sure that you clone the repository from my GitHub because of the dependencies. Simply copying the script from the gist won’t work.
|
|
I made this in a way that you can use it in your own scripts. I purposely made this a lightweight function without any requests to Teams or Graph.
I did, however, create an example script to show you how you can use the function to validate phone number assignments in bulk.
If you want to read users/numbers from a CSV or directly from Teams/Graph, you’ll need to write your own code to build the $lineURIs
array.
Use -AdminUser
to specify the user you want to get a Teams token for via the AADInternals module. Use either -MFA $true
or -MFA $false
to tell the function if the account specified is MFA enabled or not.
The example script will create an object in $results
which includes all the checked phone numbers and if they’re assigned successfully. If a number is assigned correctly, the object id of the user will be included as well if you keep $hideObjectId $false
.
The actual function doing the RNL can be found here.
I hope that this bit of code helps you the next time you need to check if a Teams phone number is assigned 100% correctly.