none
lync user busy with one call RRS feed

  • Question

  •  

    Hello technet,

    I'm looking for a possibility to respond incoming phone calls with busy if the user is already on the phone. The best should be a MSPL script but I'm not familar with MSPL scripts. The basic idea would be to intercept an incoming INVITE, check if this is a phone call, look at the presence state of the called user and either pass the invite to the user or reply with "486 Busy Here". I don't need any difference by user the solution should be applied to all users.

    I wonder if somebody has already implemented a similar solution?

    Thanks.

     

    UPDATE: found this thread http://social.microsoft.com/Forums/en/communicationsservertelephony/thread/d39a8fc0-7a8c-4ebf-9633-6d21bbd3d544, will try to implement
    • Edited by willi.weikum Wednesday, September 21, 2011 9:59 AM
    Wednesday, September 21, 2011 9:45 AM

Answers

  • Hi there,

    Unify square had a 3rd party product that does this (not used it)

    Call Busy on Busy.

    http://www.unifysquare.com/blog/post/Unify-Square-BusyOnBusy-20-%28Lync-version%29-Released.aspx

    Hope that helps

    Tom


    Tom Arbuthnot, Consultant Modality Systems
    Blog: Lync'd Up Blog Subscribe for updates: Email or RSS
    Twitter @tomarbuthnot
    Wednesday, September 21, 2011 10:47 AM
  • I started with a script mentioned above and added some functionality.

    At the moment the script checks all INVITEs, and exits without any action if this is a RE-INVITE or an INVITE which doesn't contain audio (IM conversation). Then it looks for a presence state of all registered endpoints of a user and if one of the endpoints is on the phone it responds with "486 Busy here".

    I would be glad if somebody with more scripting experince would take a look at the script and tell if there are problems inside or maybe a better aproach.

     

    <?xml version="1.0" ?>
    <lc:applicationManifest
     lc:appUri="C:\inetpub\wwwroot\busybusy.am"
     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 />
    <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;
    	  }
    	} 
    
    
    
    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;
    	}
    }
    
    
    
    queryUri = RequestTarget.Uri;
    
    
    totalEndpoints = 0;
    anyEndpointBusy = false;
    
    foreach (dbEndpoint in QueryEndpoints(queryUri)) {
       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(queryUri, 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", queryUri );
           //log a request which was replied with busy signal
           Log( "Event" , true,  "***BusyBusy***: Busy responce given for", queryUri );
           }
    
    }
    
    Log( "Debugr", false, "***BusyBusy***: finished script");
    
    return;
        
    ]]></lc:splScript>
    </lc:applicationManifest>    


    • Marked as answer by willi.weikum Tuesday, October 4, 2011 9:02 AM
    Wednesday, September 28, 2011 1:29 PM

All replies

  • Hi there,

    Unify square had a 3rd party product that does this (not used it)

    Call Busy on Busy.

    http://www.unifysquare.com/blog/post/Unify-Square-BusyOnBusy-20-%28Lync-version%29-Released.aspx

    Hope that helps

    Tom


    Tom Arbuthnot, Consultant Modality Systems
    Blog: Lync'd Up Blog Subscribe for updates: Email or RSS
    Twitter @tomarbuthnot
    Wednesday, September 21, 2011 10:47 AM
  • Hello Tom,

    thank you for this information. I know about this Add-On. I just wanted to avoid adding 3-rd party software. The other cause is that it's not really cheap.

    Wednesday, September 21, 2011 11:05 AM
  • As I know, except the 3rd party software, the lync server can not implement this option at present.
    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Thursday, September 22, 2011 8:56 AM
    Moderator
  • I started with a script mentioned above and added some functionality.

    At the moment the script checks all INVITEs, and exits without any action if this is a RE-INVITE or an INVITE which doesn't contain audio (IM conversation). Then it looks for a presence state of all registered endpoints of a user and if one of the endpoints is on the phone it responds with "486 Busy here".

    I would be glad if somebody with more scripting experince would take a look at the script and tell if there are problems inside or maybe a better aproach.

     

    <?xml version="1.0" ?>
    <lc:applicationManifest
     lc:appUri="C:\inetpub\wwwroot\busybusy.am"
     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 />
    <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;
    	  }
    	} 
    
    
    
    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;
    	}
    }
    
    
    
    queryUri = RequestTarget.Uri;
    
    
    totalEndpoints = 0;
    anyEndpointBusy = false;
    
    foreach (dbEndpoint in QueryEndpoints(queryUri)) {
       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(queryUri, 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", queryUri );
           //log a request which was replied with busy signal
           Log( "Event" , true,  "***BusyBusy***: Busy responce given for", queryUri );
           }
    
    }
    
    Log( "Debugr", false, "***BusyBusy***: finished script");
    
    return;
        
    ]]></lc:splScript>
    </lc:applicationManifest>    


    • Marked as answer by willi.weikum Tuesday, October 4, 2011 9:02 AM
    Wednesday, September 28, 2011 1:29 PM
  • Hi Willi,

    I am Rohit Jhangiani from Unify<sup>2</sup>. I just wanted to follow-up to your requirements for a BusyOnBusy like solution.

    BusyOnBusy is an enterprise-quality product that includes the following components/functionality:

    ·         The BusyOnBusy msi provides an easy click-through installation. BusyOnBusy comprises of the following components:  

    ·         BusyOnBusy.exe: A command Line Administration Utility to configure and administer Lync users (per-user level administration) for BusyOnBusy

    ·         BusyOnBusy server application: A Lync Server Application that intercepts calls, and either allows  or blocks calls for Lync users, based on a user’s BusyOnBusy configuration settings

    ·         Batch configure users script: A PowerShell script that can be used to “batch configure” users for BusyOnBusy

    ·         BusyOnBusy Global settings configuration file: A configuration file that can be used to set the global BusyOnBusy policy for the Lync Pool

    ·         BusyOnBusy 2.0 User Guide: Documentation on installing/uninstalling BusyOnBusy and details & examples, on administering and using BusyOnBusy

    Feel free to contact us at emeasupport@unifysquare.com for any general BusyOnBusy related queries and/or at sales@unifysquare.com for pricing related information.

    We will be glad to assist you. We look forward to working with you.

    Thanks!

    Rohit

    Monday, October 3, 2011 9:32 PM
  • Hello Rohit,

     

    thank you for you reply. I'm aware about your BusyOnBusy solution. I'm sure this software has some advantages over self-written scripts but it also has disadvantages like licensing costs, 3rd party software on the server. From my point of there is also little to no use of per-user administration as more then 95% of our users don't want to have additional calls while on the phone so I don't need it for my installation.

    I also think this feature is essential for Lync.  So sharing this code with others may let them use it and make Microsoft change thier mind and add this feature to the product.

    I wish you good business with you product wich may fit the needs of some customers.

     

    Willi

    Tuesday, October 4, 2011 9:13 AM
  • Hi Willy, have you find a solution about your script? I am interested in it.
    Tuesday, February 21, 2012 11:33 AM
  • Hi Antonio,

    the published script is working fine despite some error messages in the client eventlog (you may want to remove a number of logging output for production use). There are limitations compared to a commercial alternative but it does the job from my point of view.

    Thursday, February 23, 2012 9:55 AM
  • Hi Willi, thanks for your reply.

    I tryed to use code posted in this thread and followed all steps but it does not work form me. Lync client on the phone is still free, if a second call arrives.

    Can you point me in a right way? Can I contact you directly?( by mail ).

    Should I see something into event server of FE? or just at the client side ( after activated logging into client options ) ??

    Regards.

    Thursday, February 23, 2012 3:20 PM
  • Hello Antonio,

    the code posted works fine in my environment. The only limitation especially in my case of using DirectSIP is it only works when SIP Invite includes a complete users E164 number. It doesn't work if it contains any short number like yuo are using in PBX numbering e.g. sip:+4930987654321@lyncfe.customer.de works and sip:321@lyncfe.customer.de doesn't work. At the moment my scripting capabilities are not enough to make it work for other case also.

    According loggin there are 2 possibilities: all log output from script which starts with <Log( "Event" , > goes to the event log, and output starting with <Log( "Debugr", > goes to the debuggig interface you can make visible with ApiLogger.exe (part of Lync SDK). The looging occurs on the server.


    Friday, February 24, 2012 9:25 AM
  • Hello Willi for you clarification.

    Now i am able to use apilogger and events at event viewer.

    Regarding script's code i will study it to make it usable with my enviroment.

    I will report here if i will find a solution.

    Thanks

    Friday, February 24, 2012 1:31 PM
  • Hi, hope this will help all of you that are interested in this script:

    http://voipnorm.blogspot.com/2012/02/lync-mspl-busy-on-busy-script-project.html?utm_source=feedburner&utm_medium=email&utm_campaign=Feed%3A+VoipnormsUnifiedCommunicationsBlog+%28VoIPNorm%27s+Unified+Communications+Blog%29

    Saturday, February 25, 2012 12:25 PM
  • hi, are there anyone run this scrip sucsess?
    Sunday, February 26, 2012 7:19 PM
  • Guys from Voipnom had tested it in a lab enviroment and it works. I am going to try it as well but in my case i have another kind of issue, related to short number inside INVITE because it is coming from a IP-PBX

    Regards

    Monday, February 27, 2012 7:58 AM
  • Hi Antonio,

    try to configure your PBX to send a full e164 number (also called canonical format). Most of the PBX I know have a number of options to manipulate the number. As a second options you may change the number wihich goes to the scipt and add the missing prefix (+, country code and subscriber number) to the transmitted number but this is going to to be hard you need to mess with more then a couple of locations.

    Monday, February 27, 2012 8:14 AM
  • Hi willi,

    I cannot modify or add "+" and country code from my ip-pbx. It has not this function so the only possibility is to work at lync side.

    I had a couple of normalization rules at lync side to get it working but seams that are not applied before going into script. Have you such an idea where, in which priority i have to make script busy start?

    Regards... 

    Monday, February 27, 2012 1:29 PM
  • Hi all, I tried to add, at IP-PBX side the "+", to have a E164 formatting. I can call now from my PBX an extension of lync in E164 mode.

    Here a log of Apilogger. As you can see i cannot hear yet busy tone. What Am I wrong?

    From 5099;phone-context=DefaultProfile@lwtec.eu PR
    ClientVersionFilter: User is not homed on this server - do not run CVC logic
    Found multipart body. Content-Type:multipart/alternative; boundary=Oztc5WbKZ2zQ4
    5216VC5o4lZdNWEXs4X
    Found SDP content-type in Multipart item count: 0
    Found Audio content
    Found M/MIME body with audio SDP
    FROM URI is Phone. Ignoring
    IIMFilter application processing request - INVITE
    Inbound Routing: We have a request - INVITE
    Found multipart body. Content-Type:multipart/alternative; boundary=Oztc5WbKZ2zQ4
    5216VC5o4lZdNWEXs4X
    Found SDP content-type in Multipart item count: 0
    Can use Inbound Routing App for INVITE request to sip:+3707@lwtec.eu;user=phone
    Can use Inbound Routing App for M/MIME INVITE to sip:+3707@lwtec.eu;user=phone
        endpoint.EPID - ed5cce6c05
        endpoint.Instance - 66f37653-9446-5266-ad6b-538a8adaf8a0
        endpoint.ContactInfo - sip:172.16.52.149:51888;transport=tls;ms-opaque=6ce12
    cbc3a;ms-received-cid=2400
    We are the primary cluster for the target
    Usc is online - will use Dnd state.
    Dispatching to Inbound Routing App
    ***BusyBusy***: We have a request - INVITE
    ***BusyBusy***: toUserAtHost         - +3707@lwtec.eu
    ***BusyBusy***: Found multipart body. Content-Type:multipart/alternative; bounda
    ry=Oztc5WbKZ2zQ45216VC5o4lZdNWEXs4X
    ***BusyBusy***: Found SDP content-type in Multipart item count: 0
    ***BusyBusy***: found m=audio
    ***BusyBusy***: content has audio
    ***BusyBusy***: this is an audio call
    ***BusyBusy***: found 0 endpoint(s)

    Any idea???

    Thursday, March 1, 2012 4:03 PM
  • Hi Antonio,

    your number still looks wierd. You need have a complete e164 number in the INVITE including country code, area code and the subscriber number. On the other side you can try to configure the lync user to have this short number for testing.

    Monday, March 5, 2012 9:40 AM