Sometimes provisioning users into Office 365 services requires custom settings to be executed with PowerShell. This can present a problem when the teams responsible for managing the ongoing process have varying levels of understanding. How do you provide a front end user interface for my custom code without the need for the operators to need or know PowerShell?
This is the case for Microsoft Teams. Microsoft Phone System ‘Direct Routing’ feature lets you connect your telephony gateway (SBC) to Microsoft Phone System. With this capability you can configure on-premises telephone numbers with Microsoft Teams client. A subtle difference using Direct Routing for your PSTN connectivity over Microsoft Calling (Telstra Calling in AU) is the inability to assign phone numbers to users via the Teams Admin Portal. The only way to assign the phone number is through a PowerShell cmdlet with parameter ‘OnPremLineURI‘:
Set-CsUser -Identity $UPN -EnterpriseVoiceEnabled $true -HostedVoiceMail $true -OnPremLineURI $lineURI
So here in lies my problem. Let’s fix it.
Components
- Microsoft Forms – The front end UI with required input fields.
- Logic App – The glue and manages the process.
- Azure Runbook – where my code lives to perform the steps against Office 365 API’s.
Microsoft Forms
This is a pretty basic form. I just need enough information as inputs to execute my PowerShell. The great thing about Microsoft Forms is that it has to be authenticated, the fact that it’s built into Office 365 is that it’s all done by Azure Active Directory.
Note: Unfortunately the simplicity of this form is also its short coming. I would love if we can do some form validation on the input string before it was submitted. Especially on the phone number format and length.
Create the Logic App
Open a new Blank Template in the Logic App Designer and search for Microsoft Forms and use the option ‘When a new response is submitted‘.
Start by getting the form data into the Logic App.
Assign all of the form inputs as variables in your Logic App to then be passed to our Runbook.
Azure Runbook
Create a Runbook, make sure you have defined the parameters (highlighted in lines 1-5). The Logic App will reference these automatically for you when working in the designer.
Note: All the settings we need are part of the Skype for Business PowerShell module which isn’t available in the Azure Automation Gallery. If you install Microsoft Teams module version 1.1.6 you will have the ability to execute New-CsOnlineSession and pull down all the cmdlets into the PS session. At the time of writing I don’t know a way of using a managed identity or client secret for New-CSOnlineSession, so it’s just a standard user account with bypass MFA (yuck).
Param ( [Parameter (Mandatory = $true)][string]$upn, [Parameter (Mandatory = $true)][string]$lineURI, [Parameter (Mandatory = $true)][string]$dialPlan ) $debug = $false import-module MicrosoftTeams if($debug -like $true){ Write-Output "Connecting to Skype Online..." } $creds = Get-AutomationPSCredential -Name "SkypeCreds" try{ $sfboSession = New-CsOnlineSession -Credential $creds -OverrideAdminDomain "domain.onmicrosoft.com" } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "Connecting to Skype Online" cmdlet = "New-CsOnlineSession" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output "Importing PS Session..." } try{ Import-PSSession $sfboSession -AllowClobber } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "Importing PS Session" cmdlet = "Import-PSSession" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output "Processing line: $($upn) " } #Correct User if($upn -like $null){ $sip = (Get-CsOnlineUser -Identity $($user.displayname)).SipAddress $upn = $sip.TrimStart('sip:') } #Correct Number if($lineURI -notlike "tel:*"){ if($lineURI.Length -eq 12){ $lineURI = "tel:"+$lineURI } elseif($lineURI.Length -eq 11){ $lineURI = "tel:+"+$lineURI } } if($debug -like $true){ Write-Output " INFO: Using values - $($upn) with $($lineURI)" Write-Output " INFO: Attempting to remove Skype for Business Online settings: VoiceRoutingPolicy" } try{ Grant-CsVoiceRoutingPolicy -PolicyName $NULL -Identity $upn } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "VoiceRoutingPolicy" cmdlet = "Grant-CsVoiceRoutingPolicy" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output " INFO: Attempting to remove Skype for Business Online settings: UserPstnSettings" } try{ Set-CsUserPstnSettings -Identity $upn -AllowInternationalCalls $false -HybridPSTNSite $null | out-null } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "UserPstnSettings" cmdlet = "Set-CsUserPstnSettings" } Write-Output ( $errOutput | ConvertTo-Json) exit } # https://docs.microsoft.com/en-us/powershell/module/skype/grant-csteamsupgradepolicy?view=skype-ps if($debug -like $true){ Write-Output " INFO: Attempting to grant Teams settings: user to UpgradeToTeams (TeamsOnly)." #Upgrades the user to Teams and prevents chat, calling, and meeting scheduling in Skype for Business } try{ Grant-CsTeamsUpgradePolicy -PolicyName UpgradeToTeams -Identity $upn } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "UpgradeToTeams" cmdlet = "Grant-CsTeamsUpgradePolicy" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output " INFO: Attempting to set Teams settings: Enabling Telephony Features & Configure Phone Number" } try{ Set-CsUser -Identity $UPN -EnterpriseVoiceEnabled $true -HostedVoiceMail $true -OnPremLineURI $lineURI } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "SetUser" cmdlet = "Set-CsUser" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output " INFO: Attempting to grant Teams settings: TeamsCallingPolicy" #Policies designate which users are able to use calling functionality within teams and determine the interoperability state with Skype for Business } try{ Grant-CsTeamsCallingPolicy -PolicyName Tag:AllowCalling -Identity $upn } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "TeamsCallingPolicy" cmdlet = "Grant-CsTeamsCallingPolicy" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output " INFO: Attempting to grant Teams settings: Assign the Online Voice Routing Policy" } try{ Grant-CsOnlineVoiceRoutingPolicy -Identity $upn -PolicyName Australia } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "VoiceRoutingPolicy" cmdlet = "Grant-CsOnlineVoiceRoutingPolicy" } Write-Output ( $errOutput | ConvertTo-Json) exit } if($debug -like $true){ Write-Output " INFO: Set Dial" } try{ if($dialPlan -eq "National"){ Grant-CsTenantDialPlan -PolicyName $null -Identity $upn }else{ Grant-CsTenantDialPlan -PolicyName $dialPlan -Identity $upn } } Catch{ $errOutput = [PSCustomObject]@{ status = "failed" error = $_.Exception.Message step = "DialPlan" cmdlet = "Get-CsEffectiveTenantDialPlan" } Write-Output ( $errOutput | ConvertTo-Json) exit } #Completion Output $errOutput = [PSCustomObject]@{ status = "Completed" error = "None" step = "endOfJob" cmdlet = "None" } Write-Output ( $errOutput | ConvertTo-Json)
Link the Runbook to your Logic App
Now we can update the Logic App with our Runbook information.
Output the details via Email
I found the best way to get consistent structured results is to have error handling in your Runbook, and parse this back to the Logic App as outputted JSON with a known schema/structure. A sample output of the JSON can be used to generate a schema, like the example below.
{ "status": "failed", "error": "One or more errors occurred.: Unable to find an entry point named \u0027GetPerAdapterInfo\u0027 in DLL \u0027iphlpapi.dll\u0027.", "step": "Connecting to Skype Online", "cmdlet": "New-CsOnlineSession" }
This enables you to have sufficient levels of diagnostics logs as part of the output. In this case I’m using a email.
The example workflow is below.
Additions
Additional functionality you could include might be:
- Check for licenses
- AAD Module in PowerShell, or
- AAD Group Membership in Logic App
- License the user via PowerShell or Graph
- Send the response in a Teams Notification, rather than email or teams channel.
- Email the user on successful completion detailing they have a new phone number.
- More error handling
- Smaller more specific Runbooks that are executed rather than a large script block, allowing for more conditions to considered per step.
Lets Talk Teams!
We have years of experience deploying unified communication in the Microsoft stack. Reach out, we have a rapid deployment solution for Teams Direct Routing leveraging the public cloud and we have tried and tested a number of flavours of SIP Providers. Trial or PoC a voice solution with minimal effort leveraging public cloud deployments