Update: “Busy Here” Scripting Project Phase II

I have had some interesting comments and emails around the first phase of the script. Some people had issues getting it working and others had issues with PSTN URI’s but hopefully this latest update will solve a lot of those issues along with adding a new UM lookup feature. This update removes the User.txt file and blanket enables for the whole deployment.

Download the script:

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

Again a big shout out to Angela and Jay from Melding Technology for taking the time to work on the script and make improvements. I am super grateful for the time and effort these guys have put into making this script both easier to use and added functionality. MSPL scripting is a great skill to have and these guys are proving through this community project what champions they are at it. If you would like some help on a project like this or general Lync deployment services and like what you see in this post please reach out to Melding Tech at Sales@MeldingTech.com

Please let us know if you have any ideas or questions around the scripts use or functions. If you wish to make additional changes to the script please send me an email with updates. This is a community project so any updates, comments and changes are welcomed and remember to test in a lab first.

Below is extract from an email exchange I had with Jay around the improvements to the script. Please note that the script is downloadable from the link I provided above and below is only an extract of the entire script.

This is a new iteration on the script. It has more substantial changes than before. There are some key things that are different:

 

·         Removed user filtering based on the users.txt file. Now the filter will apply to everybody. This makes easier handling both Lync calls and external phone calls.

·         Fixed issues that prevented it to work when dialing Phone numbers directly vs Lync calls (we were using the incorrect uri to query for End points).

·         The proxyByDefault option was changed to false. Now the script is responsible for redirecting as appropriate. Need to be careful when modifying to make sure the script does not generate dead ends.

·         Added the following logic: If user is UM enabled, redirect to Voice mail right away, otherwise send busy response.

 

Important note: Since the script now redirects to Voice mail, there is a check to ignore requests going to the Voice mail itself to prevent a potential loop.  When using the script it is necessary to replace the highlighted URI with the URI for Voice mail in your environment.

 

Below is highlighting the more relevant areas:

 

 

<?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="false" />

  <lc:scriptOnly />

 

  <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 Sip URI string for the user in the To: header.

//

toUri = GetUri(sipRequest.To);

 

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

Log( "Debug", false, "***BusyBusy***: RequestUri ", sipRequest.RequestUri);

 

// Prevent a loop by ignoring requests to the Voice mai

// ***** Replace with your Exchange UM account

if (ContainsString(sipRequest.RequestUri, "sip:MTLync@mtex.MeldingTech.Com", false))

{

       Log("Debug", false, "***BusyBusy***: ignoring Voice mail request" );

       ProxyRequest();

       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 return now

          Log("Debug", false, "***BusyBusy***: this is not an audio call!" );

          ProxyRequest();

          return;

       }

}

 

 

totalEndpoints = 0;

anyEndpointBusy = false;

 

foreach (dbEndpoint in QueryEndpoints(toUri)) {

   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***: State - ", 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)" );

 

 

// If any point is busy respond with voice mail of busy signal

if (anyEndpointBusy) {

  if (RequestTarget.Aor != "BENOTIFY") {

       // Check if user is enabled for UM

       userProperties = QueryCategory(toUri, 1, "userProperties", 0);

       Log( "Debugr", false, "***BusyBusy***: User Properties - ", userProperties );

       if (ContainsString(userProperties , "1</exumEnabled>", false))

       {

           Log( "Debugr", false, "***BusyBusy***: Redirecting to voice mail for ", toUri);

          toVoiceMail = Concatenate( toUri, ";opaque=app:voicemail");

           ProxyRequest(toVoiceMail);

           return;

       }

       else

       {

           Respond( 486, "Busy here" );

           Log( "Debugr", false, "***BusyBusy***: Busy response given for ", toUri);

           //log a request which was replied with busy signal

           Log( "Event" , true,  "***BusyBusy***: Busy response given for ", toUri);

           return;

       }

    }

 

}

 

Log( "Debugr", false, "***BusyBusy***: finished script.. no action taken");

ProxyRequest();

return;

   

]]>

  </lc:splScript>

</lc:applicationManifest>

 

 

VoIPNorm

15 comments:

  1. Hi,
    amongst other your blog articles on the theme MSPL have been inspiring to our team but we have noticed that the normal admin can not utilize MSPL i.e. does not even know it exists. Thus we came to the conclusion to build the tool SimpleRoute with which it is easy and safe to generate MSPL scripts without any programming skills.

    I will be happy if you could take a look at SimpleRoute and give me some feedback. The FREE-Edition of SimpleRoute is our calling card and is freely available at http://www.colima.de/en/products/simpleroute.html
    If you like I could send you a trialversion of the PRO-Edition which offers extended functions. Because of the community being helpful to us we allow the use of the generated MSPL scripts. Thus the client benefits by saving time through the generating of valid scripts.

    I look forward to your answer (you'll find my contact details on the Presence-Widget on the website mentioned above).

    Best regards
    Matthias

    ReplyDelete
    Replies
    1. Hi Matthias,

      I took a quick look at you web site and the tool looks great. I am sure there are a bunch of people that would be interested in your tool and also seeing it develop even further. Feel free to send me some screen shots and info and I would be happy to do a post about it. When i get time I will download the free version to try out in my lab.

      Cheers
      Chris

      Delete
  2. Good afternoon,

    When reading your script, I stumbled on this description of proxyRequest:

    http://msdn.microsoft.com/en-us/library/dd167433(v=office.12).aspx

    and it seems, that voice mail redirection branch will generate error, since you have strictRoute="true" in script manifest. According to ProxyRequest() description it must be called without arguments, when strictRoute is true.

    ReplyDelete
    Replies
    1. Hi:

      Thanks for your comment. I was taking a look to understand your point about the call to ProxyRequest(). I see your point. From the docs:
      " If the request has a strict route (the "Route" header is present and populated), you must only pass the empty string as a parameter. Alternate destinations are not allowed in this case."

      On the other hand, checking at the docs for the requestFilter element (where the strictRoute attribute is set): "Set strictRoute to true if you want the application to receive all messages. Setting the strictRoute filter to false guarantees that only requests that need routing will be delivered."
      http://msdn.microsoft.com/en-us/library/gg439282.aspx

      In this case then, it means that we are getting both strict and non strict requests. I think in this case then, what might needs to be fixed is the strictRoute attribute. Still, the script works and does not generate errors because the flow of the code will only get to the call of ProxyRequest(toVoiceMail) with non strict requests.

      Thanks for checking out the script!

      Delete
  3. Hello, I was wondering if you could please assist with a question I have? I have implemented the busybusy.am MSPL script with a high priority and calling non UM enabled users works great and I receive the expected busy tone. When I call UM enabling users this unfortunately fails, and while the Lync 2010 client does not error, the active call window just displays "Calling Lync..".

    I think my issue is the value I have used for Exchange UM SIP URI. Using the OCSUMUtil application on my front end server I have confirmed my Subscriber Access SIP URI is DefaultUM.contoso.local@contoso.local. I was wondering if you could confirm if I am using the correct field? Any help would be greatly appreciated.

    ReplyDelete
  4. I am getting the same as @Andy
    Lync enabled user with no UM enabled - fine, busy works
    Lynch UM enabled user strange thing happens, caller gets ringing and recipient doesn't get any call waiting or any pop up to say another call... only when the busy call is finished, an email is received telling of miss call.

    Is there a way to also mix into the pool PSTN calls, when a PSTN calls a Busy Lync user, it just drops to normal dial tone, no busy... no voicemail nothing..

    Help would be appreciated

    excellent script btw

    ReplyDelete
  5. IMPORTNT:
    I think response 600 is better.

    It work on PSTN calls and in Lync it gives "user is on phone".

    So modify
    Respond( 600, "Busy here" );
    And VOILA!


    Also note, if you have phone normalization for incoming numbers, SIP uri will be very wrong. (not normalized number)
    I will investigate that next...

    ReplyDelete
  6. It worked!

    I have perfect SIP trunk now. Thanks guys.

    I needed to modify SIP trunk call because toUri looked like
    "sip:08xxxx009;phone-context=DefaultProfile@mydomain.fi;user=phone"
    (with this code does not find user and think user is always "free")
    (xxx are for my safe)

    So i just modify it to look like
    "sip:08xxxx009@mydomain.fi;user=phone"

    This code is not direct use for everybody because I have normalization also here:
    if (IndexOfString (toUri, "sip:xxxx", false) > -1) { // xxxx = your first digits in your linephone
    toUri = Concatenate("sip:+358yyyy",SubString(toUri,10,3), "@mydomain.fi;user=phone");
    Log( "Debugr", false, "***BusyBusy***: SIP TRUNK fix ", toUri);
    }

    ReplyDelete
  7. If you want busy also when in conference call. (We did want)

    Here it is:

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

    ReplyDelete
  8. This line is returning NULL in every case tested: Where is determines the busy status of the callee.

    publication = QueryCategory(toUri, 2, "state", dbEndpoint.Instance);

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Does this script work if your Lync environment has Enhanced Privacy mode enabled? If not, is there a work around?

    ReplyDelete
  11. Hi,

    Trying to configure this script on our Lync 2013 environment, however it doesn't seem to work. Calls are still coming through when busy, both internal and PSTN.

    This is my first encounter with MSPL scripts, have I created the it correctly (as follows)?
    Identity : Service:Registrar:fepool1.mydomain.com/busybusy
    Priority : 0
    Uri : http://www.meldingtechnology.com/busybusy
    Name : busybusy
    Enabled : True
    Critical : True
    ScriptName : C:\scripts\busybusy.am
    Script :

    Can anyone advise whether this script should also work on Lync 2013?

    Thanks,
    Shaun

    ReplyDelete
  12. Hi,
    i just tried to implement the script on Lync 2013, but without success. When i activate the script with a priority lower than 7, no calls are routed after activating.

    If I activate the script with a priority lower than 7, the script isn't used, i can see no log entries in the apilogger.exe

    Any Ideas?

    ReplyDelete

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