Home » Microsoft Teams

Category Archives: Microsoft Teams

Skype for Business Mobile Autodiscover Gotcha When Moving to Microsoft Teams

On a migration recently we moved a bunch of users from Skype for Business On-Premises to Microsoft Teams Only, leaving behind Enterprise Voice users for the time being.

During this interop period it is required that both EV and Teams users can join Skype for Business meetings hosted by the remaining on-prem users until such time as Teams meetings take over.

After moving several users, reports came in that Teams Only people could not sign in to Skype for Business using the mobile app to join a Skype meeting, but where able to sign-in using the Skype desktop client.

The message received on the mobile client

Troubleshooting the issue with the old Lync Connectivity Analyzer suggested that something wasn’t quite right with the authentication process via the autodiscover web service

Autodiscover: SendRequest(): the URL https://lyncdiscover.commsverse.com/Autodiscover/AutodiscoverService.svc/root/user?originalDomain=commsverse.com?sipuri=John.Smith@commsverse.com couldn’t be connected.  Complete HTTP headers:\r\n Pragma: no-cache

I decided that I would run CLS Logging and trace the authentication, so from a Front End

Start-CsClsLogging -Pools FEPOOLA.commsverse.com -Duration 00.00:02 -Scenario Authentication

I attempted to sign in from mobile to capture some logs.

Searching the logs, top tip to reduce the export size, always specify the URI you are interested in, it makes following the log much, much easier!

Search-CsClsLogging -Pools FEPOOLA.commsverse.com -OutputFilePath c:\temp\mobile.log -Uri "john.smith@commsverse.com" -MatchAny

Opening the log in snooper and following it line by line I found that authentication passed, but the FE could not find the hosting provider for the user.

(0000000001B0CB29)Could not find hosting provider for hosted user. User: john.smith@commsverse.com

So Next step was to check the hosting provider for Skype for Business Online

Get-CsHostingProvider -Identity SkypeforBusinessOnline
Identity                  : SkypeforBusinessOnline
Name                      : SkypeforBusinessOnline
ProxyFqdn                 : sipfed.online.lync.com
VerificationLevel         : UseSourceVerification
Enabled                   : True
EnabledSharedAddressSpace : True
HostsOCSUsers             : True
IsLocal                   : False
AutodiscoverUrl           :

Missing was the Autodiscover URL for Skype Online, so setting that on the hosting provider as follows

Set-CsHostingProvider -Identity SkypeforBusinessOnline -AutodiscoverUrl https://webdir.online.lync.com/Autodiscover/AutodiscoverService.svc/root

Forcing replication of the CMS and then trying to sign in again fixed the issue and Teams Only users are able to sign in to Skype for Business Online using their mobile app successfully.

The reason why the desktop client was unaffected is because it looks up the _Sip SRV DNS record for the access edge location and redirection was happening properly through SIP registration.

So make sure you set the Autodiscover URL in your Hosting Provider for SfB Online if you want mobile sign in for those legacy meetings!

Tranching Users Ready For Your Microsoft Teams Migration

If you are planning on migrating your users from another system, perhaps Skype for Business or indeed a 3rd party system, the question of how to do this gets more complicated to answer as the numbers of users you have to deal with increases.

Consider the scenario where you have a large Skype for Business deployment of tens of thousands of users. There will be a number of users with a persona that can be easily migrated to Teams e.g. chat and meetings. Others will be more complicated and require more thought, voice users for instance.

Doing moves from Skype to Teams using PowerShell is a must, but when you are moving hundreds, or thousands of users in multiple threads and shells to the cloud at scale and speed, how do you keep track of your progress, and more importantly ensure that you are moving the correct users?

The answer invariably means tranching your users offline in some kind of Excel file. To ease the burden of this manual task I have created a simple script to tranche users based on a full export from your Skype for Business deployment.

Step 1 – Export your users to csv files

You can export your users to one csv file by running this command

Get-CsUser | Export-Csv c:\temp\allusers.csv -NoTypeInformation

Alternatively, you can export by whatever chunking condition you want, e.g. users by pool

$pools = Get-CsPool | Where {$_.Service -like "*Registrar*"} | Select Fqdn
ForEach ($pool in $pools){
    Get-CsUser | Where {$_.RegistrarPool -eq $pool.fqdn} | Export-Csv "c:\temp\$($pool.fqdn).csv" -NoTypeInformation
}

Step 2 – Edit the Tranching Script

The default behaviour of the tranching script is to tranche all users that do not have Enterprise Voice Enabled. You can make your own filter by editing the line (31)

$validUsers = $importFile | Where {$_.<column name to filter on> -<condition> <value>}

Step 3 – Run the Script

Run the script from PowerShell to parse the source files extracted in Step 1. The script will ask you the location of those files as an input parameter e.g. c:\temp\.

The script will collect all csv files in that directory and parse them as per your condition filter. By default, it will create csv files in output folders in blocks of 250 users per file. You can then use these files to migrate to Teams using multiple shell windows, users and servers.

If you want to change the number of users per tranche, edit the script and change the following variable

$blockSize = <your number here> default is 250

The script can be found below

 #Tranching Users by Source File

$sourceDir = Read-Host "Please set the working directory of where the Source Files are"

$filesToProcess = Get-Childitem $sourceDir | Where {$_.Extension -eq "csv"}

ForEach ($sourceFile in $filesToProcess){

    Set-Location $sourceDir

    $importFile = Import-Csv $sourceFile.Name

    #create output dir

    $folderName = ($sourceFile.Name).split('.') | Select -First 1

        try{

        New-Item -ItemType Directory -Name $folderName -Force

        }catch{

        }

    Set-Location ".\$($folderName)"

    # Filter users that are not EV enabled

    $validUsers = $importFile | Where {$_.EnterpriseVoiceEnabled -eq $False}

    $countUsers = ($validUsers).count

    Write-Host "There are $($countUsers) users found to be tranched" -ForegroundColor Yellow

    # Set Pagination

    $blockSize = 250

    # Create Tranches

    $startPos = 0

    $counter = 1

        While($startPos -lt $countUsers){

                $validUsers | Select-Object -Skip $startPos -First $blockSize | Export-Csv "MigrationBlock_$($counter).csv" -NoTypeInformation -Force

                $startPos += $blockSize

                $counter++

                Write-Host "Tranching next Block Starting at Row $($startPos)" -ForegroundColor Yellow

    }

}

    Write-Host "Finished Tranching Users" -ForegroundColor Green 

Data Inaccuracy Blocks Voice Deployments with Microsoft Teams

Time and time again I come across the same old issue when customers want to use Skype for Business or Microsoft Teams for voice and they want to retire their old PBXs. That is data quality issues!

The same can be said for customers who are green and want to “light up” calling in Teams from their existing Office 365 data.

What data am I talking about? Well, the most important piece of data is the telephone number. This should be simple, but often it is not. You’d be surprised that although the end user of that phone number knows it, the majority of the systems and administration staff (IT, HR etc.) don’t.

Typically, there will be several directories used as the data source, we have AD, HR databases, PBX phonebooks, Printed Cards, or books on walls, people’s personal contacts, post-it notes. The list goes on. Yet when you look at all these data points you’ll realise that the number for Mary Smith is different across these data sources.

You see, in the PBX and VoIP world, the phone number is not personal to the user assigned it. The number is personal to the device the user is using. Think of it as a tenant and landlord situation. The mortgage company (in this case the PBX) deals with the landlord to make sure that they keep on paying their bill (the phone), while the tenant (the end user) lives in the house. Tenants change, as do people using the phone, but the landlord doesn’t tell the mortgage company because they don’t care, its information they do not need to keep the system running as designed.

What usually happens in organistions is that when a user moves, the next person just takes on the phone and number on the desk. There probably won’t even be a IT ticket for it, just the Manager saying, ah yes, use that phone.

As time goes by, change by change the data that was once accurate drifts more into irrelevance. Fundamentally, the system works, but when you come to the point of moving away from it, then you realise how much of a pickle you have found yourself in.

If you are moving to another VoIP system, then of course, it’s easier, but still trash in, trash out if you do not address the data issue. However, with a Unified Communications platform that ties a user to several communication identities e.g. E-Mail, SIP Address, Phone Number in one platform, it is absolutely necessary that you know that each of these identities is accurate for that user.

Take a situation I found myself in many times. The customer supplies the data file from their HR database of users to migrate. They affirm to me that this is the most up to date, most accurate data source to base a migration off. We analyse the VoIP system to find the relationships between the stations, set up hunt groups, shared Line appearances, team call groups etc all ready to go, and then we enable the users we believe are the owners of that number for Teams / Skype. Job done, lets all go back to the hotel for some Pizza!

The following day feeling all positive and enthusiastic because the night before went so well, we get to site. 9:45am, there is a service ticket, I am not receiving calls. 9:50am Why am I getting John’s calls, I am Matt? 09:51am Why are some phones randomly ringing together in the office?

All of a sudden we are hauled into a crisis meeting and hastily roll back the migration. The customer labels the migration a failure and offloads on to us.

The reality is quite different, the migration was a success, the process and steps worked end to end. The problem of course was the data that was supplied into the process at the beginning.

Experience has taught me that pre-project / migration data cleansing is absolutely necessary. A lot of companies will not factor that in to their migration project and when asked will be very resistant to remediation. But if it is not done, then moving to Teams will be a very poor experience.

To fix the problem, you must first understand how it has been created in the first place. Here are some of the most common factors that I have come across

  • User leaves and service ticket as part of the off-boarding process is not assigned to telecoms for decomissioning of the station assigned to the user
  • User moves within same department and number change happens without IT involvement
  • Issue with the execution of the off-boarding process where the telecoms admin does not update the station profile
  • There is no relation to users in the PBX configuration for a station by design because admins don’t want the problem of maintaining relationships to names
  • AD admins when disabling the account for (x time to infinity) do not remove the number from the telephone attributes in the object

Moving to Unified Communications enforces change to these processes and they must be enforced otherwise the business will grind to a halt. With Teams et al you cannot simply get around a miss assignment of a number, because the system will route the call straight back to the person who has been assigned that in the UC platform, regardless of any external factor that disagrees with it.

How do you fix these as a project?

Well, you have three choices that you can make with your customer

  1. The best solution is to remediate the problems at source and give you a good start of migration success. This will involve surveying the users and asking them to confirm their phone number to you. Once confirmed, you’ll need to update the source systems, but AD will be the most important one as that is what Teams will use moving forward
  2. Fix forward and move with a dataset that you collectively agree based on business analysis is the most accurate knowing that their will be problems and having a process in place to resolve those
  3. Implement green field and give as many users as the business can sustain new phone numbers and manage through change and awareness.

All options require you to keep at least AD up to date. Giving new phone numbers to users is not as taboo as people make it out to be. It can be managed if it is known ahead of migration. Some may have to keep their number, but if 80% of users could function with new numbers with no business operation impact, your migration complexity has just reduced to 20%. This means that the customer can start taking advantage of Teams for voice quicker whilst the complex scenarios are worked on.

I have run successful migrations in the past where number change has been managed through advanced communications and instructions with a clear date where the old number will cease to operate. One technique is to get the user to record a voicemail greeting to say on this date my number will change to.. as well as email footer updates etc. Its not that hard and it is more convenient to the user than IT trying to ensure quality through compromised data and getting it wrong.

Once decided on the model, ensure that operationally you fix the processes to ensure that AD is updated with MACD changes. Teams / Skype contact cards will use phone numbers (including mobile) that are extracted from the telephone attributes on the user’s object. These are synced to AzureAD and any inaccuracies will also be present there.

My closing statement here is that enabling Teams voice is really easy as long as you embrace and face the problem of data quality head on before you plan migrations. If you ignore it or dismiss it’s importance, then believe me you will feel pain at a level you have never experienced before. Untangling spaghetti is hard enough, it’s even harder when it is boiling hot!

 

Proximity Join Fails with Microsoft Teams

I was demoing Microsoft Teams Meeting Rooms Proximity Join yesterday and came across a couple of nuanced behaviours that effect the ability to utilise this feature.

The first issue was that even with bluetooth beaconing enabled, the Teams client wouldn’t find the room when pre-joining the Teams meeting.

The problem was that post enabling the bluetooth beaconing feature you should restart the MTR device as just enabling it in the back end admin settings is not enough.

The second issue that prevented proximity join derives from organiser presence. If you are in a presence state of Do Not Disturb, this prevents proximity join from working. The fix here of course, is to reset, or change your presence state to another setting and then proximity join will work.

Very short post, but these are simple fixes that at the start of a meeting can make you panic.

 

Microsoft Teams Media with Privacy Boundaries

Recently there was a discussion between myself and a few fellow colleagues about how media is treated by Microsoft based on the client and tenant location. This came about as a result of an Ignite session delivered by Korneel Bullens over the use of Transport Relays with Microsoft Teams media. It caused quite the debate…

The issue we debated was the bullet point “EU tenants use Transport Relay in EU and US” as a result of EU privacy laws. Searching the wide open internet revealed no clarification, or supporting evidence relating to this specific scenario, which contributed towards trying to get to the bottom of issue.

Our understanding, as per current documentation on docs.microsoft.com is that a transport relay closest to the client’s geographical public IP address is chosen where possible.

What is seemingly missing from this statement are the words “feasibly and legally” possible, according to the statement in the above slide extract.

So now consider your organisation has global presence, especially in APAC regions, but you have an EU tenant. What does this mean for your media paths for APAC users?

On the face of it seems that their media path is going to take a significant path across the internet for calls.

Well, after testing the answer is, it depends…

First, let’s take Teams meetings. A Teams meeting is spun up in the region that the first joiner is located. If this person happens to be in APAC, then the Teams meeting and MCU will be located in the APAC region e.g. Hong Kong, Singapore etc.

The behaviour now depends on your outbound security settings on the corporate firewall, and taking some assumptions about internet breakout.

If the APAC users break out of the corporate network in APAC, their client will have an APAC geo-ip. In an unrestricted deployment where the Teams client can use high ports to connect to media, the Teams client will connect directly to the media processor closest to the conferencing server. In the scenario above, this is great news for APAC users, because they are not using transport relays and therefore, are able to connect directly to the B2BUA (MP).

However, where this falls apart is when APAC users join a conference that was started by someone in EMEA, or NORAM. In the unrestricted deployment, media will be connected over the internet between the corporate APAC Internet IP and the  Media Processor in EMEA for instance. This opens up lots of potential for media issues traversing unmanaged internet routers and links.

In a restricted deployment, the Teams client would be blocked from connecting to high ports, instead being allowed only to establish media using Transport Relays. This is the current recommended deployment model from Microsoft and their Office 365 IP and URL ranges (Rule ID 11) only recommends UDP is open on 3478-3481. When this is enforced, the Teams client must connect to a transport relay to proxy its media to either the media processor, or to the SBC in the case of a PSTN call.

This is where the potential issue lies. If we have an APAC hosted conference, then in this mode according to the slide at the top of this page, the APAC users would relay their media by via a server in EMEA which would then relay that to the media processor in APAC. Not an ideal media route and we start to have conflict between an organisation that has stricter controls on outbound connections and the technology we are trying to implement.

Before we conclude this scenario, let’s now understand how PSTN calls behave in the same situation.

In a non-media bypass scenario and in an unrestricted deployment, the Teams client will connect to the Media Processor that is closest to the terminating SBC and not to the client. In APAC, it is likely that the SBC will be located in the same region as the APAC user, again this appears optimised.

However, if the APAC user is making an international call, and the organisation has deployed least cost routing, regulation permitting, international calls would be placed using SBCs in distant regions. In the unrestricted model, the APAC Teams client would connect to the Media Processor in EMEA for European international calls and NORAM for North American Calls etc. Similar to the way that conferencing works.

In a restricted deployment where transport relays are enforced, or media bypass has been enabled on the SBC, the above slide suggests that media would be relayed from APAC to EMEA and back to APAC for PSTN calls if media bypass was not possible.

Putting this to the test. I deployed a virtual machine in an AWS datacenter in Singapore. I wanted to be sure to be using a network that was not owned by Microsoft and conducted several tests.

The first test was a PSTN call to an international number based in EMEA with no restrictions. The below is a trace route of the media flow.

We can see that media is connected between the APAC client and the Media Processor on high ports. When IP tracing the IP 52.122.162.21 it returns a location of Dublin, Ireland.

So as expected, the Media Processor closest to the SBC was selected and the APAC client uses the internet to transport media from the client edge to the Media Processor in Ireland.

Now the same scenario, but with high ports blocked and the client forced to use transport relays. If the slide above is correct, the IP of the transport relay should also return either an EMEA, or US geo-ip.

Performing an IP trace on 52.114.13.37 resolves a location of Singapore!!

This seems to discredit the interpretation of the slide that caused the debate, but then neither of us attended it and we’re probably missing a lot of context. The reality is that the IP address is just the Azure Edge IP that the client connects to. The actual server can be anywhere inside Azure. We just don’t know and neither should be be concerned about it. The point is that media path is optimised. What happens inside Azure is outside your control and if there were problems, you’re backed by a financial SLA by Microsoft.

The same experience is found for meetings as well during the tests. For EMEA conferences, APAC users in a restricted deployment connected media via the Singapore relay server and EMEA users connected to an EMEA transport relay for an APAC conference.

Why is this better or worse than just allowing unrestricted connectivity to Microsoft?

The key difference in all scenarios is that a transport relay is selected based on the connecting client’s public IP address and not the closest media processor to the termination point (conference mcu or sbc). This ultimately means that the media path between the connecting client and Microsoft Azure Edge is always optimal and shortest possible. The relay server will then relay that media to the conference server, or SBC using the Azure global network as opposed to the wild internet, which in most circumstances will offer a greater experience to everyone in the meeting or call.

It is also highly recommended to enforce relay if you’re deploying hosted Direct Routing as with or without media bypass you are always going to ensure that the closest relay point to the client / site is chosen and you leverage the Azure network to optimise the media between the relay and the hosted SBC.

Example:

APAC Client —> Internet —> Azure APAC —> APAC Transport Relay —> EMEA Media Processor —> EMEA Azure —> Internet —> Hosted SBC —> PSTN

as opposed to

APAC Client —> Internet ——————————> Azure EMEA —-> EMEA Media Processor —-> Internet —-> Hosted SBC —> PSTN

In summary, the transport relay may or may not be in the EU or US as per the slide. The important design factor here is that by enforcing relay, you are optimising your media paths to take the shortest hop across the internet to the Microsoft network as possible in all scenarios and that is a good thing!

 

 

Microsoft Teams Meet Now Is Here

Microsoft have released another feature to Microsoft Teams that has been available in Skype for Business for many years, Meet Now!

Meet Now is a feature that allows you to start a meeting on-demand, without having to schedule it in advance. In Skype for Business, personally I never really used this feature, simply because I could start a conversation with someone, and then add another person into that conversation using drag and drop, thus making it a conference, or meeting.

However, with Teams, perhaps this feature could be more needed than it ever was in Skype for Business. Teams lacks the ability to drag and drop people into on going conversations that would then make a meeting. Instead it is invite based.

The biggest use case I can see for this feature is from Teams Meeting Room devices, or Teams conference phones where you’re in a room discussing a topic and need external support from multiple parties. Here, Meet Now is a significant and welcome feature!

With a Teams meeting, the organiser would then have the meeting controls to dial people in to the meeting from the PSTN (subject to audio conferencing license, or calling plan), as well as also being able to ping a meeting join link to others via chat to say “Join now”.

There are situations however, where you need to be selective in this features use. If all participants in the meeting are already using Teams for meetings, or Teams Only mode, then when you invite them to your meeting, they will join via the toast notification they’ll receive. But, if a participant is using Skype for Business then meet now is not going to currently work for them.

This is due to the current fact that currently, Skype for Business clients are not compatible clients to connect to a Teams meeting. When invited, the Skype for Business user may get an invite, and if they click accept, meeting join will fail. They have no other means to join that conference, because they don’t know the meeting join link. In most cases, Teams would simply let you know that it could not invite this person.

This leaves you as an organiser with three options to choose from

  1. Do you try and dial the Skype for Business user into the meeting using their PSTN phone number? This could be a disadvantage, especially if you are screen sharing, or expecting the Skype user to contribute
  2. Do you copy the meeting join information to your clipboard, open a separate chat to the Skype user and paste the join Information? This will allow the Skype user to join via the web client
  3. Should you have used scheduled meetings instead of Meet Now? and simply carry on with the meeting without the Skype person?

As more and more organisations start to move to Teams, this feature will grow. For now at least, in my opinion, you should be selective of its use.

Microsoft Teams Location Based Routing Not Just For Toll Bypass Laws

Some of you may have been unlucky / lucky enough to work on global voice deployments that have encountered telephone regulations restricting the use of a flexible Unified Communications telephony network that is designed for least possible cost. The term LBR, of Location Based Routing to give it’s full name is a technology baked into the majority of UC systems that help keep organizations compliant with these laws.

Traditional LBR Implementation

In this example, we see a traditional implementation of LBR in action. A user in the Dubai Office wants to make a phone call to a person in the UK. The organization the user works for has a telephony gateway in the UK. Under normal conditions where there are no regulations, the organization can configure all outbound UK numbers to route through their UK gateway and therefore pay local rate call charges, instead of international rate. However, the UAE, amongst other nations have regulations that stipulate all international (and sometimes even provincial) numbers must route out of a gateway connected to the local telephone company network. This means the organization must force calls through this gateway when a user is in the Dubai Office. This is where LBR comes in.

As we move towards a cloud first telephony model, and in particular Teams, it presents some challenges within organizations that have been using traditional PBXs installed at various locations. In the old model, we had a number of PBXs we could configure for its individual site’s needs. In Teams we have one “PBX” type replacement that is used across all sites. As a result, there are some limitations to this concept and design.

Let me give you an example. An organization has 10 sites in the UK of various sizes. At each site there is a PBX of some description. The sites a physically protected by the Organization’s security team who are responsible for building and campus security and emergencies. The Organization has a policy that any employee can dial 3333 from any phone and get through to the security office personnel responsible for the site in which they are calling from.

How can you achieve this same experience in Teams, from desktop clients, to mobile and desk phone?

The first solution may meet most of your requirements depending on how mobile your workforce is. The simple solution would be to create a dial plan for the site that transforms 3333 into the local DID of the security office Teams Object, whether that is a single endpoint, an endpoint with team call group or even a Call Queue.

However, if you have a mobile workforce that float between sites, then how do you maintain continuity for them? Using the 1st approach, if the mobile worker dialed 3333 at a site that is not “home” to them, they would be connected to the wrong security office which will waste valuable time and even compromise the emergency they are calling about. IT cannot change dial plans in line with each transient move.

Another solution could be to create different emergency numbers at each site. Site 1 is 3333, Site 2 is 4444 etc. But this relies on users remembering and knowing these. Probably not a feasible solution.

But wait, don’t people search by name in Teams? Can we not just rely on that? Well, you could, but the same problem arises in what do users have to type in to get the lookup record they want? Typing a name also takes up valuable time, more if you have to use a desk phone or mobile. It’s also probably not an ideal solution.

You could compensate for this by creating your own Teams Directory App that lists clearly all the important contacts in your Organization, therefore all the user has to do is visually find the contact that applies and click to dial. Again, you’re limited to desktop client for this functionality currently and in emergencies people are going to want to dial a number from a physical phone in most cases.

So where does this leave us?

It leaves us with two viable options. Option 1 is to deploy a contact center where the 3333 number routes to that has a voice IVR that is capable of routing based on voice answers, e.g. Please tell me the site you are calling from? Answer: Cardiff. Then routes to the Cardiff site security office.

Or use LBR to route these calls to the local security office. From the outset, you would need a local SBC at each site. Whether you choose to connect them to local PSTN services or centrally is your choice. Looking at the SIP message received from Direct Routing, all client IP information is abstracted and replaced by Microsoft SBC FQDNs, so we are unable to rely on a conditional route based on the Client IP in the FROM Header. Shame.

Using LBR in this way would enable you to route the call placed to 3333 to any desired endpoint, whether that is in Teams or another PBX.

LBR used for Internal Routing Of Calls

In this example we have two sites, Cardiff and London. London is the main office and has an SBC connected to the PSTN via a chosen ITSP. Cardiff is a satellite site with no direct local PSTN access. Deploying an SBC here and connecting it downstream to the London SBC will allow Cardiff users to share the PSTN access with London, but also be leveraged for this use case.

Simon is a mobile worker and splits his time between both sites. We cannot rely on Simon’s SIP message information from pstnhub to determine location. The only reliable information we have is the client IP address used by his device and that is held only between the client and Microsoft Teams Back-end. So we have to look inside what we can do with Microsoft.

By using LBR and taking his client workstation IP we can determine which gateway to use based on this information allows Simon to dial 3333 at both sites and get through to local building security personnel.

In Teams each building security object would have it’s own unique DDI and the SBCs would transform 3333 into that local number and route back into Teams for lookup and dial tone. Any other number would be routed back to the London SBC for PSTN dial tone.

At first it seems an over engineered solution to an internal problem, but when global numbers are used to route to local services it is often very hard to change this approach. In all likelihood any organization of this size and complexity probably have a valid use case for local PSTN access too making this more cost effective. Smaller organizations may want to consider the options suggested earlier in this article first as they carry less investment.

However, it seems that Location Based Routing for Microsoft Teams is going to play more of a part in modern cloud telephony than it previously did with On-Premises. For more information about how to set up LBR for Microsoft Teams:
https://docs.microsoft.com/en-us/microsoftteams/location-based-routing-plan

Unused Teams Audit & Warning with Microsoft Teams & Graph API

Inspired by my peers using Microsoft Graph API to interact with the Microsoft 365 substrate and in particular Microsoft Teams, I decided to give it a whirl and see what I could break.

I came up with a valid problem that administrators will face as Teams proliferate through their organization. How to clean-up unused Teams. We accept there will be a lot of Teams created for test purposes. There may also be well used Teams created that have expired their usefulness and simply left dormant. What do we do with these?

Built into Microsoft 365 we have the ability to set group expiration policies. Two issues with these policies spring to mind. 1) They required AzureAD Premium P2 licensing and 2) they work on a fixed time duration approval basis. This means every X Days a Team / Group owner will need to reconfirm they still want the Team / Group to remain active. This may get annoying for Owners who have multiple Teams.

Using the Graph API, we have access to lots of information, some of which is inaccessible by any other means. I wanted to investigate if I could audit Teams and figure out if there has been any activity in that Team within a set number of days. If not, then post a message into that Team to warn members, unless they use it, it will be deleted or archived, whatever your policy dictates.

The metrics used are message creation date in any Team channel and any file activity on the Team drive. If there has been activity in either metric within a configured time period, consider these Teams active and ignore. If the last activity exceeds the configured time period, issue a warning to the General channel of the Team by posting a channel message containing the warning.

To get started, I enlisted the help of Lee Ford’s amazing post on getting started with Graph API and PowerShell. I suggest you head on over to his post to discover how to create the required AzureAD App Registration.

Back? Good. There are a few alterations to the AzureAD App permissions needed for the script to work. These are:

  • ChannelMessage.Read.All
  • Group.ReadWrite.All
  • User.Read.All
  • Files.Read.All
  • Directory.Read.All

Delegated User permissions are needed to post messages in the channel, so you need to add the following for these:

  • Group.ReadWrite.All

Unfortunately, we have to use both Application and Delegated permission because we cannot send a message to a Team as an Application. It has to be done by an account with membership to the Team in question. The end result will post a message in the offending Teams like this

The script will run, collect the information and then decide if it should post a message. If it does, then it will temporarily add the delegated user to the Team as an owner, post the message and subsequently remove the user from the Team to maintain information security. At the end the administrator will have the option to export the Teams that were affected to a CSV file for further analysis.

Below is the script:

<###########################################################################

SCRIPT: WARNUNUSEDTEAMS.PS1

THIS SCRIPT CAN BE LAUNCHED IN POWERSHELL AND YOU MUST HAVE AZUREAD POWERSHELL MODULE INSTALLED

TO INSTALL RUN INSTALL-MODULE AZUREAD

YOU MUST CREATE AN AZUREAD APP REGISTRATION FOR THIS SCRIPT. THIS SCRIPT REQUIRES BOTH APPLICATION
AND DELEGATED PERMISSIONS TO RUN

MORE INFORMATION HTTPS://blog.valeconsulting.co.uk

OFFERED WITHOUT WARRANTY, SUPPORT OR RESPONSIBILITY. USE AT YOUR OWN RISK

###########################################################################>


<###########################################################################

SCRIPT PARAMETERS - PLEASE MODIFY

###########################################################################>

#SET THE NUMBER OF DAYS FROM TODAY THE SCRIPT WILL CLASSIFY AS MINIMUM ACTIVITY

$days = "60"

#SET YOUR AZUREAD APP ID'S, SECRET AND SUPPORT UPN USED TO RUN AS USER TO POST MESSAGES IN TEAMS HERE

$clientId = ""
$tenantId = ""
$clientSecret = ''
$supportUPN = "itsupport@valeconsulting.co.uk"

<################################################################################################

PLEASE MODIFY THE CONTENT ELEMENT WITH YOUR CHOSEN MESSAGE YOU WANT POSTING IN THE TEAM

#################################################################################################>


$body = @"
{
"body": {
      "contentType": "html",
      "content": "Hello, we have noticed that this Team has not been used for at least $($days) Days. In line with our IT Policy, if there continues to be no activity for a further 30 Days, this team will be automatically deleted. Thank you. IT Support"
    }

}
"@

<#################################################################################################

DO NOT EDIT BELOW THIS LINE

##################################################################################################>

<#################################################################################################

CREDIT TO LEE FORD (WWW.LEE-FORD.CO.UK) FOR THIS SCRIPT ELEMENT

##################################################################################################>
# Construct URI
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

# Construct Body
$body = @{
    client_id     = $clientId
    scope         = "https://graph.microsoft.com/.default"
    client_secret = $clientSecret
    grant_type    = "client_credentials"
}

# Get OAuth 2.0 Token
$tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing

# Access Token
$token = ($tokenRequest.Content | ConvertFrom-Json).access_token

Write-Host "Connected to AzureAD App and Acquired Token" -ForegroundColor Yellow

# Base URL
$uri = "https://graph.microsoft.com/beta/"
$headers = @{Authorization = "Bearer $token"}
$ctype = "application/json"
<#################################################################################################

END CREDIT

##################################################################################################>


#Get Support UPN Object ID
Write-Host "Getting Azure Object ID of Support UPN" -ForegroundColor Yellow
$objID = Invoke-WebRequest -Method GET -Uri "$($uri)users/$($supportUPN)" -ContentType $ctype -Headers $headers | ConvertFrom-Json

 
#Get all Teams
Write-Host "Getting All O365 Groups that are Teams Enabled" -ForegroundColor Yellow
$graph = Invoke-WebRequest -Method GET -Uri "$($uri)groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')" -ContentType $ctype -Headers $headers | ConvertFrom-Json

#For each Team now find their channels, last message and last modified file.

$results = @()
Write-Host "Analyzing Teams Activity. Please Wait..." -ForegroundColor Yellow

ForEach ($team in $graph.value){

        #Get files activity

            $drive = Invoke-WebRequest -Method GET -Uri "$($uri)groups/$($team.id)/drive" -ContentType $ctype -Headers $headers | ConvertFrom-Json

            $activity = Invoke-WebRequest -Method GET -Uri "$($uri)drives/$($drive.id)/activities?`$top=1" -ContentType $ctype -Headers $headers | ConvertFrom-Json

            $lastTime = $activity.value.times.recordedDateTime

        #Get Teams Owners

            $owners = Invoke-WebRequest -Method Get -Uri "$($uri)groups/$($team.id)/owners" -ContentType $ctype -Headers $headers | ConvertFrom-Json       

             
        #Get Channels from Team

           $channels = Invoke-WebRequest -Method GET -Uri "$($uri)teams/$($team.id)/channels" -ContentType $ctype -Headers $headers | ConvertFrom-Json

        #Loop through channels and get last message by date      

                ForEach ($ch in $channels.value){

                    $chmsg = Invoke-WebRequest -Method GET -Uri "$($uri)teams/$($team.id)/channels/$($ch.id)/messages?`$top=1" -ContentType $ctype -Headers $headers | ConvertFrom-Json

                    #if there was a message and it was posted over the time period set warning flag to true
                    
                    if ($chmsg.value.createdDateTime -ne $null){
                        
                        $time = New-TimeSpan -Start $chmsg.value.createdDateTime -End (Get-Date)

                        if ($time.Days -gt $days){

                            $warn = $true

                        }else{
                            
                            $warn = $false

                        }

                    }else{
                        
                        $warn = $true

                    }

                    #if there has been no chat activity, check file activity

                    if ($warn -eq $true){
                            
                            if ($lastTime -ne $null){
                                    
                                    $ftime = New-TimeSpan -Start $lastTime -End (Get-Date)                                    

                                    if ($ftime.Days -le $days){

                                        #if there has been file activity within the configured number of days, turn the warning flag off

                                        $warn = $false    
                                    }

                            }

                    }

                    #store all results in array
                    $results += New-Object -TypeName psobject -Property @{TeamID=$team.id;TeamName=$team.displayName;ChannelID=$ch.id;ChannelName=$ch.displayName;MessageDate=$chmsg.value.createdDateTime;MessageDaysOld=$time.Days;FileDate=$lastTime;FileDaysOld=$ftime.Days;Owners=$owners.value.mail;Warn=$warn}


                }
}

#filter the results so that only teams with warning flags set to true are used

$teamfilter = $results | Where {$_.Warn -eq $true -and $_.ChannelName -eq "General"}

Write-Host "Require User Authentication for Message Sending..." -ForegroundColor Yellow

<##################################################################################################

CREDIT TO LEE FORD (WWW.LEE-FORD.CO.UK) FOR THIS SCRIPT ELEMENT

###################################################################################################>

#authenticate with the IT user account to post a message to the general channel of these groups.

# Azure AD OAuth User Token for Graph API
# Get OAuth token for a AAD User (returned as $token)

# Add required assemblies
Add-Type -AssemblyName System.Web, PresentationFramework, PresentationCore

# Application (client) ID, tenant ID and redirect URI
$clientId = "08549aa5-f9e9-4297-a1cc-6c776c0c3a17"
$tenantId = "e7a63b11-d255-40fd-b586-9daaa76de185"
$redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"


# Scope - Needs to include all permisions required separated with a space
$scope = "User.Read.All Group.ReadWrite.All" # This is just an example set of permissions

# Random State - state is included in response, if you want to verify response is valid
$state = Get-Random

# Encode scope to fit inside query string 
$scopeEncoded = [System.Web.HttpUtility]::UrlEncode($scope)

# Redirect URI (encode it to fit inside query string)
$redirectUriEncoded = [System.Web.HttpUtility]::UrlEncode($redirectUri)

# Construct URI
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/authorize?client_id=$clientId&response_type=code&redirect_uri=$redirectUriEncoded&response_mode=query&scope=$scopeEncoded&state=$state"

# Create Window for User Sign-In
$windowProperty = @{
    Width  = 500
    Height = 700
}

$signInWindow = New-Object System.Windows.Window -Property $windowProperty
    
# Create WebBrowser for Window
$browserProperty = @{
    Width  = 480
    Height = 680
}

$signInBrowser = New-Object System.Windows.Controls.WebBrowser -Property $browserProperty

# Navigate Browser to sign-in page
$signInBrowser.navigate($uri)
    
# Create a condition to check after each page load
$pageLoaded = {

    # Once a URL contains "code=*", close the Window
    if ($signInBrowser.Source -match "code=[^&]*") {

        # With the form closed and complete with the code, parse the query string

        $urlQueryString = [System.Uri]($signInBrowser.Source).Query
        $script:urlQueryValues = [System.Web.HttpUtility]::ParseQueryString($urlQueryString)

        $signInWindow.Close()

    }
}

# Add condition to document completed
$signInBrowser.Add_LoadCompleted($pageLoaded)

# Show Window
$signInWindow.AddChild($signInBrowser)
$signInWindow.ShowDialog()

# Extract code from query string
$authCode = $script:urlQueryValues.GetValues(($script:urlQueryValues.keys | Where-Object { $_ -eq "code" }))

if ($authCode) {

    # With Auth Code, start getting token

    # Construct URI
    $uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"

    # Construct Body
    $body = @{
        client_id    = $clientId
        scope        = $scope
        code         = $authCode[0]
        redirect_uri = $redirectUri
        grant_type   = "authorization_code"
        client_secret = $clientSecret
    }

    # Get OAuth 2.0 Token
    $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body

    # Access Token
    $2token = ($tokenRequest.Content | ConvertFrom-Json).access_token

}
else {

    Write-Error "Unable to obtain Auth Code!"

}
<##############################################################################################################

END CREDIT

###############################################################################################################>

# Base URL
$headers = @{Authorization = "Bearer $2token"}
$uri = "https://graph.microsoft.com/beta/"


Write-Host "Posting Messages in Affected Teams..." -ForegroundColor Yellow

ForEach ($team in $teamfilter){
         
         
        #Add IT Support Account to the Team as an Owner

         $userbody = @"
                    { 
                    "@odata.id": "https://graph.microsoft.com/beta/users/$($objID.id)" 
                    }
"@
        
        try{
            Invoke-WebRequest -Method POST -Uri "$($uri)groups/$($team.TeamID)/owners/`$ref" -Body $userbody -Headers $headers -ContentType $ctype -ErrorAction Stop
        }catch{

        }

        #send message in Team Channel
        
        try{
            Invoke-WebRequest -Method POST -Uri "$($uri)teams/$($team.TeamID)/channels/$($team.ChannelID)/messages" -ContentType $ctype -Headers $headers -Body $body -ErrorAction Stop
        }catch{

        }

        
       
        #Remove IT Support UPN from Team

        try{
            Invoke-WebRequest -Method Delete -Uri "$($uri)groups/$($team.TeamID)/owners/$($objID.id)/`$ref" -Headers $headers -ContentType $ctype -body $userbody -ErrorAction Stop
        }catch{

        }

        

       
}

Write-Host "Cleaned Up Team Membership" -ForegroundColor Yellow

$report = Read-Host "Do you want to export the results to a CSV (y/n)"

if ($report -ieq "y"){

    $results | Export-Csv -Path C:\Temp\Teamsreport.csv -NoTypeInformation -Force

}

Write-Host "Finished" -ForegroundColor Green

The script can be downloaded here as well

Migrating to Microsoft Teams from Hosted Skype

In this article, I wanted to discuss how you could move to Microsoft Teams if you’re currently in a Hosted Skype for Business environment. Depending on your contract with your provider, you could be using dedicated compute and software instance for your Skype workload, or you could be using a multi-tenanted version of Lync 2013. Obviously, your existing hosting provider should and probably can help you on your journey to Microsoft Teams in either of these environments. However, what if you’re using Microsoft Teams as the driver to exit a contract or service altogther? Can it be done? And what are the penalities for doing so?

At first breath, you may be thinking that this challenge is a bridge to far and that you feel locked into a model that is no longer suitable for your organization to grow. However, the journey to Microsoft Teams can be simpler and less painful than you have initially envisioned.

First, talk to your hosting provider about their capabilities in moving you to a Microsoft Teams solution that is suitable to support your needs. This is often the path of least resistance. However, this may limit you to a service contract that you’re not entirely happy with and want to consider your options.

Your organization should own your Office 365 tenant (assuming you have one). You shouldn’t rent this from anyone other than Microsoft and you should have full control over its evolution. If you do not have an Office 365 tenant, then sign up for one.

Obviously there are many different scenarios you could find yourself in already that adds complexity to your move. However, as long as you’re not currently consuming Skype for Business Online as part of your hosted Skype Solution, you can read on. If you are, then this may not work as intended for you.

1. Tenant Preparation

You can do a lot of configuration in your tenant to support Teams without impacting your user’s Skype functionality. You can configure Identity and Access Management, Create Teams policies, Compliance settings etc. all without BAU being impacted.

2. Domain Registration

As Teams relies of UPN for chat and calling routing and not SIP, you can (with a caveat) register what is going to be your existing SIP domain with your tenant without impacting Skype. The caveat is, that you shouldn’t enable the Domain for Skype for Business Use. If you do then any federated partners you currently communicate with who have Skype for Business Online will not be able to reach you via Skype.

3. Configure Direct Routing

Registering your domain allows you to configure Direct Routing in advance of your move to Teams, without Skype for Business hybrid being set up. If you are concerned about your Domain registration, then use a sub-domain as a registered domain for your SBC configuration, and address the main domain further down the line.

Assuming you have made it this far, you can create all your calling policies for your users, test out calling scenarios and make sure that the solution is stable at your own pace.

4. Enabling Users for Teams

Although this is labelled as step 4, in reality you could do this alongside Step 3 as the two are not intrinsically linked. Enabling users for Teams for Chat, P2P AV, Meetings and Collaboration at this stage means users get to start using Teams whilst your hosted Skype service is unaffected for important workloads like Enterprise Voice. This gives you an advantage and head start with early adoption while you focus on the more complex voice element of your move.

An important consideration at this stage is users will need to continue to use Skype for their Voice and any federation communication with external partners.

Think of this stage as a Pseudo-Islands Mode, but your tenant is operating in Teams Only Mode.

5. Move Federation

When you are ready, you need to move federation away from Skype and to Teams. You can do this by enabling the Skype service on your Office 365 Domain and changing just the SRV record for sipfederationtls._tcp.domain.com to sipdir.online.lync.com in your public DNS. After propagation has completed, federated chat will move from Skype to Teams.

6. Move Enterprise Voice

You have two choices here, or may be a mix. New DDIs for Direct Routing or number porting. With the latter, initiate your port request if you need to your DR SIP service. Assign users their phone numbers in Teams in advance of the porting schedule so that when the port completes, calls will be delivered to Teams instantly causing very little disruption to services.

7. Say Goodbye to Your Hosting Provider

That’s it, you can now turn off your hosted Skype as your organization is now using Teams and you never had to tell them a thing!

This article is not chapter and verse, its about showing you a way to achieve something. Your mileage may vary depending on your complexity so please consider carefully before executing.

PSTN Survival With Microsoft Teams, Polycom VVX and Ribbon SBC

At EC19, Yealink announced a partnership with Ribbon to deliver PSTN survival with Microsoft Teams for their Teams Phones. I don’t have a Yealink, but I do have a few VVX’s lying around my home office, so I thought I would give it a shot and see if I can get this working on other handsets. Turns out I can and that means I do not have to invest in new hardware for this disaster event solution. Better still it works for Skype for Business On-Prem and Online as well as Microsoft Teams!

So, how does it work conceptually? Basically the VVX phone registers to both Skype for Business Online (Teams via SIP Gateway) and to the SBC at the same time. When registration fails to Skype for Business / Teams, the phone will failover the active registration to the SBC and become essentially a basic SIP phone for making and receiving calls.

In order to facilitate this functionality, there are a couple of per-requisites.

  • The SBC must have local SIP registrar licenses to cover the number of phones you want to have this capability
  • The VVX phones must be running UCS 5.8 onwards

To set this up I will say right now is not that easy, or scale-able. This solution is really meant for mission critical phones that must survive a failure and not every phone in your business. Why, will become apparent as you read on. But basically, you would provide this capability maybe for your senior execs, inbound sales / support teams and main office reception type scenarios.

First we need to configure a Cloud IP Phone Policy in the tenant. This is so we can disable the management of firmware by Office 365. The reason for this is we need UCS 5.8 or higher, and Microsoft will force a rollback to UCS 5.6 if managed by Office 365. As there is only the global policy, this would mean that all phones would be affected.

Set-CsIPPhonePolicy -Identity Global -EnableDeviceUpdate $False

Now update your VVX to UCS 5.8 either by Phone Web UI or on-prem provisioning server.

Before we touch the phone any further, we now need to set up the SBC to support this. Assuming you now have the required Local Registrar license installed.

First on the SBC go to SIP > Local Registrars and create one. I’ve called mine “Teams Fallback SIP”

Now from SIP > Local / Pass thru Auth tables create an auth table. I’ve called mine “Teams Local Fallback”.

In this auth table, create an account for the phone that you want to survive. Note it is important that the address URI is the same as the DDI assigned to the user in Teams. The username and password can be anything. But for simplicity sake, the username is the DDI and I’ve set the password to 12345

Now we have the account set up, we need to catch registrations, so we need to create a signalling group. Under signalling groups create a SIP SG, I’ve called mine “Teams Fallback Phone”

The settings of the SG should be:

  • Call Routing Table (select any for now – we will come back to this)
  • SIP Profile: Default
  • SIP Mode: Local Registrar
  • Registrar: Teams Fallback SIP
  • Media List: Default
  • Listen Ports: 5060 TCP/UDP
  • Federated IP: <your network range>

Now create a call route table to handle outbound calls from phones when they are in a fallback mode.

Go back to the Signalling Group and set the call routing table to this and apply the settings.

Now we have the bones of the configuration we need. Now to configure the outbound call route. From the routing table we created, add a route to your ITSP. You can use the same transformation tables created for your Teams -> ITSP if it is compatible

This is now outbound calling configured. Now let’s configure inbound.

For inbound we need to ensure that the fallback route is only tried if the primary (via Microsoft Teams Direct Routing) is unresponsive. There are a few ways in which to achieve this, but I am going with the simple way. Rather than using cause code re-routes, I am simply going to add my fallback signalling group to the existing ITSP -> Teams route entry as a second possible destination for calls.

The way that this works is destination SGs are attempted on a first to last basis. As the Teams SG will almost always be up, the calls will always route via that. In an outage, which is what we want, it will not be available so the 2nd SG is tried and this is to our local SIP registrar table.

The SBC configuration is now complete. Now we need to configure the VVX phone.

You will need to add the following configuration to your phone’s cfg file

feature.sfbPstnFailover.enabled="1"
reg.1.srtp.simplifiedBestEffort="1"
reg.1.server.2.address="192.168.1.252"
reg.1.server.2.pstnServerAuth.userId="+441782977074"
reg.1.server.2.pstnServerAuth.password="12345"
call.enableOnNotRegistered="1"

Where 192.168.1.252 is the IP address of your SBC, the userId is the sip user we created in the local auth table and it’s password.

Now the solution is ready the last note of interest is the experience.

By default, phones register to Office 365 for a period of 10 minutes. usually the phone re-registers when the time gets to 50% or 5 minutes. The phone is a single line, therefore, can only have one active registration for calls at any one time.

During a failure event, there may be a period of 10 minutes where no calling is possible until the registration with Office 365 times out, the phone will then automatically mark the backup registration active. Calls during this time inbound will receive a busy tone, and the message back from the phone will be a SIP 486 “Busy Here” message.

Once the phone realises that it can no longer register to Office 365 calls will proceed as normal, but the phone will be in a basic mode, which is nothing more than a landline type service.

As you can see, the solution would be quite hard to scale beyond the few critical phones you need and it is quite limited, but its giving your critical users something rather than nothing in a time where you need to be focused on restoring a service, not providing an ad-hoc workaround on a case by case basis.

%d bloggers like this: