Microsoft Lync and Skype for Business have 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 those APIs.

Extending Lync Server routing with MSPL

Posted: July 8th, 2011 | Author: | Filed under: Lync Development, MSPL | Tags: , | 24 Comments »

Recently I’ve taken a number of questions about customizing the routing of messages in Lync Server, which is possible with the Lync Server 2010 SDK. Since most of the how-to material out there is about either UCMA or the Lync 2010 (client) SDK, I thought I would write up a few articles on the basics of what you can do with the Lync Server 2010 SDK, and particularly with Microsoft SIP Processing Language, a.k.a. MSPL.

I’ll start by explaining a bit about the SDK: what it is, what you can do with it, and how it differs from the other ones that are available for Lync.

At a high level, there are three elements to the Lync Server SDK:

  • Application manifest files
  • Microsoft SIP Processing Language (MSPL)
  • Managed SIP Application API

Application manifest files are XML files that give Lync Server details on an application it should load when starting up. They are stored on the server where Lync Server is running, and have a .am extension (short for “application manifest”).

Microsoft SIP Processing Language, or MSPL, is a scripting language that you can use to customize how Lync Server handles and routes SIP messages. Unlike UCMA applications, MSPL scripts run in the context of the Lync Server process itself, so they can intercept messages between users, filter messages, reroute messages, and so forth. Also, again unlike UCMA applications, they don’t run as a separate process, nor do they create endpoints of their own. Consequently, they aren’t limited to controlling messages to and from a single endpoint, like UCMA applications.

Here are a couple diagrams to illustrate the differences. First, here’s a UCMA application:

Diagram of a UCMA application

The application runs on a separate server from Lync Server, and creates an endpoint which communicates with other Lync endpoints via the Lync Front End Server. This means that it can participate in and control a conversation between, for instance, Endpoint 2 and itself, but it can’t do anything with a conversation between Endpoint 2 and Endpoint 3.

Diagram of an MSPL script

The MSPL script, by contrast, simply modifies the behavior of the Lync Server Front End. It has visibility to and control over every message that is proxied through the Front End Server, regardless of where it comes from or where it goes.

The MSPL code itself is included directly in the application manifest.

The concept is basically the same for the Managed SIP Application API. With this API, you can write managed code to manipulate SIP messages, instead of using MSPL. This opens up many more possibilities for things like data storage and complex message modifications, but there are many more performance and scalability issues to consider.

Let’s take a look at a very simple MSPL script. Now, since the MSPL script goes inside an application manifest, the first step is to create the manifest file, which we can call Simple.am.

The application manifest is an ordinary XML file. You can edit it in Visual Studio, or in another text editor; it doesn’t make much difference since there is no Intellisense available. At the very beginning of the manifest file, along with the xml declaration, is the root element, <lc:applicationManifest>. The only thing you need to change in here is the appUri attribute, which is basically a unique identifier for your application. You’ll need this later in order to install the application.

<?xml version="1.0" ?>
<lc:applicationManifest
    appUri="http://blog.greenl.ee/Application"
    xmlns:lc="http://schemas.microsoft.com/lcs/2006/05">

Next, we can specify what types of messages this application should handle, using the <lc:requestFilter> and <lc:responseFilter> elements. As you might have guessed, these control the types of requests and responses, respectively, that are passed through the application. Here’s an example of a <lc:requestFilter> element:

<lc:requestFilter methodNames="ALL" strictRoute="false"
    registrarGenerated="true" domainSupported="true"/ >

The methodNames attribute can have one of the following:

  • ALL
  • NONE
  • a list of SIP method names separated by commas

The strictRoute attribute, which defaults to false, controls whether the application sees requests for which explicit routing details have already been specified. If this is set to true, these messages that have a “strict” route will also go through the application.

There are a few other attributes that can go in this element, but I’ll skip them for now since they are not necessary for most situations.

The <lc:responseFilter> element works similarly:

<lc:responseFilter reasonCodes="ALL" />

The reasonCodes attribute holds a comma-delimited list of SIP response codes, or ALL, or NONE.

Another important setting is controlled by the <lc:proxyByDefault> element, which looks like this:

<lc:proxyByDefault action="false" />

If this is set to false, any messages that are not proxied, responded to, or otherwise handled by the application are simply dropped. If it’s true, messages that are not handled are proxied to their destination by Lync Server, just as they would were the application not installed.

Since this application will consist only of an MSPL script, and will not use any managed code, we can add the following element as well:

<lc:scriptOnly />

So here’s what the manifest looks like now, with those elements added:

<?xml version="1.0" ?>
<lc:applicationManifest
    appUri="http://blog.greenl.ee/Application"
    xmlns:lc="http://schemas.microsoft.com/lcs/2006/05">

<lc:requestFilter methodNames="ALL" strictRoute="false"
    registrarGenerated="true" domainSupported="true"/ >

<lc:responseFilter reasonCodes="ALL" />

<lc:proxyByDefault action="false" />

<lc:scriptOnly />

We have an application manifest, but now we need to add the script. The MSPL script is included in the manifest within the <lc:splScript> element, like so:

<lc:splScript>
    <![CDATA[ /* script goes here */ ]]>
</lc:splScript>

Besides lacking Intellisense and compile-time syntax checks (since the script is not compiled), MSPL is not that difficult to write. The syntax looks more or less like a pared-down version of C#. While I can’t possibly cover everything you can do with MSPL in this post, I’ll go over some of the basics, and leave the rest to future posts. You can also find the full documentation on MSDN at http://msdn.microsoft.com/en-us/library/gg425301.aspx.

To start, most scripts will need to access the message. The sipMessage variable provides access to a class that contains the message details. However, in many cases, the application’s behavior will depend on whether it is handling a request or a response; and the data available will depend on which type of message it is. The application can use something like the following to do this:

if (sipRequest) {
    /* do something */
}
if (sipResponse) {
    /* do something else */
}

It can then look at the properties on the request or response and take action appropriately. Here’s an example that blocks all messages from a certain SIP URI:

if (sipRequest) {
  if (ContainsString(sipRequest.From, "sip:disgruntled_employee@ourcompany.biz", true)) {
    Log("Debugr", false, "Blocked message to ", sipRequest.To);
    Respond(403, "Forbidden");
  }
}

What we’re doing here is looking at the From header on the SIP request, checking to see whether it contains a particular SIP header (the true in the third parameter tells the function to ignore case), and if there is a match, responding with a 403 Forbidden response. Because we have proxyByDefault set to true, any other request will simply be proxied by Lync Server as usual.

If we changed proxyByDefault to false, we’d need to add a bit of code:


if (sipRequest) {
    if (ContainsString(sipRequest.From, "sip:disgruntled_employee@ourcompany.biz", true)) {
        Log("Debugr", false, "Blocked message to ", sipRequest.To);
        Respond(403, "Forbidden");
    }
    else {
        ProxyRequest();
    }
}

ProxyRequest tells Lync to go ahead and proxy the request as it normally would. You can also (as long as the message does not have a strict route, as described above) add a string parameter to ProxyRequest, which tells Lync specifically where to send the message. One possible reason you might do this is if you want to send certain requests to a PBX, a UCMA application, or some other specific destination.

There is also a ProxyResponse method, which does the same thing with responses. It can’t take any parameters since the response always goes back to where the request came from.

The Log method allows you to write messages to the log while your script is running. The first parameter must be “Event”, “Error”, or “Debugr”. The r in Debugr is not a typo. The second parameter can be true (to log the entire message except the Content) or false to log only some basic details. Finally, you can specify as many string parameters as you want to add notes to the log message.

So here’s a complete, although very rudimentary, MSPL script in an application manifest:


<?xml version="1.0" ?>
<lc:applicationManifest
    appUri="http://blog.greenl.ee/Application"
    xmlns:lc="http://schemas.microsoft.com/lcs/2006/05">

<lc:requestFilter methodNames="ALL" strictRoute="false"
    registrarGenerated="true" domainSupported="true"/ >

<lc:responseFilter reasonCodes="ALL" />

<lc:proxyByDefault action="false" />

<lc:scriptOnly />






    <lc:splScript><![CDATA[ 
        if (sipRequest) { 
            if (ContainsString(sipRequest.From, "sip:disgruntled_employee@ourcompany.biz", true)) { 
                Log("Debugr", false, "Blocked message to ", sipRequest.To); 
                Respond(403, "Forbidden"); 
            } else { 
                ProxyRequest(); 
            } 
        } 
    ]]></lc:splScript>
</lc:applicationManifest>

So far, you’ve seen how MSPL works and how to create a very basic MSPL application. There is quite a lot more to learn about MSPL. In my next post, in a few days, I’ll explain how to install the script on Lync Server using the New-CsServerApplication PowerShell command (http://technet.microsoft.com/en-us/library/gg398096.aspx). Meanwhile, if you have specific questions, feel free to comment or send me an email!


24 Comments on “Extending Lync Server routing with MSPL”

  1. 1 Munir Husseini said at 4:32 am on December 15th, 2011:

    Dear Michael,

    Thank you for providing all this information on your blog – I’ve been reading it intensively the last days and it helped me a lot.
    I’ve got a question regarding filtering SIP messages, though. I’m trying to figure this out for days but had no luck until now. I really hope that you can give me a hint on what could be going on here.

    I created the following MSPL script:

    The Identity of the script is “Service:Registrar:…”

    With this script, I hoped to be able to log all SIP messages that pass the Lync Server. Now I have two scenarios of client operation I am interested in:

    1- A Lync client calls another Lync client
    2- A Lync client calls a PSTN phone via Enterprise Voice

    In scenario 1, I can see the INVITE message in the log produced by my script – but nothing more. I can’t see any OK, ACK, CANCEL or BYE.

    In scenario 2, I can’t even see the INVITE message in the log. I only see a PRACK coming from the Lync client.

    If I use the logging tool, I can see all the messages I was missing. So my question is: why are there messages I obviously can’t filter with MSPL ? Why can’t I see an INVITE coming from Lync clients that are establishing calls to the PSTN? What can I do to catch all messages (or at least all INVITE messages)?

    Any help would be appreciated. Thank you very much.

    Best regards,
    Munir

  2. 2 Munir Husseini said at 4:35 am on December 15th, 2011:

    Dear Michael,

    apparently, the comment function removed the XML tags from my comment. So here it is with some escaping:

    <lc:applicationManifest appUri=”http://myurl.de/test” xmlns:lc=”http://schemas.microsoft.com/lcs/2006/05″>
    <lc:requestFilter methodNames=”ALL”
    strictRoute=”true”
    registrarGenerated=”false”
    domainSupported=”true”/>
    <lc:responseFilter reasonCodes=”NONE”/>
    <lc:proxyByDefault action=”true”/>
    <lc:scriptOnly />

    <lc:splScript>
    <![CDATA[
    Log(“Event”, true, “MESSAGE”);
    ]]>
    </lc:splScript>
    </lc:applicationManifest>

    Best regards,
    Munir

  3. 3 Marcus said at 6:01 am on December 15th, 2011:

    Hello Michael

    Very nice post,

    I have a question to mspl!

    Is it possible to filter login messages and gain the ip adress of the user currently logging in? (or in an inderect way through the host name and then the dhcp server)

    I need an automation for user to change the agentgroup when they login from a specific subnet?!

    greetings

  4. 4 Michael said at 10:19 am on December 20th, 2011:

    Munir, you might need to adjust the “Priority” value when registering the MSPL script using New-CsServerApplication. Lower priority values mean the script will be run earlier relative to other scripts. It’s possible that something else is handling those messages before your script gets them.

    Here’s some detail on New-CsServerApplication: http://technet.microsoft.com/en-us/library/gg398096.aspx

  5. 5 Michael said at 10:21 am on December 20th, 2011:

    Marcus, in principle you should be able to check for REGISTER messages using an MSPL script and get the IP that way. Does that help or do you need more details?

  6. 6 Munir Husseini said at 4:09 am on December 22nd, 2011:

    Dear Michael,

    I followed your advice by setting the priority. The problem was that I only was able to intercept all needed messages when the priority was zero. Normally, setting the priority to a value below 4 is not allowed. To achieve this, I had to add the allowRegistrationBeforeUserServices element to my script :

    Now, I can “see” all messages I was expecting. Thank you very much.

    Best regards,
    Munir Husseini

  7. 7 Munir Husseini said at 4:11 am on December 22nd, 2011:

    Again, the comment swallowed my script:

    <lc:applicationManifest …>
    <lc:allowRegistrationBeforeUserServices=”true” />

    </lc:applicationManifest>

  8. 8 Michele said at 6:16 am on February 9th, 2012:

    I can catch sip message like SERVICE and Register but I can’t catch any SUBSCRIBE one.
    I see there are many server applications the script MSPL run through..USERSERVICEs,IIMFILTER,DEFAULTROUTING..

    Do you know how I should change priority of my server application in order to catch SUBSCRIBE SIP MESSAGE?

  9. 9 Michael said at 7:00 pm on February 9th, 2012:

    You might try changing the priority of your script to 0. Depending on what your script does, though, you might find that this messes up other Lync functionality, so it’s a good idea to do some testing afterwards.

    You can use the Set-CsServerApplication command in Lync Management Shell to change the priority – you’d need to do something like this:

    Set-CsServerApplication -Identity Service:Registrar:fe.domain.local/Script -Priority 0

    Let me know if that works for you.

  10. 10 Michele said at 5:49 am on April 17th, 2012:

    I set the priority to 0 and now I can catch subscribe message. I ask you help for another task related: what about benotify message? Is there any specific configuration to apply in order to catch benotify message through mspl script?

    Thanks in advance

  11. 11 Michael said at 8:09 am on April 17th, 2012:

    You’ll need to make sure that the methodNames attribute in your requestFilter element in the application manifest (the .am file) includes BENOTIFY or ALL.

  12. 12 Aaron said at 1:25 pm on June 6th, 2012:

    Hi Michael,

    Thanks for excellent post!

    Using a Managed SIP application, I noticed that the SIP CANCEL message is not an available method in the MSPL “requestFilter” and is not delivered even when the requestFilter is set to methodNames=”ALL”. Btw, CANCEL is listed in the StandardMethodType enum (code 13), however it is not listed as a valid option for the requestFilter methodName.

    Questions: Is there a Lync server 2010 SDK update available that allows the SIP CANCEL message to be detected? If not, is there another technique available to detect the SIP CANCEL message and it’s response? NOTE: The Lync logging applications do see the SIP CANCEL/response messages.

    Thank you,
    Aaron

  13. 13 Michael said at 11:50 am on June 13th, 2012:

    Hi Aaron,
    As far as I know there is no way to intercept or detect CANCEL messages using the Lync Server SDK. I don’t know the reason for this, but will let you know if I find out.
    Michael

  14. 14 Christian said at 4:02 pm on July 19th, 2012:

    Hey Michael, very nice post. I have implemented Call Blocking on Lync using the code from GotUC ( I will post the code below). The entire script works as planned with one exception. I am able to send 403 responses to any caller ids in the blocked callers text file. The call does not ring at my desk. My problem is that the call notification does get through to my UM server and emails me a notice that a call was missed. The Debug Message looks like this: ExumRouting SPL got new multipart audio INVITE request to sip:****@****.com;opaque=app:voicemail. I want to block the email notification as well. Can you take a look at the code and help me through it? Thanks in advance.

  15. 15 Michael said at 9:25 pm on July 23rd, 2012:

    I don’t see the code in your post – maybe it got stripped out? Also, what priority did you assign to the script when you set it up using New-CsServerApplication?

  16. 16 Tony A said at 2:33 pm on July 26th, 2012:

    Hi Mike, great post and thanks for the insight on MSPL. I’d like to start writing some scripts, but don’t know where to beging. What would a basic script look like to block user A from sending an IM to user B? It’s been over 10 years since I program and really am that raw 🙁

  17. 17 Michael said at 4:54 pm on August 17th, 2012:

    Hi Tony,

    If you install the Lync Server 2010 SDK, it comes with a bunch of sample MSPL scripts. The Filter sample does basically what you’re describing (block messages in certain cases).

    Michael

  18. 18 Travis said at 4:46 pm on August 17th, 2012:

    I have a use case where if a message is an IM, send request to server (A), but if voice/video send to server (B). Since both requests are going to come to user@domain.com, and I have currently static route from Lync for domain.com to server (A), is it possible to use MSPL to send voice / video to a different route? Is this defined in the script?

  19. 19 Michael said at 4:52 pm on August 17th, 2012:

    Yes, you can use MSPL to check the contents of the SDP body of the message, and look for the media type (m=message, m=audio, m=video, etc.). One of my other posts (http://blog.greenl.ee/2011/12/08/distinguishing-im-vs-audio-calls-mspl/) has more details on this.

    Once you’ve identified the media type, you can then use ProxyRequest to send the message to the appropriate server – e.g., ProxyRequest(server-a.domain.local)

    Hope this helps.

    Michael

  20. 20 Travis said at 4:35 pm on August 20th, 2012:

    Michael,

    Thank you for your quick response. Let me clarify. The messages I want to detect are generated from Lync to a non-microsoft destination. So a Lync client will either IM or CALL user@domain.com. So I am assuming I need to use a responsefilter vs a requestfilter. Also, I am not sure how the proxyrequest is going to change the server destination. I do not want to change the user@domain.com. I have a static route for domain.com to 10.1.1.1 but in the case of a voice call for instance, I want to keep the to:user@domain.com but not use the static route, but send to 10.1.1.2.

    It seems like the proxyrequest, I can just change the destination to user@new.domain.com

    Maybe your answer remains the same, I just wanted to clarify what I am trying to do.

  21. 21 Michael said at 6:06 pm on August 21st, 2012:

    Hi Travis,

    You should still be able to use the approach I described, and I think you’ll want a request filter, not a response filter. These are new calls/IMs you’re trying to detect, right?

    Calling ProxyRequest will not change the To header at all – it will only change where the message is sent as the next hop.

    Let me know if this answers your questions or if there’s anything else you have questions about.

    Michael

  22. 22 Repa said at 6:23 am on January 24th, 2013:

    Hi Michal,

    I want to have a script which is blocking some called numbers for some users. I have now something like this (should block everything for specific user):
    if (sipRequest)
    {
    if(IsAudioCall())
    {
    if(GetUri(sipRequest.From) == “sip:x.y@sadf.hu”)
    {
    Respond(“403”, “Forbidden”);
    }
    }
    }

    It looks in the API logger that the filter is executed and even there is a 403 message. But it looks Lync still executes applications with higher priority (Outbound Routing). How can I terminate the execution of higher priority Application?

  23. 23 Anthony said at 12:16 am on March 30th, 2015:

    Michael,
    I was wondering if you know if it would be possible to intercept a file transfer using MSPL and branch from one response to two?
    Thanks,
    Anthony

  24. 24 Marco said at 11:22 pm on May 6th, 2015:

    Greeting Michael, I need help in call forwarding un-wanted calls based on caller id to an specific number, the script will have to be able to read the unwanted number from a file that will be populated will a bunch of un-wanted number
    example 1:
    from sip:+17704444440 , to sip:+17864443330, forward to +17864443331

    example 2 :
    from sip:+17704444440 , to any,forward to +17864443331

    thank you


Leave a Reply

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

  •