Microsoft Lync has a rich set of .NET APIs which make it easy to extend the platform and integrate it with other applications. This blog helps explain how to use them.

Removing the plus sign on incoming PSTN calls

Posted: January 11th, 2013 | Author: | Filed under: MSPL | Tags: , , , | 1 Comment »

When calls come in to Lync from the PSTN, Lync normalizes the called phone number by applying the normalization rules from the global dial plan. However, if the number starts with a plus sign (+) when it arrives from the gateway, Lync assumes it is already correctly normalized in E.164 format and skips the normalization step. This means that there’s no way to apply further rules to the called number when it is already formatted with a plus sign by the gateway.

As it often does, the Lync Server SDK comes to the rescue here with a slightly complicated but functional way to remove that plus sign and get Lync to apply the normalization rules to those calls. With the Lync Server SDK, you can intercept the INVITE messages representing calls from the PSTN using a script, and then modify the INVITE messages in a small filter application to remove the plus sign.

Since this example has a few different pieces, I put some sample code on CodePlex at https://lync2010plusremover.codeplex.com/. This is non-production sample code and should not be used in a live environment, but feel free to repurpose it in your own solution if you like.

For more background on the Lync Server SDK, which this sample uses, check out my previous post on modifying SIP headers using the Managed SIP Application API. That post also has instructions for installed a managed SIP application like this one.

The first piece of the application is a manifest file with a short script:

<?xml version="1.0"?>
<r:applicationManifest
r:appUri="http://mspl.greenl.ee/PlusRemover"
xmlns:r="http://schemas.microsoft.com/lcs/2006/05">
 <r:allowRegistrationBeforeUserServices action="true" />

 <!--
 - Handle all INVITES without a strict route.
 -->
 <r:requestFilter methodNames="INVITE"
                  strictRoute="false"
                  domainSupported="false" />

 <!--
 - Handle none of the responses.
 -->
 <r:responseFilter reasonCodes="NONE"/>

 <!-- Dispatch all INVITEs from the Mediation Server where the To or From header contains a plus sign.
 -->
 <r:splScript>
   <![CDATA[
if (sipRequest && (ContainsString(sipRequest.From, "+", true) || ContainsString(sipRequest.To, "+", true)))
{
   foreach (ua in GetHeaderValues("User-Agent"))
   {
       if (!ContainsString(ua, "Mediation", true))
       {
           return;
       }
   }

   Dispatch("OnRequest");
}

]]>
 </r:splScript>

The purpose of the script is to intercept any INVITE messages coming from the Mediation Server where the From or To header contains a plus sign. It first looks at the From and To headers and checks whether they contain the + character using the ContainsString function. It then looks as the User-Agent header to see if the message was sent by the Mediation Server, which has a user agent string containing the word “Mediation.” Any of these messages that it finds, it dispatches to the OnRequest method in the managed code part of the application using the Dispatch function.

The managed code piece is (in this case) a console app that runs on the Front End Server. When the console app starts up, it waits for messages to be dispatched to it by the script. When a message arrives, it sends it to the designated method in the PlusRemover class for processing. Here is the PlusRemover class that handles the dispatched messages:

using System;
using Microsoft.Rtc.Sip;

namespace PlusRemover
{
    public class PlusRemover
    {
        private bool _modifyFromHeader = false;
        private bool _modifyToHeader = false;

        public PlusRemover(bool modifyFromHeader, bool modifyToHeader)
        {
            _modifyFromHeader = modifyFromHeader;
            _modifyToHeader = modifyToHeader;
        }

        public void OnRequest(object sender,
            RequestReceivedEventArgs e)
        {
            // Enable simple proxy mode and disable forking, for better performance.
            e.Request.SimpleProxy = true;
            e.ServerTransaction.EnableForking = false;

            Console.WriteLine("Received dispatched INVITE.");

            if (_modifyFromHeader)
            {
                // Grab the From header, and strip the initial plus.
                Header fromHeader = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.From);
                fromHeader.Value = fromHeader.Value.Replace("tel:+", "tel:").Replace("sip:+", "sip:");

                Console.WriteLine("New From header: {0}", fromHeader.Value);
            }
            if (_modifyToHeader)
            {
                // Grab the To header, and strip the initial plus.
                Header toHeader = e.Request.AllHeaders.FindFirst(Header.StandardHeaderType.To);
                toHeader.Value = toHeader.Value.Replace("tel:+", "tel:").Replace("sip:+", "sip:");

                // Also strip the initial plus from the request URI.
                e.Request.Retarget(e.Request.RequestUri.Replace("tel:+", "tel:").Replace("sip:+", "sip:"));

                // Add in a phone context to the To URI and request URI so that normalization is performed.
                if (!toHeader.Value.Contains("phone-context"))
                {
                    toHeader.Value = toHeader.Value.Replace("@", ";phone-context=DefaultProfile@");
                }
                if (!e.Request.RequestUri.Contains("phone-context"))
                {
                    e.Request.Retarget(e.Request.RequestUri.Replace("@", ";phone-context=DefaultProfile@"));
                }

                Console.WriteLine("New To header and request URI: {0}", toHeader.Value);
            }

            // Send the request along.
            e.ServerTransaction.CreateBranch().SendRequest(
                e.Request);
        }
    }
}

As you can see, it’s set up to strip plus signs from the From header, the To header, or both, depending on configuration. If it strips the plus sign from the To header, it also removes it from the request URI.

It also adds ;phone-context=DefaultProfile to both the To header and the request URI. This is necessary in order for Lync to use the default dial plan to normalize the number.

Once these modifications are done, it sends the message along to the (possibly modified) request URI by calling e.ServerTransaction.CreateBranch().SendRequest(e.Request).

Since this is a console application, there’s also a Program.cs to handle startup:

using System;
using System.Configuration;
using System.Threading;
using Microsoft.Rtc.Sip;

namespace PlusRemover
{
    class Program
    {
        static void Main(string[] args)
        {
            bool modifyFromHeader = false;
            bool modifyToHeader = false;

            // Get settings from the App.config.
            modifyFromHeader = bool.Parse(ConfigurationManager.AppSettings["modifyFromHeader"]);
            modifyToHeader = bool.Parse(ConfigurationManager.AppSettings["modifyToHeader"]);

            PlusRemover serverApplication = new PlusRemover(modifyFromHeader, modifyToHeader);

            try
            {
                // Try to connect to the FE server several times.
                ServerAgent.WaitForServerAvailable(10);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            // Load the app manifest from the current directory where the application is running.
            Environment.CurrentDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
            ApplicationManifest manifest = ApplicationManifest.CreateFromFile("PlusRemover.am");

            try
            {
                // Try to compile the manifest.
                manifest.Compile();
            }
            catch (CompilerErrorException ex)
            {
                Console.WriteLine(ex);
            }

            ServerAgent agent = null;
            try
            {
                // Set the PlusRemover object as the handler for messages.
                agent = new ServerAgent(serverApplication, manifest);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            if (agent != null)
            {
                Console.WriteLine("Plus remover has started successfully.");
                while (true)
                {
                    // Handle messages as they arrive.
                    agent.WaitHandle.WaitOne();
                    ThreadPool.QueueUserWorkItem(new WaitCallback(agent.ProcessEvent));
                }
            }
            else
            {
                Console.WriteLine("Failed to start.");
            }
        }
    }
}

Finally, there’s a very small App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- Note: These settings are only read at application startup. -->
    <add key="modifyFromHeader" value="false" />
    <add key="modifyToHeader" value="true"/>
  </appSettings>
</configuration>

If you install this application using the New-CsServerApplication commandlet in Lync Server Management Shell, and run it on all the Front End Servers in your lab environment, you will see Lync start to remove the plus signs from called numbers from the PSTN and apply its own normalization rules from the default dial plan.

Let me know if you have questions, and feel free to tinker with and improve the code in the CodePlex project!

Thanks to Ken Lasko for the original question.


One Comment on “Removing the plus sign on incoming PSTN calls”

  1. 1 i3laze said at 6:36 am on December 16th, 2013:

    My RCC solution reports all calls, including internals, in +1234 (E.164-format)

    I made your PlusRemover working fine (successfully registered in Event Log) and modified PlusRemover.exe.config to , yet I can’t remove + sign from RemoteCallControl flow.
    Would you be honest to make some advices how to modify your script to catch RCC requests also.
    Seems RCC works directly Lync ClientRCC URI server.


Leave a Reply

  • Note: Comment moderation is in use because of excessive spam. Your comment may not appear immediately.

  •