Lync MSPL “Busy Here” Script Project

*****Before I begin I just wanted to give a shout out to Melding Technology for helping with this project. They basically took an unfinished script that a reader found out on the internet and fixed it to a working state. So thanks to Jay and Angela for working on the script.*******

image

I got a question from a reader about a MSPL script he had found on the web from this TechNet forum post.

http://social.technet.microsoft.com/Forums/eu/ocsvoice/thread/f5829e31-42bd-4a65-bf1a-4777c5c54770

Basically the script didn’t run but was supposed to enable busy on busy. With some help from Melding Technology here in Seattle this script is now functional but with some limitations. Apparently the original script was close to working but needed some small changes. Currently it doesn’t check if a user is enabled for Unified Messaging and just returns a busy signal if already on a call. I will post updates if  they enable this. This script does however check if a user is defined for this feature by looking up the user.txt file. So as long as you define the user in User.txt they will get busy on busy if they are on a call when a second call arrives. Also beware this script has not been tested in a large scale roll out. It has only been tested in a lab setting so use at your own risk.

The links below are access to both the script and user file from my Public Skydrive share. Please feel free to grab and use this. I have posted the full text of the script below to view before downloading the file.

Script

https://skydrive.live.com/redir.aspx?cid=25a5ce54e91be979&resid=25A5CE54E91BE979!712&parid=25A5CE54E91BE979!142 

User text file

https://skydrive.live.com/redir.aspx?cid=25a5ce54e91be979&resid=25A5CE54E91BE979!711&parid=25A5CE54E91BE979!142

<?xml version="1.0" ?>
<lc:applicationManifest
lc:appUri="http://www.meldingtechnology.com/busybusy"
xmlns:lc="http://schemas.microsoft.com/lcs/2006/05">
  <lc:allowRegistrationBeforeUserServices action="true" />
  <lc:requestFilter methodNames="INVITE"
                          strictRoute="true"
                          registrarGenerated="true"
                          domainSupported="true" />
  <lc:responseFilter reasonCodes="NONE" />
  <lc:proxyByDefault action="true" />
  <lc:scriptOnly />
  <!--
    -
    - users.txt
    - Users database file. The BusyBusy Filter will only be applied to the users listed in
    - this file.
    -
    -->
  <lc:file name="usersTable" path="users.txt" delimitedBy="whitespace" keyColumnName="uri">
      <!-- Filter applies to this URI. -->
      <lc:column name="uri" />
  </lc:file>

  <lc:splScript>
    <![CDATA[
    // Function that returns true if the given content has the SDP audio m lines
    // and false otherwise
    function contentHasSDPAudio(content) {
        // SDP format is strict enough that the following check is a valid
        // way to determine if the offer includes audio.
        if (ContainsString(content,"\nm=audio ", false) ||
            ContainsString(content,"\rm=audio ", false)) {
            //Log( "Debug", false, "***BusyBusy***: found m=audio" );
            return true;
        }
        return false;
    }

    //Log ("Event", false, "***BusyBusy***: started script");
    Log( "Debug", false, "***BusyBusy***: We have a request - ", sipRequest.Method );

    // Check if this is a re-INVITE and exit if so
    foreach ( sessionExpires in GetHeaderValues( "Session-Expires" ) ) {
      if ( ContainsString( sessionExpires, "refresher", true ) ) {
          Log( "Debugr", false, "***BusyBusy***: skipped; This is a session refreshing invite" );
          return;
      }
    }

//
// Get user@domain string for the user in the To: header.
//
toUri = GetUri(sipRequest.To);
toUserAtHost = Concatenate( GetUserName( toUri ), "@", GetHostName( toUri ) );

Log( "Debugr", false, "***BusyBusy***: toUserAtHost         - ", toUserAtHost );

//
// Look up the user in our user table
//
user= usersTable[toUserAtHost].uri;

// Comment this if block if you want to apply the filter to everybody
if(user == null)
{
    Log("Debug", false, "***BusyBusy***: ignoring filter for this user." );
    return;
}


if (sipRequest.StandardMethod == StandardMethod.Invite) {
    // Determine if this request is for an audio session
    hasBody = false;
    hasAudio = false;
    foreach (header in GetHeaderValues (StandardHeader.ContentType)) {
        // Found a content-type header, so we know it has a body.
        hasBody = true;
        if (IndexOfString (header, "multipart/", true) == 0) {
            //Log( "Debugr", false, "***BusyBusy***: Found multipart body. Content-Type:", header );
            i = 0;
            while (i<MultiPartItem.Count && BindMultiPartBodyItem(i)) {
                if (ContainsString(MultiPartItem.ContentType, "application/sdp", true)) {
                    //Log("Debugr", false, "***BusyBusy***: Found SDP content-type in Multipart item count: ", i);
                    if (contentHasSDPAudio(MultiPartItem.Content)) {
                        //Log("Debug", false, "***BusyBusy***: content has audio" );
                        hasAudio = true;
                        break;
                    }
                }
                i=i+1;
            }
        }
    }

    if ( hasAudio ) {
        Log("Debug", false, "***BusyBusy***: this is an audio call" );
    }
    else if (!hasBody) {
        // An INVITE without a body is taken as an implied audio INVITE.
        Log("Debug", false, "***BusyBusy***: content has no body, implied to be an audio call" );
    }
    else {
       // not an audio call retuern now
       Log("Debug", false, "***BusyBusy***: this is not an audio call!" );
       return;
    }
}


totalEndpoints = 0;
anyEndpointBusy = false;

foreach (dbEndpoint in QueryEndpoints(toUserAtHost)) {
   totalEndpoints = totalEndpoints + 1;
  
   Log( "Debugr", false, "***BusyBusy***: endpoint.EPID        - ", dbEndpoint.EPID );
   Log( "Debugr", false, "***BusyBusy***: endpoint.ContactInfo - ", dbEndpoint.ContactInfo );
   //Log( "Debugr", false, "***BusyBusy***: endpoint.Instance    - ", dbEndpoint.Instance    );


   publication = QueryCategory(toUri, 2, "state", dbEndpoint.Instance);
   //Log( "Debugr", false, "***BusyBusy***: publication - ", publication );

   if (IndexOfString(publication, "on-the-phone") >= 0) {
      Log( "Debugr", false, "***BusyBusy***: endpoint in a call change to busy state" );
      anyEndpointBusy = true;
      break;
   }
   else {
      Log( "Debugr", false, "***BusyBusy***: endpoint not in call stay in free state" );
   }

}

//Log( "Debugr", false, "***BusyBusy***: found ", totalEndpoints, " endpoint(s)" );


// Respond busy if any endpoint was busy...
if (anyEndpointBusy) {
    if (RequestTarget.Aor != "BENOTIFY") {
       Respond( 486, "Busy here" );
       Log( "Debugr", false, "***BusyBusy***: Busy responce given for", toUserAtHost);
       //log a request which was replied with busy signal
       Log( "Event" , true,  "***BusyBusy***: Busy responce given for", toUserAtHost);
       }

}

Log( "Debugr", false, "***BusyBusy***: finished script");

return;
   
]]>
  </lc:splScript>
</lc:applicationManifest>

If you want to use this script and make changes please let me know. This is a community project that started as a post on a forum so please share if you make improvements, changes etc.

Comments welcomed.

VoIPNorm

10 comments:

  1. hi, ı posted script sucsess. But it didnt work:(

    ReplyDelete
    Replies
    1. Hi,

      You might need to change the order in which the script runs on the server. Like placing it higher than the out bound routing application.If its not running high enough in the application order it may never get a chance to function.

      Cheers
      Chris

      Delete
    2. Hi:

      I would start by making sure the Script is installed correctly. I found this video very helpful: http://blog.greenl.ee/2012/02/09/video-installing-configuring-mspl-script/

      Also make sure that you added the user that you are testing with to the users.txt file .

      I also found the ApiLogger tool that is included in the SDK is great once you are sure your script is installed right. You can download the Lync SDK here:
      http://www.microsoft.com/download/en/details.aspx?id=18898

      Once you are sure the BusyBusy script is installed correctly and running you can run the ApiLogger and watch for the debug messages from BusyBusy. They are easy to spot because they start with "***BusyBusy***". If you don't see them at all it probably means that Chris is right and your script is not running because of priority issues.

      If you see the messages and but busybusy still does not work, they will provide a clue of what is going on.

      -Angela

      Delete
  2. Does this script work for calls coming from PSTN ?
    I dont see any mechanism in the script that does Reveser Number Lookup.

    $0.1

    ReplyDelete
    Replies
    1. It does not work right now. Just noticed that yesterday. The reason is that the code is using the wrong URI to query end points.

      With the right URI no lookup is necessary. QueryEndPoints works just fine with Uris in the format SIP:@host.

      We have a fixed version that will share with Chris to repost (with some bonus functionality).

      For now you can try to fix it by removing the user filtering and replacing toUserAtHost with toUri in all function calls.

      -Angela

      Delete
    2. Thanks Angela. Updated script has been posted.

      Delete
  3. How to get this to work with a PSTN call again ?!!
    None of the version seems to do wht you are claiming.

    ReplyDelete
  4. Please check the following post. Angela made some changes to the way the script works that might help.

    ReplyDelete
  5. Good afternoon,

    Noticed, that while in p2p call and adding new participant, script blocks convertion to conference call. I added this snippet to filter this situation, based on what was blocked:

    fromSip=GetUri(sipRequest.From);
    toSip=GetUri(sipRequest.To);
    if (ContainsString(toSip,fromSip, false)) {
    Log("Event", false, "***BusyBusy***: Not processing. Conference call");
    ProxyRequest();
    return;
    }

    Great sctipt! Thanks!

    ReplyDelete
  6. Anyone have this working , whats the syntax on the users.txt name@domain?

    ReplyDelete

Note: Only a member of this blog may post a comment.