Skip to content

Commit

Permalink
Add post: Document your ThinkTel DIDs
Browse files Browse the repository at this point in the history
  • Loading branch information
pvaillant committed Jun 5, 2015
1 parent 102e0e1 commit c703dd4
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 0 deletions.
143 changes: 143 additions & 0 deletions _posts/2015-06-05-document-your-thinktel-dids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
layout: post
title: "Document Your ThinkTel DIDs"
comments: true
tags: ["SkypeForBusiness","PowerShell","ThinkTel","uControl"]
---

Documentation is important. It's key to being able to work together in teams and not have to rely on information based informally from person to person. I've talked before about how to [document your PowerShell scripts](/2015/05/01/document-your-powershell-scripts.html) but did you know you can also label each DID on your ThinkTel SIP trunk? This lets you record who or what the DID is for and can be very useful since it is shown in CDR reports, letting you know who originated or received individual calls. This can also be a big step towards not needing to document your phone number assignments in a separate Excel spreadsheet.

While the script below is specifically for Microsoft Lync or Skype for Business, it could easily be adapted for any system where the information on the allocated DID and the assigned user was available using PowerShell. This could be a backend database, a directory of some kind or web service API.

## Get uControl Labels

Using previously shown PowerShell (see [Keeping Lync Unassigned Numbers Updated](/2015/04/10/keeping-lync-unassigned-numbers-updated.html)), we start by downloading all the DIDs and their labels from uControl.

<pre class="hljs powershell"><code>
$sipTrunkDidsUrl = "https://api.thinktel.ca/REST.svc/SipTrunks/{0}/Dids?PageFrom=0&PageSize=100000" -f $SipPilotNumber
$didsList = Invoke-RestMethod -URI $sipTrunkDidsUrl -Credential $Credential -Method GET -ContentType "text/xml"
if(-not $didsList -or -not $didsList.ArrayOfTerseNumber) {
Write-Error "Failed to load or parse DIDs on SIP Pilot $SipPilotNumber"
exit
}

$existingLabels = @{}
$didsList.ArrayOfTerseNumber.TerseNumber | foreach {
$existingLabels.Add($\_.Number, $\_.Label)
}

</code></pre>

One note, and you'll see this below too, I'm using Invoke-RestMethod here so this will need to be run on a machine with PowerShell 3.0+ since that's when this cmdlet was introduced. This was released with Windows 2012 R1 so hopefully you have at least that. If you did have Windows 2008 R2 for some reason, my sympathies, but you could use System.Net.WebClient instead. If anyone has this situation, and is interested in a version of this script for PowerShell 2.0, let me know.

## Get Labels From Skype for Business

Next step, again using previously shown PowerShell (see [Managing Your Lync Phone Numbers](/2015/03/18/managing-your-lync-phone-numbers.html)), we get a list of all numbers assigned and a label to represent to what they are assigned to.

<pre class="hljs powershell"><code>
$currentLabels = @()
function AddCurrentLabel($telUri, $label) {
if($telUri -notmatch '^tel:+1') {
Write-Warning "Skipping $telUri ($label) because it doesn't start with tel:+1"
} elseif($telUri -match ';ext=') {
Write-Warning "Skipping $telUri ($label) because it contains an extension"
} else {
# $telUri will start with tel:+1 so let's skip that
$number = $telUri.Substring(6)
$currentLabels.Add($number, $label)
}
}
Get-CsUser -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineURI, "User " + $\_.SipAddress)
}
Get-CsUser -Filter {PrivateLine -ne $Null} | foreach {
AddCurrentLabel($\_.PrivateLine, "User " + $\_.SipAddress + " (private line)")
}
Get-CsAnalogDevice -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineURI, "Analog Device " + $\_.DisplayName)
}
Get-CsCommonAreaPhone -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineURI, "Common Area Phone " + $\_.DisplayName)
}
Get-CsRgsWorkflow | ?{ $\_.LineURI } | foreach {
AddCurrentLabel($\_.LineURI, "RGS Workflow " + $\_.PrimaryAddress)
}
Get-CsDialInConferencingAccessNumber -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineURI, "Dial In Conf Number " + $\_.DisplayName)
}
Get-CsExUmContact -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineURI, "ExUmContact " + $\_.DisplayName)
}
Get-CsTrustedApplicationEndpoint -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineUri, "Trusted App Endpoint " + $\_.SipAddress)
}
Get-CsMeetingRoom -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($\_.LineURI, "Meeting Room " + $\_.SipAddress)
}

</code></pre>

For users, the choice of label is obvious (name and SIP address), and for other objects there is generally a description or display name available. In a worse case scenario, I find it sufficient to just note the kind of usage.

## Find Updates

Then we compare the two hashes:
* We check for any labels that are only in the current set; those are ones that don't exist on the SIP trunk since at worse an empty label would be returned for each DID,
* We check for any labels that are only in the existing set; those are old ones that don't exist in Skype for Business,
* We check all the labels in both to see if they match, other wise we update it

<pre class="hljs powershell"><code>
$newLabels = @{}
foreach($num in $currentLabels.Keys) {
if($existingLabels.ContainsKey($num)) {
# this is a valid number on the trunk
if($currentLabels[$num] -ne $existingLabels[$num]) {
# the label needs to be updated
$newLabels.Add($num, $currentLabels[$num])
}
} else {
# this is not a number on the trunk
Write-Warning "Skipping $num since it isn't on the trunk"
}
}

</code></pre>

## PUT data back to uControl

Last, but not least, we then send this data back to uControl.

<pre class="hljs powershell"><code>
foreach($num in $newLabels.Keys) {
$label = $newLabels[$num]
Write-Verbose "Updating $num ($label)"

# we actually need to start by getting the current number information
# this ensures we don't clear out an existing number translation
$sipTrunkDidUrl = "https://api.thinktel.ca/REST.svc/SipTrunks/{0}/Dids/{1}" -f $SipPilotNumber,$num
$d = Invoke-RestMethod -URI $sipTrunkDidUrl -Credential $Credential -Method GET -ContentType "application/json"
if(-not $d -or $d.Number -ne $num) {
Write-Error "Failed to load information for DID $num"
continue
}

# update the DID label
$d.Label = $label

# now POST this back to $sipTrunkDidUrl
if($PSCmdlet.ShouldProcess($num,"Update DID label")) {
Invoke-RestMethod -URI $sipTrunkDidUrl -Credential $Credential -Method "POST" -ContentType "application/json" -Body $d
}
}

</code></pre>

You might wonder why I'm downloading each DID instead of just building it the response in memory. The initial GET query we did for all the DIDs doesn't have all the information for the DID. So features like number translation would be over-written if we didn't include it in the PUT body. You can see the difference since the initial GET query doesn't return an ArrayOfDid, instead it's an ArrayOfTerseNumber, with TerseNumber only having Number and Label instead of all the DID information.

Note too the [$PSCmdlet.ShouldProcess](https://msdn.microsoft.com/en-us/library/ms568271%28v=vs.85%29.aspx) used above. That, plus [[CmdletBinding(SupportShouldProcess=$true)]](https://msdn.microsoft.com/en-us/library/system.management.automation.cmdletbindingattribute%28v=vs.85%29.aspx) above param() is what makes -WhatIf work so that you can see what changes would be made before actually committing to making the changes.

And that's all there is to it. Now, if you set this up as a scheduled task, your current number assignments will be reflected automatically into the uControl web portal.

## Download the Script

<a class="download" href="/content/Update-ThinkTelDidLabels.ps1"><i class="fa fa-file-text-o"></i> Update-ThinkTelDidLabels.ps1 <i class="fa fa-download"></i></a>
130 changes: 130 additions & 0 deletions content/Update-ThinkTelDidLabels.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<#
.SYNOPSIS
Keeps uControl DID labels up to date with their assigned usage in Lync/Skype for Business
.PARAMETER SipPilotNumber
10 digit SIP Trunk pilot number
.PARAMETER Credential
PSCredential object (like returned from Get-Credential) for the SipPilotNumber
.EXAMPLE
Update-ThinkTelDidLabels.ps1 -SipPilotNumber 7005551212
This will prompt for credentials for the SIP trunk, connect and update all DID labels.
.NOTES
Version 1.0.0 (2015-05-19)
Written by Paul Vaillant
.LINK
http://paul.vaillant.ca/help/Update-ThinkTelDidLabels.html
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true)][ValidateRange(1000000000,9999999999)][long]$SipPilotNumber,
[Parameter()][System.Management.Automation.PSCredential]$Credential = $(Get-Credential)
)

###############################################################################
## Step 1: get the DIDs from uControl
###############################################################################

$sipTrunkDidsUrl = "https://api.thinktel.ca/REST.svc/SipTrunks/{0}/Dids?PageFrom=0&PageSize=100000" -f $SipPilotNumber
$didsList = Invoke-RestMethod -URI $sipTrunkDidsUrl -Credential $Credential -Method GET -ContentType "text/xml"
if(-not $didsList -or -not $didsList.ArrayOfTerseNumber) {
Write-Error "Failed to load or parse DIDs on SIP Pilot $SipPilotNumber"
exit
}

$existingLabels = @{}
$didsList.ArrayOfTerseNumber.TerseNumber | foreach {
$existingLabels.Add($_.Number, $_.Label)
}

###############################################################################
## Step 2: get assigned usage from Lync/Skype for Business
###############################################################################

$currentLabels = @()
function AddCurrentLabel($telUri, $label) {
if($telUri -notmatch '^tel:+1') {
Write-Warning "Skipping $telUri ($label) because it doesn't start with tel:+1"
} elseif($telUri -match ';ext=') {
Write-Warning "Skipping $telUri ($label) because it contains an extension"
} else {
# $telUri will start with tel:+1 so let's skip that
$number = $telUri.Substring(6)
$currentLabels.Add($number, $label)
}
}
Get-CsUser -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineURI, "User " + $_.SipAddress)
}
Get-CsUser -Filter {PrivateLine -ne $Null} | foreach {
AddCurrentLabel($_.PrivateLine, "User " + $_.SipAddress + " (private line)")
}
Get-CsAnalogDevice -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineURI, "Analog Device " + $_.DisplayName)
}
Get-CsCommonAreaPhone -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineURI, "Common Area Phone " + $_.DisplayName)
}
Get-CsRgsWorkflow | ?{ $_.LineURI } | foreach {
AddCurrentLabel($_.LineURI, "RGS Workflow " + $_.PrimaryAddress)
}
Get-CsDialInConferencingAccessNumber -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineURI, "Dial In Conf Number " + $_.DisplayName)
}
Get-CsExUmContact -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineURI, "ExUmContact " + $_.DisplayName)
}
Get-CsTrustedApplicationEndpoint -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineUri, "Trusted App Endpoint " + $_.SipAddress)
}
Get-CsMeetingRoom -Filter {LineURI -ne $Null} | foreach {
AddCurrentLabel($_.LineURI, "Meeting Room " + $_.SipAddress)
}

###############################################################################
## Step 3: figure out which labels need to be updated
###############################################################################

$newLabels = @{}
foreach($num in $currentLabels.Keys) {
if($existingLabels.ContainsKey($num)) {
# this is a valid number on the trunk
if($currentLabels[$num] -ne $existingLabels[$num]) {
# the label needs to be updated
$newLabels.Add($num, $currentLabels[$num])
}
} else {
# this is not a number on the trunk
Write-Warning "Skipping $num since it isn't on the trunk"
}
}

###############################################################################
## Step 4: update each label that needs to be updated
###############################################################################

foreach($num in $newLabels.Keys) {
$label = $newLabels[$num]
Write-Verbose "Updating $num ($label)"

# we actually need to start by getting the current number information
# this ensures we don't clear out an existing number translation
$sipTrunkDidUrl = "https://api.thinktel.ca/REST.svc/SipTrunks/{0}/Dids/{1}" -f $SipPilotNumber,$num
$d = Invoke-RestMethod -URI $sipTrunkDidUrl -Credential $Credential -Method GET -ContentType "text/xml"
if(-not $d -or $d.Number -ne $num) {
Write-Error "Failed to load information for DID $num"
continue
}

# update the DID label
$d.Did.Label = $label

# now POST this back to $sipTrunkDidUrl
if($PSCmdlet.ShouldProcess($num,"Update DID label")) {
Invoke-RestMethod -URI $sipTrunkDidUrl -Credential $Credential -Method "PUT" -ContentType "text/xml" -Body $d
}
}
32 changes: 32 additions & 0 deletions help/Update-ThinkTelDidLabels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title : Get-Help Update-ThinkTelDidLabels
layout : layout
---

# Update-ThinkTelDidLabels
Keeps uControl DID labels up to date with their assigned usage in Lync/Skype for Business

## Syntax
<code>Update-ThinkTelDidLabels.ps1 [-SipPilotNumber] &lt;Int64&gt; [[-Credential] &lt;PSCredential&gt;] [-WhatIf] [-Confirm] [&lt;CommonParameters&gt;]</code>

## Parameters
<table class="table table-condensed table-striped">
<thead><tr><th>Name</th><th>Description</th><th>Required?</th><th>Pipeline Input?</th><th>Default Value</th></tr></thead>
<tbody>
<tr valign="top"><td>SipPilotNumber</td><td>10 digit SIP Trunk pilot number</td><td>true</td><td>false</td><td>0</td></tr>
<tr valign="top"><td>Credential</td><td>PSCredential object (like returned from Get-Credential) for the SipPilotNumber</td><td>false</td><td>false</td><td>$(Get-Credential)</td></tr>
<tr valign="top"><td>WhatIf</td><td></td><td>false</td><td>false</td><td></td></tr>
<tr valign="top"><td>Confirm</td><td></td><td>false</td><td>false</td><td></td></tr>
</tbody></table>

## Notes
Version 1.0.0 (2015-05-19)<br/>
Written by Paul Vaillant

## Examples

### EXAMPLE 1
<code>Update-ThinkTelDidLabels.ps1 -SipPilotNumber 7005551212</code>

This will prompt for credentials for the SIP trunk, connect and update all DID labels.

1 change: 1 addition & 0 deletions help/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ layout : layout
* [Update-LyncExtensionDialing](Update-LyncExtensionDialing.html)
* [Update-LyncFederatedDomains](Update-LyncFederatedDomains.html)
* [Update-LyncUnassignedNumbers](Update-LyncUnassignedNumbers.html)
* [Update-ThinkTelDidLabels](Update-ThinkTelDidLabels.html)
1 change: 1 addition & 0 deletions scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ And continuous integration testing via AppVeyor: [![Build status](https://ci.app
* [Update-LyncExtensionDialing.ps1](/content/Update-LyncExtensionDialing.ps1) (see [Managing Extensions in Lync Dial Plans](/2015/04/17/managing-extensions-in-lync-dial-plans.html))
* [Update-LyncFederatedDomains.ps1](/content/Update-LyncFederatedDomains.ps1) (see [Keeping Lync Federated Domains Up To Date](/2015/03/13/keeping-lync-federated-domains-up-to-date.html))
* [Update-LyncUnassignedNumbers.ps1](/content/Update-LyncUnassignedNumbers.ps1) (see [Keeping Lync Unassigned Numbers Updated](/2015/04/10/keeping-lync-unassigned-numbers-updated.html))
* [Update-ThinkTelDidLabels.ps1](/content/Update-ThinkTelDidLabels.ps1) (see [Document Your Thinktel DIDs](/2015/06/05/document-your-thinktel-dids.html))

0 comments on commit c703dd4

Please sign in to comment.