Tuesday, December 3, 2013

Correctly Adding Contacts and Groups to Lync Using UCMA 3.0


Recently I have been revisiting one of our products, BuddyList Migrator, which migrates IBM Sametime buddylists into Microsoft Lync contact lists.  While I was doing this, I came across an unexpected, and, so far as I can tell, undocumented nuance involved in using the UCMA ContactsGroupServices class to add contacts to a Lync user’s contact list.  In the interests of serving the UCMA development community, I’d like to share what I’ve learned.
To follow along with this, you will need to have a Lync 2010 server to play with, and a development environment with the UCMA 3.0 SDK installed.  You will also, ideally, need to setup a Lync TrustedApplication,so that your application can login in behalf of users without needing to supply credentials, although for experimentation's sake, you could skip that step.

Application Background

In our application, we need to generate contacts for a Lync user based on their existing SameTime buddylist.  SameTime buddylists are stored in a Lotus Notes database, VPUserInfo.nsf.  The buddylists in this database refer to each buddy in a user’s list by the buddy’s SameTime Notes ID, which, in most cases, is completely unintelligible to Lync.  However, we can cross-reference this Notes ID with the user’s email address, by looking the user up in the Notes address book database, Names.nsf.  Once we have the buddy’s email address, we can use this to find the buddy’s Lync address in Active Directory to add them as a contact in Lync.
This process of exporting SameTime buddylists is outside the scope of this discussion.  We use another internal Java application to perform this process for us, which outputs an XML representation of each user to be converted’s buddylist.  The actual format is slightly more complex, but for the purposes of illustration, we can view this file as similar to this:
<xmlBuddyList>
    <ownerEmailAddress>john.doe@foo.com</ownerEmailAddress>
    <buddylistGroups>
        <group>
            <groupName>Group 1</groupName>
            <groupUsers>
                <userEmailAddress>Bill.Smith@foo.com</userEmailAddress>
                <userEmailAddress>Mary.White@foo.com</userEmailAddress>
                <userEmailAddress>Jim.Ford@foo.com</userEmailAddress>
            </groupUsers>            
        </group>
        group>
            <groupName>Group 2</groupName>
            <groupUsers>
                <userEmailAddress>John.Green@foo.com</userEmailAddress>
                <userEmailAddress>Hannah.Hayes@foo.com</userEmailAddress>
                <userEmailAddress>Betty.Adams@foo.com</userEmailAddress>
            </groupUsers>            
        </group>
    </buddyListGroups>
</xmlBuddyList>


Once we have the user’s buddylist in this logical XML representation, we can start the process of pushing the buddylist into the user’s Lync contact list.

Lync Initialization

To do much of anything useful with the Lync UCMA library, one needs to first connect to the Lync server, and obtain a UserEndpoint object for the user.  After working on a number of UCMA projects, I’ve developed this simple utility class to abstract out the details:

class UCMAWrapper {
    public bool IsInitialized { get; private set; }
    private CollaborationPlatform _collaborationPlatform;
    private readonly Dictionary<string, UserEndpoint> _userEndpoints = 
        new Dictionary<string, UserEndpoint>();

    public void Start(string appName, int port, string gruu) {

        string localhost = System.Net.Dns.GetHostEntry("localhost").HostName;
        // for a TrustedApplication
        var settings = new ServerPlatformSettings(
            appName, 
            localhost, 
            port, 
            gruu, 
            CertificateHelper.GetLocalCertificate()
            );
        _collaborationPlatform = new CollaborationPlatform(settings);
        /* for a non-trusted application
            *
        var settings = new ClientPlatformSettings("myapp", SipTransportType.Tls);
        _collaborationPlatform = new CollaborationPlatform(settings);
            * 
            */
        try {
            _collaborationPlatform.EndStartup(
                _collaborationPlatform.BeginStartup(null, null)
            );
        } catch (RealTimeException ex) {
            // handle error
        }
        IsInitialized = true;
    }
    // More...

CertificateHelper is a utility class I created to encapsulate grabbing the correct certificate from the local machine store for the TrustedApplication.

In this class, _collaborationPlatform is our connection to the Lync server.  We will need this connection object in order to create UserEndpoint objects, which will allow us to login to Lync on behalf of users and perform different actions.  We also create a Dictionary to contain the UserEndpoints that we have created, indexed by the user’s Lync SIP address, as creating and connecting a UserEndpoint is a relatively expensive operation, and in situations where we will be reusing the same endpoint repeatedly, it is worthwhile to maintain a previously created endpoint and reuse it, rather than dispose and recreate it.

Once we have created our CollaborationPlatform and successfully connected to the Lync server, we can create UserEnpoints that allow us to perform actions on behalf of the user and access their contact lists.  We’ll use the following method to create and connect a UserEndpoint, reusing our cached instance if we already have one:

public UserEndpoint GetUserEndPoint(string uri) {
    string proxyServerFqdn = "lync server fqdn or ip address";
    const int tlsPort = 5061;

    if (uri == null) {
        return null;
    }
    if (_userEndpoints.ContainsKey(uri)) {
        return _userEndpoints[uri];
    }

    var settings = new UserEndpointSettings(uri, proxyServerFqdn, tlsPort);
    _userEndpoints[uri] = new UserEndpoint(_collaborationPlatform, settings);

    try {
        _userEndpoints[uri].EndEstablish(_userEndpoints[uri].BeginEstablish(null, null));

    } catch (Exception ex) {
        _userEndpoints[uri] = null;
    }
    return _userEndpoints[uri];
}

NOTE: You’re going to see me using the End(Begin()) method pairs in a synchronous fashion here.  Microsoft doesn’t really recommend doing it this way, but I have found that the recommended asynchronous way results in a proliferation of callback methods and wait handles that can introduce irritating bugs if you mess up your state management and makes the code much more difficult to follow.  I’m also going to strip out most of the error-handling and logging code that you would want in a production application. 

Lastly, we need some methods to clean up our UserEndpoints and the CollaborationPlatform we created when we are finished with them.

public void Shutdown() {
    while (_userEndpoints.Keys.Any()) {
        CloseUserEndpoint(_userEndpoints.Keys.First());
    }
    try {
        _collaborationPlatform.EndShutdown(
            _collaborationPlatform.BeginShutdown(null, null));
    } catch (RealTimeException ex) {
    }
}

private void CloseUserEndpoint(string uri) {
    if (!_userEndpoints.ContainsKey(uri) || _userEndpoints[uri] == null) 
        return;
    _userEndpoints[uri].EndTerminate(
        _userEndpoints[uri].BeginTerminate(null, null));
    _userEndpoints.Remove(uri);
}

Managing Contacts

Once we have created a UserEndpoint whose contact list we wish to add to, we will add contacts and groups using the endpoints ContactGroupServices object.  Once again, there is a fair amount of plumbing in doing this correctly, so we will create another wrapper class to handle this for us:

public class ContactManager {
    private readonly ContactGroupServices _contactGroupServices;
    private ContactList _contactList;
    private UserEndpoint _userEndpoint;

    public ContactManager(UserEndpoint endpoint) {
        if (endpoint == null) {
            throw new ArgumentNullException("endpoint");
        }
        _contactList = new ContactList();

        _userEndpoint = endpoint;
        _contactGroupServices = _userEndpoint.ContactGroupServices;
        //Log.Info(("Subscribing to contact updates"));
        _contactGroupServices.NotificationReceived += OnNotificationReceived;
        try {
            _contactGroupServices.EndSubscribe(
                _contactGroupServices.BeginSubscribe(null, null));
        } catch (InvalidOperationException ex) {
                
        }
    }
    // More...
}

This class manages an in-memory version of the user’s contact list in the _contactList member.  We create a ContactManager by passing in the UserEndpoint that we have created.  Next, we store the ContactGroupServices member of the endpoint in another member variable.  We’ll bind an event handler to the ContactGroupService’s NotificationReceived event, which fires whenever we subscribe to the user’s existing contact list, using the End/BeginSubscribe methods, and whenever we modify the contact list using the ContactGroupServices methods.

Our event handler for the NotificationReceived event extracts any new contacts and groups from the notification and adds them to our in-memory contact list.  Ignore the _groupWaitHandles dictionary for the moment; its reason for existence will become clearer once we cover adding a new group to a contact list.

private void OnNotificationReceived(object sender, ContactGroupNotificationEventArgs e) {
    foreach (var contact in e.Contacts.Select(item => item.Item)) {
        if (_contactList.Contacts.All(c => c.Name != contact.Name)) {
            _contactList.Contacts.Add(
                new ContactInfo {
                    Data = contact.ContactData,
                    Name = contact.Name,
                    GroupIds = contact.GroupIds,
                    Extension = contact.ContactExtension,
                    Uri = contact.Uri
                });
        }

    }
    foreach (var group in e.Groups.Select(item => item.Item)) {
        if ( _contactList.Groups.All(g => g.Name != @group.Name))
        _contactList.Groups.Add(
            new GroupInfo {
                Data = group.GroupData,
                Name = group.Name,
                ID = group.GroupId
            });
        if (_groupWaitHandles.ContainsKey(@group.Name)) {
            _groupWaitHandles[group.Name].Set();
        }
    }
}
private readonly Dictionary<string, AutoResetEvent> _groupWaitHandles =
    new Dictionary<string, AutoResetEvent>();
 
NOTE:  Properly, we should be checking the value of the Operation member of each contact and group received as part of the notification.  For our particular scenario, (migrating a SameTime userbase to Lync) we are operating in an environment where users are not actively logged into the Lync server while our tool runs.  Some care should probably be taken to keep the memory list in sync with the server if you are running in an environment where users or other tools may also modify contact lists.

As you can see, our in-memory _contactList member is essentially just a persistent version of the data returned to us by the NotificationReceived event.  We need to maintain this data because the Lync server does not always send us out a full listing of the user’s contact list; only when we subscribe to the contact list using the ContactGroupServices End/BeginSubscribe methods do we receive the full state of the contact list.  Otherwise, we only receive the modified items.  The structures for the _contactList are:

public class ContactList {
    public List<GroupInfo> Groups { get; set; }
    public List<ContactInfo> Contacts { get; set; }
    public ContactList() {
        Groups = new List<GroupInfo>();
        Contacts = new List<ContactInfo>();
    }
}
public class ContactInfo {
    public string Data { get; set; }
    public string Extension { get; set; }
    public string Name { get; set; }
    public string Uri { get; set; }
    public int[] GroupIds { get; set; }

    public ContactInfo() {
        Data = Extension = Name = Uri = "";
        GroupIds = null;
        Extension = "<contactSettings></contactSettings>";
    }
    public class GroupInfo {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Data { get; set; }
    }
}

Adding Groups

Once we have created our ContactManager and received the current state of the user’s contact list via the NotificationReceived event, our next step will be to add the user’s SameTime private groups as groups to their Lync contact list.  Basically, the process is as follows:

  1. Determine if the group that we are adding already exists in the user’s contact list

  2. If it does not, add a new group.

  3. Wait for the notification that the group has been created on the Lync server, or that the operation has timed out.

public void CreatePrivateGroup(string grp) {
    try {
        var groupName = grp;
        var groupID = GetGroupID(groupName);
        if (groupID == -1) {
            var gp = new GroupInfo { Name = groupName, ID = groupID, Data = "" };
            AddGroup(gp);
        }
    } catch (Exception ex) {
    }
}
public void AddGroup(GroupInfo group) {
    if (_contactList.Groups.Find(g => g.Name == group.Name) != null) {
        return;
    }
    try {
        _contactGroupServices.EndAddGroup(
            _contactGroupServices.BeginAddGroup(
            group.Name, 
            group.Data, 
            null, 
            null
        ));
        _groupWaitHandles.Add(group.Name, new AutoResetEvent(false));
        //HACK - I can never actually receive the notification that a group was added
        _contactGroupServices.EndUnsubscribe(
            _contactGroupServices.BeginUnsubscribe(null, null));
        _contactGroupServices.EndSubscribe(
            _contactGroupServices.BeginSubscribe(null, null));
        //ENDHACK
        var success = _groupWaitHandles[group.Name].WaitOne(20000);
        if (!success) {
        }
        _groupWaitHandles.Remove(group.Name);
    } catch (Exception ex1) {
    }
}
public int GetGroupID(string groupName) {
    foreach (var g in _contactList.Groups.Where(
        g => g.Name == groupName)) {
        return g.ID;
    }
    return -1;
}

This is where the previously mentioned _groupWaitHandles dictionary comes into play.  If we only wanted to add groups, we could simply call End/BeginAddGroup, and return, then move onto the next group.  However, we are going to be adding contacts into these newly created groups.  Lync associates the contacts with groups by using the integer ID field, which is auto-generated on the server, so, while we could take a best guess at what it might be, we don’t really know how to assign contacts to the group until we receive the notification that the group has been created, with the accompanying new ID.  This is why we need the dictionary of wait handles.


You’ll notice the End/BeginUnsubscribe-End/BeginSubscribe block outlined with HACK comments.  For whatever reason, I have not been able to get the Lync server to actually fire a notification when a group is created as it should.  Unsubscribing and then resubscribing forces the full contact list push, which ensures that we obtain the new group’s ID before moving on to try to add contacts to it.

Adding Contacts


Compared to adding groups, adding contacts is simple.  The only tricky thing here is to ensure that we add the group ID for each group the contact is a part of to the ContactAddOptions structure that we pass into the End/BeginAddContact calls.

public void AddContact(ContactInfo contact) {
            
    if (_contactList.Contacts.Find(
            c => c.Uri == contact.Uri) != null) {
        return;
    }
    try {
        var contactAddOptions = new ContactAddOptions {
            ContactData = contact.Data,
            ContactExtension = contact.Extension,
            ContactName = contact.Name,
        };
        contactAddOptions.GroupIds.AddRange(contact.GroupIds);
        _contactGroupServices.EndAddContact(
            _contactGroupServices.BeginAddContact(
                contact.Uri, contactAddOptions, null, null
            )
        );

    } catch (InvalidOperationException ex) {
    }
}


Putting it Together



With these utility classes created, the process of moving a user to Lync from SameTime becomes relatively straightforward.



  1. Read in the xml buddylist document.


  2. Resolve the user’s email to their Lync SIP address using Active Directory.


  3. Obtain a UserEndpoint for the user.


  4. Create a ContactManager object for the user.


  5. Create the groups for the user.


  6. Add the contacts to each group, resolving the contact email addresses to Lync SIP addresses using Active Directory.


After the running through this process, you will see the user’s SameTime contacts and groups moved over to Lync (click for larger images):














image image
SameTime buddylist xml Lync Contact List




We’ve got all the user’s groups and contacts moved over.  Great, we’re done!


Not so fast…


Let’s switch over from the Groups tab in the Lync client to the Status and Relationship tabs:


image


Nothing… Hmm…


When one adds a contact using the Lync client, contacts will be sorted into these views automatically.  Yet none of the contacts we’ve created using our UCMA application are to be found.  Why is that?


The Mysterious Tilde Group



After digging through the underlying SQL databases for several hours, and pursuing a number of other blind alleys, I found a clue in the bowels of the Lync RTC database.  Poring through the ContactGroup table, I noticed that a large number of the rows included a value of 0x7E for the DisplayName column.  After checking out the OwnerId keys for a number of these rows, I realized that there appeared to be a row like this for each user that has actually created contacts using the Lync client.  Taking one of the users, I cross-referenced with the ContactGroupAssoc table, and discovered that this 0x7E group appeared to contain all of the contacts in all of the user’s other groups.


Hmm, that is interesting, I thought to myself.


Since 0x7E, and all the other values in that column, looked like hex, I grabbed one of the entries for my personal Lync account, and discovered, after running it through a hex-to-string converter, that it matched up with the name of one of my contact list groups.  So what is 0x7E?  Turns out, it converts to “~”.  Looking at my Lync client, I didn’t see any groups displayed named “~”.  Curious.


image


Next, I took one of our test accounts that I had run our conversion tool on, and tested the effects of various actions in the Lync client.  I found that if I moved a contact from one of the SameTime imported groups into one of the other groups (Other Contacts or Pinned Contacts), that contact would then appear in the the Status/Relationship views, even after I moved the contact back to its previous group.  The Lync client was clearly doing something under the covers when I moved a contact into another group, so I pulled up the database again and found the particular contact in the ContactGroupAssoc table.  In addition to the group that the contact was actually in, I now found another row, linking it to another group that turned out to be the ~ group for this user.  Bingo…


I modified the code for our application, so that it added all the imported SameTime contacts to this “~” group in addition to the imported SameTime groups, reran the import for a test user, and voila, the Status and Relationship tabs lit up.


image


Conclusions



Now, be forewarned, that all of what follows is based solely on what I have been able to deduce, by examining this situation, the Lync databases, and the behavior of the Lync client.  I wasn’t able to find any mention of this tilde group in the UCMA API docs, and the one example I found of a similar situation involved a completely different method of adding contacts than the one presented here.



  • The Lync client creates and maintains a special contact group, named “~”.


  • This group is not displayed in the Groups pane of the client UI, but is used to populate the Status and Relationship views, filtered by presence/privacy list information maintained eleswhere.


  • The Lync client silently updates the ~ group whenever you add, remove, or relocate contacts using the Lync client.


  • Adding a contact to a contact list with UCMA does not modify the ~ list, unless you explicitly add the new contact to the ~ list, as well as the group that it actually belongs to.


  • The ~ group is only created once the user has actually logged into the Lync server using the Lync client, and possibly only after contacts have been added using the Lync client.


So, if you’re working with contact lists in UCMA, make sure you add new contacts to the magical ~ group!

Wednesday, August 7, 2013

Floating a child element in a parent

Welcome;

In the HR Auditor product, we produce a list of chat sessions on the left side and display the results of the selected chat session on the right.  A problem we ran into was that with longer lists, the results would be hidden up at the top, forcing the user to scroll up to see the results.

We decided to "float" the results, so it would stay in view-able to the users as they scrolled up and down.  We had looked into a couple of options, but the most promising one had the issue of putting the floating object outside the parent if the results were changed.

I started to investigate what was going on and once I started looking at the code, I found it was over-complicating what we were trying to accomplish. I decided to simplify the JavaScript and write my own function.

This function will take 3 variables
 
parentElmtName This is the element that will drive the "child" element "floating".  We are using a class name, so it should be unique.
floatElmtName This is the element that will be "floating". We are using a class name, so it should be unique.
topOffset How far you want "floating" element to be from the top.
 // Keeps the child visible in the parent
 function vertFloatByClass(parentElmtName, floatElmtName, topOffset) {
    var el = document.getElementsByClassName(parentElmtName)[0];
    if(typeof el != 'undefined'){
      var parentOffset = $(el).offset().top;
      var value = parentOffset - $(window).scrollTop();

      if (0 > value) {
        var childEl = document.getElementsByClassName(floatElmtName);
        if (!$(childEl).hasClass("floatingChild")){
         var leftOffset = $(childEl).offset().left;
         $(childEl).addClass("floatingChild")
         $(".floatingChild").css({"position": "fixed", "top": topOffset + "px", 
               "left": leftOffset + "px"});
        }
     }
      else {
        var childEl = document.getElementsByClassName(floatElmtName);
        $(childEl).removeClass("floatingChild");
        $(childEl).removeAttr("style");
    }
  }
}
Our HTML looks likes this:
  <div class="parent">
    <div class="child">
      Watch me float
    </div>
  </div>
We call the function when the window is scrolled
  $(window).scroll(function () {
     vertFloatByClass("parent", "child", 20);
  });
View the code in action
Downlod Sample Code

Please feel free to let me know if you recommend any changes to the code.
Shawn Works
Email Me

Wednesday, July 31, 2013

So, I ran into an interesting problem the other day.


I've been part of building HR Auditor for Microsoft Lync these last couple of months. Now, when you turn on archiving, Lync starts dumping all the conversations from its platform into a SQL database. HR Auditor is a front end platform that lets users search for that conversation by keyword, user, and time, or some combination thereof. And that's good, because on a medium to large company, you can end up with forty thousand distinct conversations in a few days -partly because every time you close a window, Lync counts it as you closing your side of a conversation, which writes it out to the logs.

So after I hammered out the installation for Windows Server 2008 and WS 2012 (Using the VS 2010 installer project. Not...not my favorite thing ever), I switched out onto SQL side of things. Now, most of my experience has been with raw SQL, so I was/am a LINQ to SQL newbie. All the extant SQL behind the searching was in LINQ to SQL, so I caught up and tried to find how to implement case insensitive search.

As I found out, by itself LINQ isn't concerned with case. That is to say, it doesn't have a lot of tools for dealing with it, and can't be set to ignore it on a high level. What LINQ does is follow the collation set by SQL database. And Lync (as separate from LINQ, and links...yeah, my office doesn't get confusing at all) sets up LCSLOG, where it stores its conversation logging data, with Latin1_General_BIN as the collation. Latin1_General_BIN is --you guessed it-- case sensitive.

Okay, okay, not a problem, just say (s=&gt;s.UPPER(Body)) and you'll be fine, right? Normally yes, and that was my first impulse. Except I was foiled by Lync again, because all body fields are encoded as NTEXT, which will not accept casting like that.

The person who had been working on the problem before me had tried to solve the problem by dynamically altering the table collation, which worked...and also built up enough logging records to crash our SQL server.
So.
I decided not to do that.

There's a translate method to be had, though. What that does is take an IQueryable (hang on; I'll define it below) and build an IEnumerable out of it, which is what I get back from LINQ in the first place, so I want that back from my modified method, too.

Now, first we have to get the SQL command that LINQ generates, and modify it, which is pretty easy, since it comes back in a string like this:

String dbCommandText = yourDataContext.GetCommand(db.LINQDataModel.Where(Your LINQ ));

To use translate, you've got to have an IQueryable, and an open connection. So open a connection with:

if(System.Data.ConnectionState.Closed == db.Connection.State){ db.Connection.Open();}

So now you've got this string and an open connection. What do you do with it? Well, my need (and probably yours) was to edit the WHERE clause. I ended up doing mine with a regular expression, which was as fun as it sounds. My pattern was pretty simple, it looked like this:

var pattern = Regex.Escape("[") + "t.*" + Regex.Escape("]") + Regex.Escape(".") + Regex.Escape("[") + "Body" + Regex.Escape("]");

That's "find [t(any number of any character)].[Body]". I replaced all the matches with

UPPER(CONVERT(NVARCHAR(MAX), [t(original value)].[Body]))

Which is exactly what I want to have. I've already converted my keyword to upper case (did that in C# before I even passed down the parameter), so now any instance of "cAmEl CaSe" will match "CAMEL CASE" or "camel case" or any mix thereof.

At this point, I've got exactly the SQL command I want to execute, so I've got to get my IQueryable up and running, which we do

Now that you're getting your results back, and they're going into your customer data structure, what you do with your modified text is execute it and then pass the results right back to your handler, just as if you'd called it with straight LINQ. It works like this:

                using (var myReader = mySessions.ExecuteReader())
                {
                    var results = db.Translate(myReader);
                    myCustomDataStructureInstance = results.ToList();     
                }

And there are two parts to that magic. The first is the reader. That's a lot like using System.IO.File.ReadAllText. Only where ReadAllText gets all the text in a file, ExecuteReader gets all the results from the SQL query and packs it into your custom data structure.
"results" is going to be whatever enumerable type you've declared (usually something like a dictionary, for me), and it's actually getting populated when you make the call to Translate, which is what passes the LINQ in.
For my program, there was a little more transformation required before I passed things home, but it worked out.

Let me know how it works for you!

Michael McPherson
(mmcpherson@instant-tech.com)

Wednesday, June 12, 2013

HR Auditor for Lync Beta is Available


After years of 'patching' our Instant Archive Viewer for Lync, it was apparent that the application required a substantial 'rethink'.  As with many applications, Archive Viewer for Lync evolved over many years and transitioned from a historical chat 'viewer' for LCS 2005, through the various OCS releases, and then into the Lync generation.  As with a lot of applications, the evolution of the application created a bunch of strange behaviors, a collection of UI designs, and some frustrating installation and usage scenarios.  In other words, it was difficult for customers to use the product, difficult for the development team to update, and a source of frustration for too many people.

In order to take a completely new approach to the application, and position the application and our development team to respond to the latest customer requests and usage scenarios, we have decided to start with a clean slate to completely rewrite and redesign the application.  Whew!

We are hand crafting this release with a very focused design and development team - and we are spending a lot of time on every area of the product - from the installation, to the query and visualization user interface, and reviewing the complete user experience during every product update. In other words, we really want to get this right!

The beta of the application is available here: http://www.instant-tech.com/hrauditor.cfm

Of course, we are extremely interested in your feedback and product suggestions. 
Our goals for the application are:
  1. Develop an application that is easy to install in less than 30 minutes
  2. Optimize the user interface to quickly perform a set of high value search operations against an existing Lync archive database
  3. Provide tools to save, visualize, and export, a variety of useful queries 
  4. Simplified access control list structure optimized for 5-15 people (the 5-15 folks who will be performing the queries)
We are very interested in your feedback and welcome your questions, suggestions, and product ideas.
Peyton

Wednesday, April 10, 2013

Update to Email to IM Gatewy for Lync Release 1.0.13


We have recently reproduced an error with our email to IM gateway for Microsoft Lync where customers attempt to create email messages from powershell/telnet, when routing through a Domino or Exchange server, rather than connecting directly to the gateway.  In essence, the customers have existing powershell and email creation scripts, and these processes directly access the Exchange server via powershell and/or telnet.

When sending unauthenticated mails in this fashion, it appears that Domino and Exchange tag the FROM smtp command with an AUTH <> parameter, which was creating some issues with address parsing module of our SMTP gateway server.  (For more information on this, see http://www.ietf.org/rfc/rfc2554.txt  particularly section 5.)

We have made some changes to the SMTP layer, and also added some additional error handling in the gateway code to catch this problem, and have tested using both Domino and Exchange with good results.
 
We have also incorporated a feature suggestion to add a setting to disable the message delivery status emails. 



Setting this value to “true” will disable the status emails.  So, with this setting disabled, our gateway will not automatically send a reply back to the author of the email indicating that their message has been dispatched as a Lync IM message.
 
Information on our email to IM gateway for Microsoft Lync is available at:
 

Wednesday, April 3, 2013

Instant Lync BuddyList Viewer 1.0

Here at Instant Technologies, we have been doing a lot of work lately converting IBM Sametime accounts to Microsoft Lync 2010 & 2013 using our product Instant Buddylist Migrator.  It became rather tedious to log in and out of the Lync accounts that we were importing contact lists into - in some cases we are converting tens of thousands of accounts from Sametime to Lync.  Therefore, we decided to leverage our UCMA and Lync development knowledge to create a simple tool that would allow us to inspect large numbers of contact lists quickly.  The result is the Instant BuddyList Viewer.
The Instant BuddyList Viewer application
The same account, viewed in the Lync Client
The Instant BuddyList Viewer connects to your Active Directory domain controller and your Lync Server to display the contact lists for all Lync-enabled users in your organization. As you can see, the left-pane displays a tree view of your Active Directory structure, terminating at the Lync-enabled users.  In the right pane, the selected user's display name and SIP URI are displayed, along with the users and groups in their contact list.  As you can see, the BuddyList Viewer application contains the same contact information.  Additionally, if I wanted to inspect contact lists for another user, I merely have to select their name from the left pane, and the BuddyList Viewer application will retrieve their information - rather than having to sign out of the Lync client, change the sign-in address, and then log in.

Connecting to Lync and Active Directory

Before you can view contact list information, you must first connect to Active Directory and the Lync server.  To do this, select File->Connect...
The connection configuration dialog
You should see the dialog at left pop up.  Enter an LDAP connection string for your Active Directory domain controller.  This should be of the form. LDAP://yourdc.yourdomain.com or LDAP://192.168.1.1.
You may specify the Lync connection information manually, or if you prefer, you may select the TrustedApplication that the BuddyList Viewer will use to connect from a list of available TrustedApplication by selecting the Browse TrustedApps button.  This feature does require that the Lync powershell cmdlets be installed.
TrustedApplication picker
After you have entered your connection information, select the Connect button, and the BuddyList Viewer application will connect to Active Directory and the Lync server, and begin retrieving contact lists.

Installation Prerequisites:

  • UCMA 3.0 Runtime (download link)
  • .Net Framework 4.0
  • A configured Lync TrustedApplicationPool, with at least one available TrustedApplication.  See General Application Activation for a walk-through on setting this up.


Installation Link:

BuddyListViewerInstall-1.0.0.zip



Wednesday, March 27, 2013

Chat logging for Sametime Compliance

Frequently, we encounter customers who are considering developing their own chat logging tool for IBM Sametime, or they are using a product that has been discontinued or is facing 'end of life'.  At Instant, we have been updating our Sametime logging and Sametime compliance application since 2003 and we are happy to announce our latest release.

IMtegrity 5.2, our chat logging solution for IBM Sametime, includes several customer focused enhancements as well as improved support for Sametime 8.5. In our continued commitment to Sametime 8.5.x, IMtegrity 5.2 includes all of our recent improvements for the Sametime 8.5 platform.

Specifically, the 5.2 release also addresses these issues:

Real-time chat rules and disclaimers now work with IBM Sametime configured for LDAP directories.
Added more detailed logging to aid in troubleshooting and customer support.

Fixed an issue with attachment file names containing two exclamation marks, like "theReport!!!.xls."

We continue to see customer interest in the Sametime 8.5 platform and our latest updates to both our installer, and our real time chat rules and disclaimers should provide a compelling platform for customers who require Sametime chat logging.

For our existing customers, we are eager to hear about any new product enhancements or feature requests.

Information on the latest release of our Sametime chat logging solution is available from our IMtegrity area at http://www.instant-tech.com/IMtegrity_Archives.cfm

Thanks for reading - please contact us with any questions or for a free evaluation.

Peyton 

Tuesday, March 26, 2013

Chat Logging for Sametime

We have recently updated IMtegrity, our chat logging solution for IBM Sametime, with several customer focused enhancements.  In our continued commitment to Sametime 8.5.x, IMtegrity 5.2 includes all of our recent improvements for the Sametime 8.5 platform. 
Specifically, the 5.2 release also addresses these issues:

Real-time chat rules and disclaimers now work with IBM Sametime configured for LDAP directories.
Added more detailed logging to aid in troubleshooting and customer support.

Fixed an issue with attachment file names containing two exclamation marks, like "theReport!!!.xls."

We continue to see customer interest in the Sametime 8.5 platform and our latest updates to both our installer, and our real time chat rules and disclaimers should provide a compelling platform for customers who require Sametime chat logging.

For our existing customers, we are eager to hear about any new product enhancements or feature requests.

Information on the latest release of our Sametime chat logging solution is available from our IMtegrity area at
http://www.instant-tech.com/IMtegrity_Archives.cfm 
 

Tuesday, March 5, 2013

Connect your enterprise email and Microsoft Lync environments with Instant’s Email-2-IM Gateway


The @IM - Email-2-IM Gateway for Microsoft Lync improves enterprise email and instant messaging communication and collaboration. 

Businesses consider email and IM as their two most dominant forms of communication and the Email-2-IM Gateway cuts through the clutter and bridges your enterprise email and Microsoft Lync environments.    

The Email-2-IM Gateway merges enterprise email and Microsoft Lync instant messaging with the simple @IM sub-domain.  The Email-2-IM Gateway provides the ability to receive emails using Instant’s gateway server, and dispatch Lync IM messages to individuals, distribution lists, and Lync 2010/2013 group chat rooms.  The application provides a solution to enable existing email notification systems to dispatch messages to Microsoft Lync IM users and Microsoft Lync Group Chat rooms.

“The @IM Gateway is an exciting product launch.  We are connecting email and IM at the server level in order to immediately add value to existing enterprise email and IM deployments”, commented Peyton McManus, President of Instant Technologies.  “Our Portsmouth, NH team has developed a drop in application that uses your existing email client, or email notification system, to send Microsoft Lync IM messages and post content to group chat rooms without added effort.”
 

The Email-2-IM Gateway is a simple drop in application that merges email and Lync environments.  Your existing automated email systems can now send IM’s and post information to chat rooms – including file attachments.  By using your existing email client and adding the @IM sub-domain you can now notify more individuals and groups without added effort and with advanced communications tools.
 


 










Tuesday, January 22, 2013

Instant Compliance Module for IBM Sametime Meetings

Instant Compliance Module for IBM Sametime Meetings

Don't Overlook IBM Sametime Meetings, include it in your compliance architecture!

For IBM Sametime Meetings, Instant offers a compliance module that captures, stores and dispatches electronic content generated within meeting rooms. Data that is stored for archiving and ediscovery includes group chat, minutes, action items, questions and starred items. Instant’s solution integrates with popular compliance applications such as Source One and Autonomy as well as Instant’s IMtegrity archiving database.
  • Data storage for group chat, minutes, action items, questions and starred items
  • Dispatch specific data from IBM Sametime Meeting sessions
  • Stand alone module and easy integration with IMtegrity Archives for Sametime
IBM Sametime Meetings is an on-premise electronic meeting solution that allows attendees to share content, share screens and presentations.

Instant IMtegrity Archives is a leading Sametime application in use at many large financial and insurance institutions. While many organizations are currently archiving instant message (IM) based content, it is now necessary to capture, store and/or dispatch specific content from Sametime Meeting sessions. As users post content within the meeting rooms, specifically the message content that is placed in the discussion area, the data will be identified, formatted and transferred to an internal application such as EMC Source One.

The IBM Sametime Meetings application includes two modules, one that is installed on each Sametime Meeting server and one central application that will assemble and dispatch the message traffic to an internal database. The information from the IBM Meeting Rooms will be delivered as a scheduled data export from the DB2 staging repository.

In order to capture all content that is posted to the discussion area, a specialized data collector will be installed on the Sametime Meeting server. This data collector will implement the necessary IBM Sametime Meeting server compliance APIs and will be responsible for transmitting the content to Instant’s staging repository.

Instant will incorporate this Sametime Meeting Room assembly extension within Instant’s current architecture where both data assemblers and dispatchers reside within defined adapters. This new Meeting Room adapter will fit within Instant’s compliance framework and will be responsible for providing IBM Meeting Room specific data extraction, assembly, and storage.


Wednesday, January 16, 2013

Great article on troubleshooting IBM Sametime Server Crashes

Many of our customers run a variety of IBM Sametime Servers.  Typically, these servers are rock solid, scale to tens of thousands of users, and work very well.  However, if the Sametime server is having issues, then this article from IBM will provide some pragmatic approaches to help troubleshoot the issue.

Troubleshooting_an_IBM_SametimeslaLotus_Domino_server_crash