Thursday, March 8, 2012

Cisco’s Mobility Chaos

We have all seen the Cisco mobile post by Michael Smith about how he was underwhelmed by Microsoft’s Lync mobile solution or the Cisco versus Microsoft post by Zeus but what are they not telling you about the Cisco’s solution? What does Cisco not want you to know?

To quote Michaels blog, “With Cisco Jabber, the UC capabilities are consistent across PC, Mac, tablets and phones.” But is it really? Lets take a look.

Cisco’s PC and MAC Story

While this blog post is mainly focused on Mobility I just wanted to highlight that Cisco’s patch work of clients tie you to their only available web conferencing solution and how Michaels statement really is a stretching the truth. And to be exact if I were to remove WebEx because Michael distinctly calls out Jabber it looks some what of a different story

With WebEx support:

  Windows Mac
Presence CUPC Jabber for Mac
IM CUPC Jabber for Mac
VoIP CUPC Jabber for Mac
Voicemail CUPC Jabber for Mac
Video CUPC or WebEx WebEx only
Desktop Sharing CUPC Jabber for Mac
App Sharing WebEx WebEx
Web Conferencing WebEx WebEx
Federation IM/P Only IM/P Only
VPN Less Connect No No

Without WebEx:

  Windows Mac
Presence CUPC Jabber for Mac
IM CUPC Jabber for Mac
VoIP CUPC Jabber for Mac
Voicemail CUPC/Jabber Jabber for Mac
Video CUPC/Jabber No
Desktop Sharing CUPC/Jabber Jabber for Mac
App Sharing No No
Web Conferencing No No
Federation IM/P Only IM/P Only
VPN Less Connect No No

So, no WebEx cloud = no Web Conferencing. Or do we need another client for that or is that another “it’s coming” moments?

Cisco’s Smartphone and Tablet Consistency

Below is what Cisco Jabber currently offers across the most popular platforms.

  iPhone iPad Android Phone/Tablet
Presence Jabber IM for iPhone No ( iPhone client) No
IM Jabber IM for iPhone No ( iPhone client) No
VoIP Jabber Voice for iPhone No ( iPhone client) Jabber for Android
Voicemail Jabber Voice for iPhone No ( iPhone client) No
Video WebEx for iPhone WebEx for iPad No
Desktop
Sharing
WebEx for iPhone
(view only)
WebEx for iPhone
(view only)
WebEx for Android
(View Only)
App
Sharing
WebEx for iPhone
(view only)
WebEx for iPhone
(view only)
WebEx for Android
(View Only)
Web Conferencing WebEx for iPhone WebEx for iPad WebEx for Android
(View Only)

 

WP7 BB Symbian
Presence No Jabber IM for BB Cisco Mobile for Nokia
IM No Jabber IM for BB Cisco Mobile for Nokia
VoIP No BBMVC Client Cisco Mobile for Nokia
Voicemail No No Cisco Mobile for Nokia
Video No No No
Desktop
Sharing
No WebEx for BB (View Only) WebEx for Nokia (View Only)
App
Sharing
No WebEx for BB WebEx for Nokia (View Only)
Web Conferencing No WebEx for BB WebEx for Nokia (View Only)

Looks pretty inconsistent to me with no on-premise solution for many of the options mentioned forcing you to only have a cloud option with WebEx. If you remove WebEx your left with only IM/P and voice but not consistently across all platforms. In fact, if video is the new voice then they have only one platform that delivers video with Jabber/CUPC and that is the Windows PC client. But isn't this the “Post PC era” according to Cisco? Well there is the Cius I guess but wait is that with Cisco Telepresence or Jabber, I am confused, maybe another app.

So I actually have a Android tablet that I use for testing and thought great I might actually download the Android application and give a spin in a lab where I have access to CUCM. Well its not that easy. I have an ASUS Tablet and Cisco’s Jabber only officially supports Samsung’s Galaxy Android devices. Bummer. I couldn’t even download the application on to my tablet to try it out. I just got a big warning that my device wasn’t compatible. So more limitations. But didn’t Michael say they had the most consistent story???

One Client To Rule Them All

Cisco Jabber

Cisco require multiple applications for Mobile UC- Jabber for IM, Jabber for VoIP and WebEx Meeting. Three clients to complete a UC solution and not all platforms are created equal.

    • Some platforms have IM, voice, VoIP and WebEx and some don’t. This mix of features creates complexity at the help desk and lacks insight on the complexity of deploying applications that don’t carry the same feature sets. This is a complex issue for any software vendor across multiple OS’s for sure. Even Cisco’s own Cuis doesn’t support video with WebEx. How confusing is that?
    • Some platforms use Cisco Secure Connect (requires Cisco ASA hardware) feature and others require AnyConnect/VPN. More complexity. Depending on what version of Anywhere Connect and whether you are running it on IOS or ASA affects your ability to allow these applications to work.
    • iPhone, iPad require AnyConnect with the Cisco ASA. So now to take advantage of Jabber you need Cisco  hardware, you already needed Cisco UCS servers for CUPS so why stop there.

I noticed in the comments on Michaels post that Cisco choose to deliver the features in separate applications . When I talk with companies this isn't what they want. They want to be able to leverage UC from a single mobile application. Which leads me to the conclusion that this wasn’t really a choice but due to their compartmentalized UC infrastructure this was their only way to deliver these features.

Why did I write this blog?

My intention here is to highlight “ don’t throw stones in glass houses”. Cisco execs think posting on a blog and spreading miss-information about their own products is okay while analyst with poorly researched articles are no less guilty. I noticed in the comments of Michaels blog there was a lot of “it’s coming” to a lot of points raised around where they fell short. No mention of that in the article it self though.

So yeah, I work for Microsoft and happy to say I have a strong opinion of where UC is heading and what companies are looking for when it comes to UC. Cisco compartmentalized infrastructure carries through to their mobility story and I am pretty happy about that. The chaos they create in their self proclaimed “Post PC” era makes my job a hell of a lot easier. Thanks Cisco.

VoIPNorm

Thursday, March 1, 2012

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

Friday, February 24, 2012

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