Monday, February 24, 2020

The Complete Guide to Using for Microsoft Teams Direct Routing

Seasoned veterans of my dusty, long-dormant blog will remember I did a comprehensive post (in 2012! EIGHT YEARS AGO!!!!) on how to use the Lync Optimizer to automatically create dialplans for your Lync deployments. That guide is still useful for every on-prem version of Lync and Skype for Business since then.  I did a similar guide for Skype for Business Online in 2017 (a mere 3 years ago). With all the changes that Teams has brought (Direct Routing, and so much more!), I figure its about time I did an updated guide on how to use in Direct Routing scenarios.
Who could forget the 2012 Hoff masterpiece Piranha 3DD? I didn't know this existed until just now. I know what I'm watching tonight!

A Milestone

Another reason why I'm doing this now (February 2020) is because for the first time, the monthly total number of S4BOnline/Teams rulesets generated by has surpassed the number of S4B On-prem rulesets. This is an unofficial barometer of which platform is more popular for Enterprise Voice deployments. I could go on about how many S4B On-prem deployments are at a mature state and generally don't require many net-new dialplans compared to Teams where many are just starting to move their voice loads to it, but SEMANTICS! Just look at the chart!

Who should use this guide?

If you're on MS Teams and you are using Teams as your phone system, you'll be using it in one of two modes (or both in some cases):

If you're using Teams Direct Routing (using your own SBCs to provide dialtone), then you should definitely use this guide. It makes the entire process of creating voice routing policies, routes, PSTN usages etc. so much easier. If you are already confused by the above terms, then I HIGHLY recommend you use this tool.

If you're using Microsoft Phone System (where Microsoft is your telephony provider), then I suggest you read my blog post on Tenant Dialplans in Skype for Business Online. It will tell you why is still useful when your entire telephony infrastructure is managed by Microsoft. Even though it was written for SfBO, it still applies to Teams.

What can do for me?

In a Direct Routing environment, will automatically create the following:
  • Dialplans with normalization rules appropriate for your country, so users can dial phone numbers as they are accustomed.
  • Voice routing policies so you can selectively control who can dial locally, nationally and internationally.
  • Voice routes, which determine where calls are routed
  • PSTN usages, which are the glue between voice routing policies and voice routes
In a Microsoft Phone System environment, will automatically create the following:
  • Dialplans with normalization rules appropriate for your country, so users can dial phone numbers as they are accustomed.

What won't do for me?

If you're using Direct Routing, the "only" thing can't do is setup your SBC infrastructure and configure the link between Teams and your SBC. That has to be done prior to applying a script to your environment. Its not a small task, and could take you a while. Don't worry, I'll be here when you're ready. 

To get familiar with what's required, read the excellent documentation on Microsoft's site starting with the Overview, then Plan Direct Routing, followed by Configure Direct Routing, but stop when you get to the section listed Configure Voice Routing. This is the point where takes over.

Getting Started

For those using Direct Routing, I'm assuming you've already setup your SBC infrastructure and have paired your SBCs to your Office 365 tenant. PowerShell scripts will check for the existence of at least one SBC via Get-CsOnlinePSTNGateway. If one isn't found, then the script will stop after creating a dialplan.

Generate your customized dialplan script

Go to and sign in with a Microsoft account. Once validated, you will see the webform for creating your dialplan become visible.

  1. Select Microsoft Teams as your gateway type
  2. Select the country and city/region where your SBCs are located.
  3. Some countries require special prefixes/suffixes for certain phone calls. Enter any prefixes in the provided boxes.
  4. If your SBCs are connected to the PSTN via SIP trunk, select SIP Trunk. If you are connected via a legacy T1/E1/analog connection or you are required to send numbers formatted to the local number standards, select T1/E1/ISDN/Analog. NOTE: There is a bug in the Microsoft PS commands used to create outbound translation rules that limits rule length to 50 characters (every other PS command allows 1024 characters). This may have an adverse effect. 
  5. If you are connecting to a legacy PBX that requires adding a prefix to external numbers, enter one here. 
  6. Add up to 50 internal extension ranges. If it works for your deployment, add every extension range used across your deployment here. You will see why at the Assign internal dialing rules to the local or Global dialplan? question during script execution (see a few sections lower down for details).
  7. If you don't like the auto-generated rulename example shown, you can change it here. Note that you can only control what's in the middle of the rulename. The country name and ruletype are fixed.
  8. If you don't want to control who can make local/national/international calls, you can select Simple Ruleset. 
  9. If the ruleset is for a country where English isn't the first language, you can force English rulenames. It will also remove any non-English standard characters from city/region names and replace them with the English equivalent. 
  10. Once you're satisfied with the configuration, press GENERATE RULES
UCDialplans will start whipping its army of trained monkeys and in no time flat, you will have a customized PowerShell script ready for download.
Notice the Donate button? Not many people see this. :)

Click the DOWNLOAD RULESET HERE button, and save the script as a .PS1 somewhere on your system. You will probably have to unblock it to make it usable without prompting. Right-click the file and select Properties. Click Unblock, and then OK.

Backup your existing Teams EV config

If you've already got a functional Teams EV environment, I strongly suggest you backup your EV details, by downloading and running my suite of Teams Backup/Restore scripts. For more details, see this post, or go to Github to download the latest version directly.

Run the script via PowerShell

Open a PowerShell window, navigate to where the script is located and run the script. You might be blocked from running the script due to PowerShell policies. If this is the case, then run the following command from an elevated PowerShell prompt:
Set-ExecutionPolicy RemoteSigned
Or you can open your kimono and use Unrestricted in place of RemoteSigned.

Run the script from a PowerShell prompt. If you are not already signed into your Office365 tenant, you will be prompted for credentials. If your admin account is not in the same domain as your tenant (ie. uses an address), then use the -OverrideAdminDomain switch.

You can also use the -PSTNGateway switch to enter the FQDNs of the SBCs you want to use. If using more than one, put them in quotes and separate by commas, like this .\US-Chicago-MSTeams.ps1 -PSTNGateway ",". If you're using Microsoft "Super Trunks", you'll need to use the -PSTNGateway switch because the script won't detect any PSTN gateways installed in your tenant.

When the script starts, you'll be presented with several questions:

Create global or user-level dialplan?

You can either assign the dialplan (which controls how users can dial phone numbers) to everyone by default via the Global dialplan or a user-level dialplan (the default selection). If the majority of your users are in the same location, you can use the Global dialplan, which requires one less step when enabling new users for voice. If you will be creating multiple dialplans, then its probably best to select User. The script will create normalization rules as desired.

Assign internal dialing rules to the local or Global dialplan?

If you entered extension ranges via the UCDialplans UI, you will be prompted with this question. If you say Global, the extension ranges you entered will be added to the Global tenant dialplan. This means that any new dialplans created in the future will add those normalization rules to the top. This takes advantage of an undocumented feature described in this post. If you don't want to take advantage of this, select Local.

Assign PSTN usages to Global Voice Policy?

A voice policy defines exactly what numbers a user can dial. You can use voice policies to allow only certain users to dial internationally, or set common-area phones to only dial local numbers (as examples). If you have a simple deployment where the majority of users will be assigned the same voice policy, you can set this globally by selecting either Local, National, or International. Local means that users will only be able to dial local numbers. National means they can dial any number within the country, and International has no restrictions.  If you do not assign anything at the global level, you will have to manually assign voice policies to all users.

Select primary/secondary PSTN gateway to apply routes

If there is a single PSTN gateway defined, the script will automatically use this for all defined routes. If there is more than one, the script will prompt you for which will be the primary gateway and which will be the secondary (if desired). This question will not be asked if you used the -PSTNGateway switch.

Configure voice policies for least-cost/failover routing

If the script detects the presence of existing voice policies created with UCDialplans, it will ask if you want to leverage least-cost/failover routing for these voice policies. If you say Yes, the script will assign the existing voice policies in a way that will route calls using the cheapest path possible, as well as ensure that calls can still work if the main PSTN gateways are down. See this post for more info (was written for Lync, but still applies to MS Teams)

Final Steps

Once done, you should have a fully functional voice infrastructure in MS Teams. If you didn't use the Global options for the dialplan and/or voice policy, you'll have to assign policies to your users. A simple example is as follows (assumes you're assigning to all EV-enabled users):
Get-CsOnlineUser | Where {$_.TenantDialplan -eq $NULL  -and $_.EnterpriseVoiceEnabled -eq $TRUE} | Grant-CsTenantDialPlan -PolicyName BR-SaoPaulo
Get-CsOnlineUser | Where {$_.VoiceRoutingPolicy -eq $NULL  -and $_.EnterpriseVoiceEnabled -eq $TRUE} | Grant-CsOnlineVoiceRoutingPolicy -PolicyName BR-SaoPaulo-National
It will take some time for these to take effect, and will probably require users to restart Teams at some point.

Oooops! I screwed up!

If you accidentally screw up applying a script and want to revert to a "clean" setup, you can run the following commands, which will wipe out all custom dialplans, voice policies etc. Don't run this if you've already got a working system.
Get-CsTenantDialPlan | Remove-CsTenantDialPlan
Get-CsOnlineVoiceRoute | Remove-CsOnlineVoiceRoute
Get-CsOnlineVoiceRoutingPolicy | Remove-CsOnlineVoiceRoutingPolicy
Set-CsOnlinePstnUsage Global -Usage $NULL
$GWList = Get-CsOnlinePSTNGateway
ForEach ($GW in $GWList) { Set-CsOnlinePSTNGateway -Identity $GW.FQDN -OutboundTeamsNumberTranslationRules $NULL -OutboundPSTNNumberTranslationRules $NULL -InboundTeamsNumberTranslationRules $NULL -InboundPSTNNumberTranslationRules $NULL }
Get-CsTeamsTranslationRule | Remove-CsTeamsTranslationRule 

Additional Tips and Tricks scripts can be used for some interesting use-cases. For example, assume you're a company that has a single SIP trunk infrastructure that is homed in a single country. For this example, we'll assume its in Canada. Now also assume that you have some users in other countries such as the UK, that have Canadian numbers assigned to them. Those UK users would normally be forced to dial phone numbers as if they were in Canada, which is not something they would be used to.

In this case, you can generate a UK-based script from and run it against your deployment using the -DPOnly switch. This will force the script to only create a dialplan for the UK and it won't create extraneous routes/policies etc. You then assign the resulting UK-based dialplan to your UK users and they can dial numbers as they are accustomed to in the UK.


  1. What when you have phone nummers from Micosoft so no sip trunk?

    1. If Microsoft is your telephony provider, will create customized normalization rules appropriate for your country, so users can dial phone numbers as they are accustomed. There is no need for voice policies/routes etc.

    2. Ken, long time, hope you're well. If I am using Microsoft Calling Plans, do I select the option for SIP trunk? If I do, how does the app know to drop the voice policies, etc?

    3. It doesn't really matter if you select SIP trunk or the other option. The script will see there aren't any PSTN gateways, and will just apply the dialplans. Easy peasy!

  2. Hey Ken, I am new to Teams Voice, and wondering when we connect the SBC's to our tenant, how do we assign phone numbers that the SBC manages?

    1. You assign phone numbers to users/call queues/auto attendants. The calls will route to the SBC according to the voice routes that have been assigned to that SBC.

  3. For Canada, your script sets up a dial plan for 7-digit 310 service numbers (CA-310Numbers), but I do not see a voice route to allow these to flow to the SBC. In my testing they were not reaching the SBC, even though I had International access, so I added a "310Numbers" voice route with ^\+(310\d{4})$ as the pattern (I placed it after TollFree routes and before Premium routes), and once that propagated I was able to get those calls to reach the SBC.

    1. You're right. It works for SfB, but not Teams dialrules. I will investigate.

    2. And the issue is now fixed. Thanks for bringing it to my attention!

    3. Awesome! I adjusted my route priorities to match your update (310Numbers at the bottom).

  4. This one is not a script issue, but one you may be interested in nonetheless. To secure our Audio Conferencing dial-out policies (as our meeting policies are very open), and to limit any Microsoft Calling Plan users to Domestic calling, which according to the MS docs is supposed to be Canada+US for both Canada and US-based users, I set CSDialOutPolicy as follows for all our users:

    Grant-CsDialoutPolicy -Identity $identity -PolicyName "DialoutCPCZoneAPSTNDomestic"

    Another reason I did this is by default many users had no policy, but some users, who had never had voice, but have moved through various E bundles plus add-ons over the years before getting to E5, had a policy applied (I forget which was most prevalent now but all were not the same).

    The result I have found though, is that this limits Direct Routed users to calling within their home country only, even though the National voice route is your Canada/US policy. The calls to the other country never reach the SBC. I verified this behaviour across a Canada and a US-based user, and I A-B tested it by enabling a user to be Direct Route International (made no difference), and a user left on National but changed to DialoutCPCZoneAPSTNInternational (user could then call both Canada and the US).

    This seems like a bug to me! The Microsoft docs are clear US and Canada are supposed to be treated as Domestic, yet Microsoft is restricting to in-country only, and that is evaluating even when the user is Direct Routed. I will open a case with Microsoft on this when I get time, but for now I am just setting the users we are activating to DialoutCPCZoneAPSTNInternational.

    Related docs:

    1. Well this one took a while to work its way through Premier Support all the way to to the product group. This is acknowledged as a bug (or at least behaviour that does not match the documentation) and it is reproduceable across tenants. We were told they are working on it but that it will take months to fix.

  5. Can your dial plans handle commas in phone numbers (for pauses to dial extensions)? I have gotten into the habit of structuring all my phone numbers in Outlook this way as iOS and Android recognize the comma as a pause. Microsoft even uses this format itself on tel: URLs for Microsoft Teams meeting dial-in numbers. In my testing, tel: links with commas return an error in Teams ("There was an issue with finding the person you were trying to reach"), and when putting commas in a dialed manually in Teams, they are ignored (the number itself gets dialed).

    This may be a feature request for Microsoft Teams instead?

    1. I've never considered adding comma support to They never seemed to work in SfB, and from your testing, they don't seem to work in Teams either.

    2. I found the Teams User Voice for commas support:

    3. Do you know if a dial plan rule could help here (i.e., ignore everything after a comma)? All the numbers with extensions I've added to my address book over the years to dial easily while on-the-go on my mobile phone can't be dialed from Teams. The Copy function is even greyed out in Teams, so I have to go back to Outlook to copy the number. Very annoying.

  6. Hi , how you route inbound calls < did> from PSTN via SBC to MS Teams DR - to users/teams
    Is the routing done via GUI or PowerShell

    1. Your SBC has to be configured to send incoming PSTN calls to Teams. If Teams is the only platform you're routing calls to, then its pretty straightforward. Refer to your SBC docs for details. Once it gets to Teams, it should all work (assuming the number is formatted to E.164 standards, and you're using my dialplans to get there.

  7. hi Ken, I ran you script for teams at the end of last year and im sure i did a month or 2 ago as well. Today i created 2 scripts for Teams and both of them has come up with this error - Cannot find specified online pstn usage "ZA-Durban-Local"
    +Category InvalidArgument: (Microsoft.Rtc.M...ceRoutingPolicy:OnlineVoiceRoutingPolicy) [Set-CsOnlineVoiceRoutingPolicy], ArgumentException
    + FullyQualifiedErrorId : CustomValidationFailed,Microsoft.Rtc.Management.Internal.SetOnlineVoiceRoutingPolicyCmdlet

    1. I see the same thing. It works fine on a clean system, but as soon as there are a set of existing PSTN usages, the command fails. Looking into it.

    2. It appears that O365 is being slow in making the PSTN usage available for adding to voice policies. If I run the commands a few minutes later, it works fine. I may have to add some delay to the script to allow for this.

    3. Further tests show that it takes up to 10 minutes before the Set-CsOnlineVoiceRoutingPolicy command recognizes there are valid PSTN usages, even though (Get-CsOnlinePstnUsage).Usage shows them right away.

    4. Trying to run the script now and it seems to be stuck on the delay, it's on round 55 and still PSTN usage not ready, any suggestions what could be wrong?

    5. Some people are reporting multi-hour delays. Just let it go and see if it works. It might time out on the connection after some point. Please report back with what finally happened.

  8. Hi Ken. Great work.

    I'm in Australia and when I produce a ruleset (Does not matter which region and if it's simple) it creates a Normalization rule called 'AU-Service'.

    The regex in this rule is failing when getting added via the PS1 script.

    Here is the regex: ^(000|1[0125]\d{1,8}|(13(\d{4}|00\d{6}))$

    It fails on some online regex checks as it is missing a closing bracket. Can I presume that the closing bracket should go after the 000 or should it be added to the end, just before the $?

  9. In response to my own query about the AU-ruleset, the closing bracket goes at the end.

    1. Yes, you're right. I've corrected the defect. Thanks for telling me!