Teams Phone Number Management List Part 3

I think it’s safe to say by now that this project has been a success. I’ve received lots of positive feedback about this from the…

I think it’s safe to say by now that this project has been a success. I’ve received lots of positive feedback about this from the community.

That didn’t stop me from going even further and adding more features to this project though.

New Features and Improvements

Localized / Formatted Phone Numbers

As you may know by now, I consider myself a data driven guy. What I really dig about Teams phone is the fact that every number is a full E.164 number in every case. This kind of absoluteness provides great safety because each number is unique. Just as with user principal names or GUIDs, there’s no funny business going on.

That safety comes at the cost of readability though. If you’ve seen this article before, then you already know that I came up with a solution to format phone numbers in their local format using Python.

The runbook logic powering the list can now also leverage Python to store the formatted phone number in an additional column. This way you always have both, the E.164 number and a prettified version of it readily available.

For US numbers or numbers from other countries such as Japan or Israel which typically use hyphens, you might have noticed that I’m removing the hyphens, even though they’re included after a number has been formatted using the phonenumbers library. I do this so that every number in the list contains only digits and a plus sign. Also, it’s how Teams Admin Center displays the numbers.

Write back Teams Phone Number to Entra ID

When you view a user’s profile card in Teams, Outlook or anywhere else in Microsoft 365, it doesn’t display the phone number which is actually configured as LineURI in Teams. Instead, the information is taken from Entra ID. I’ve created a variable inside the runbook which controls if the BusinessPhones property of Entra ID users should be set to the assigned LineUri in Teams. I’ll explain how to turn this on or off later in this article.

With the current permissions/scopes assigned to the Entra ID App registration, this only works for users which don’t have any administrator roles assigned in Entra ID. If a user has an admin role, you’ll get an error.

Previous User Column

I’ve added a new column which shows the UPN of the previous user/owner of a number after it has been removed. This is achieved by an update I made to the Power Automate Flow. Note that the flow will now also retain the value of the Comment column.

The reason for this column is that you have a better overview over who has previously used a number. I’m sure you don’t want to assign the previous CEO’s number to a new intern.

Unassigned Routing Rules

When people leave, we sometimes want to forward their unassigned number to another person in the company or to the main number. This is done by routing rules. But do we typically remember or even know that such rules have been created in the past…? Now we do!

Every number is checked against all routing rules a tenant has configured and if there are any matches, they’re reflected on the list. The name of the matching rule gets stored in the User Name column. (Because this column isn’t used for the flow trigger condition.)

This even works if a number is reserved. In this case, the status will be changed to Reserved (Routing Rule).

Improved Number Assignment Capabilities

I’ve also made some improvements to assigning numbers. If a number has a status of Reserved (Routing Rule) or Assignment Error, the script will try to assign the number as well. In the case of an assignment error, the script will try to assign the number again with each new job. This is helpful in scenarios where e.g. a license hasn’t been assigned yet.

Furthermore, you can now also reserve a number and enter the user’s UPN in the User Principal Name column. This is great if the account does not exist yet (so you can’t fill in the User Profile) but you already know the UPN of a soon to be created user. All of these changes make it easier for you to plan ahead and have the runbook take care of everything automatically, once a user has been created and licensed.

Operator Column

Last but not least, the list now also displays the name of the Operator. This works for all types, including Direct Routing. You will have to manually list the operator name for each Direct Routing number in your source CSV though.

Updating All the Components

I will most likely update the deployment script at some point to deploy everything from scratch including all the updates. If I can find enough time, I may also create an update script to update an existing deployment.

But today, I’ll show you how to implement all the changes manually in an existing deployment which consists of an Azure automation account, a list, a flow and your local source files.

To do these steps, you should have followed and deployed everything as described in this article and implemented all the updates described in this article.

Add New Columns

Add the following columns to the SharePoint list. All columns must be of type text.

  • Comment
  • Previous User
  • Operator
  • Phone Number

Add the phonenumbers Python Package to the Automation Account

Go to this website and download the built distribution (*.whl) file to your PC. Then go to your automation account in Azure Portal and upload the file under Python packages.

Add Automation Variables to Automation Account

Add the following variables to your automation account. Since you’ll need to enter a value, just type a dot (“.”). Leave the integer value at 0.

  • TeamsPhoneNumberOverview_AllCsOnlineNumbers (String)
  • TeamsPhoneNumberOverview_PrettyNumbers (String)
  • TeamsPhoneNumberOverview_TotalNumberCount (Integer)

Add Operator info to your Direct Routing Numbers

If you have direct routing numbers, you’ll need to update your source list. The old structure only had one column called PhoneNumber. Now there’s PhoneNumber and Operator.

4144520xxxx;Test Provider 1
4144512xxxx;Test Provider 2
4144512xxxx;Test Provider 3

Place your updated CSV in .\Resources\DirectRoutingNumbers-V2.csv and then run this script to update your automation variable in Azure.

Alternatively, you can also run the below code and paste the content into your TeamsPhoneNumberOverview_DirectRoutingNumbers automation variable.

“’” + (Import-Csv -Path .\Resources\DirectRoutingNumbers-V2.csv -Delimiter “;” | ConvertTo-Json | Out-String) + “’” | Set-Clipboard

Create a Python Runbook

Go to your runbooks in your automation account and click Create a runbook. Name it Format-TeamsPhoneNumbers and choose Python as runbook type and 3.10 (preview) as runtime version.

Grab this code and paste it into the Azure portal. Save and publish the runbook.

Setup a Managed Identity and Permissions

Every time the main runbook runs, it will check if the total number count is still the same by comparing the current count to the count of the previous job which is stored in the TeamsPhoneNumberOverview_TotalNumberCount variable. If the count of numbers is different compared to the previous job, the main runbook will start the python runbook. The python runbook will use the phonenumbers library to format all your phone numbers to their localized format.

However, we must first allow the runbook to start another runbook job using a managed identity. In the automation account, go to Identity and flip the toggle switch for System assigned (managed identity) to On. Then click Save.

Click Azure role assignments and then Add role assignment (Preview). Set the scope to Resource group, select your subscription and resource group and choose Automation Operator as role.

Click Save.

Update the Main Runbook

Replace the code in the TeamsPhoneOverview runbook with this code. Make sure to change the **$localTestMode** variable to **$false** before you save and publish the runbook. Also update the names of your automation account and resource group on lines 95–97.

If you don’t want to write back user’s LineURIs to Entra ID BusinessPhones, change syncTeamsPhoneNumbersToEntraIdBusinessPhones to $false on line 6.

Update the Flow

Finally, go to your flow and make the last small changes. In here, add the values for the new columns that we created. If you need a refresher, this is the part where the flow deletes numbers which were unassigned because there’s no way to remove the user profile. We then recreate the list entry with all the information except the User Profile Claims. Because the user profile stays, we can use the User Profile Email and write it to the Previous User column. Comments in the comment field will also be added again, once a deleted entry has been recreated.

Update the Flow Trigger Condition

The current trigger condition checks if a user’s email address is equal to the user principal name. As much as I want this to always be the case, the reality is sometimes different. Some readers have reported that their Flow gets stuck in an infinite trigger loop because of that.

I’ve slightly tweaked the trigger condition, so it doesn’t compare the email address to the UPN anymore. Instead, the UPN is extracted from the user profile using a split operation and then compared to the UPN from the list column. Here’s the updated trigger condition. Make sure to update it on your flow as well as it will be more robust than the old one.

not(equals(triggerOutputs()?[‘body/User_x0020_Principal_x0020_Name’], ‘Unassigned’)),
not(contains(triggerBody(), ‘UserProfile’))
not(equals(toLower(triggerOutputs()?[‘body/User_x0020_Principal_x0020_Name’]), split(toLower(triggerOutputs()?[‘body/UserProfile’][‘Claims’]),’|’)[2])),
contains(triggerBody(), ‘UserProfile’),
not(equals(triggerOutputs()?[‘body/User_x0020_Principal_x0020_Name’], ‘Unassigned’)),
not(contains(triggerBody(), ‘TeamsAdminCenter’))

That’s all. If you’ve followed all the steps, you should now be able to make use of all the new features I created. Like I said, I’ll try to find some time and create a new deployment script which deploys the version 2 from scratch. I hope you enjoy the list as much as I do.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Hosted on GitHub Pages
Built with Hugo
Theme Stack designed by Jimmy