How to enable last name, first searches in Lync Address Book

Over time, a few people have asked me why, given someone with a first name of Ziggy and a last name of Zooga, you will see the following results when searching using the Lync Address Book.

Ziggy – success

Zooga – success

Ziggy Zooga – success

Zooga Ziggy – failure

Shouldn’t we be able to find this when searching? Alas, this is not the way things work. However, there is a way to fix this.

Now before I go into details I must first provide a disclaimer. This workaround uses a completely undocumented and unsupported feature.

That being said, let’s get to the fun part. First, a bit of explanation on how we process attributes is necessary. When the address book processes an entry, it iterates through each attribute that it is interested in (which itself is configurable using a resource kit utility) and reads the value. It then may choose to break up the value. Some values, such as displayName, are just passed as is to the next step. A few attributes, however, are handled specially. One example of this is msRTCSIP-PrimaryUserAddress. Given a Sip address, we expect the following searches to be possible.

sip:ziggy@mydomain.com

ziggy@mydomain.com

ziggy

To do this, we use regular expressions to pull the value apart into the above pieces.

The finally piece involves using normalization rules to apply formatting to phone numbers. This is detailed in a number of other blogs. As you can guess, if there is a way to pull apart names, we may be able to use it to our advantage.

The way to do this is via a file called RtcAbAttributeIndexRules.xml. This file exists in the server\core directory on each front end and you will need to restart RtcSrv if you change it. The main reason for this is we never intended for you to need to change this file.

If you open this file, you will see an attributes node at the top and a charDigitMaps node towards the bottom. In this post, I will only cover the attributes node. Each of the attributes under the attributes node tells us how to pull apart a particular attribute. Most of these are “as-is” or in other words we change nothing.

<attribute name="sn">
<indexRules>
<indexRule id="sn_rule_01">
<description>
<![CDATA[
Index sn as-is
]]>
</description>
<valuePattern>
<![CDATA[^.*$]]>
</valuePattern>
<indexValues>
<indexValue>
<![CDATA[$0]]>
</indexValue>
</indexValues>
</indexRule>
</indexRules>
</attribute>

You can see that the regular expression for valuePattern will match anything passed in.  The index value, in turn, says to use the entire match (group 0) so the result is the string is unchanged.  Now compare this to what we do for the Sip address.

<attribute name="msRTCSIP-PrimaryUserAddress">
<indexRules>
<indexRule id="msRTCSIP-PrimaryUserAddress_rule_01">
<description>
<![CDATA[
Index msRTCSIP-PrimaryUserAddress as-is
]]>
</description>
<valuePattern>
<![CDATA[^.*$]]>
</valuePattern>
<indexValues>
<indexValue>
<![CDATA[$0]]>
</indexValue>
</indexValues>
</indexRule>
<indexRule id="msRTCSIP-PrimaryUserAddress_rule_02">
<description>
<![CDATA[
"sip:user@domain.com" =>
"user"
"user@domain.com"
]]>
</description>
<valuePattern>
<![CDATA[(\w+)\:(.+)@(.+)]]>
</valuePattern>
<indexValues>
<indexValue>
<![CDATA[$2]]>
</indexValue>
<indexValue>
<![CDATA[$2@$3]]>
</indexValue>
</indexValues>
</indexRule>
</indexRules>
</attribute>

Here we index the value of msRTCSIP-PrimaryUserAddress as is, but we have another index rule that has a regular expression to remove the sip: (or whatever precedes the colon) from the address.  We then reference the regular expression groups (the sip: is group 1) to add values without the protocol and without the domain.

If you think this may work for last name, first name searches, you would be partially correct.  This will work, but is a limited solution.  First I will explain the solution, then I will discuss its limitations.  The following is one way to handle displayName.

<attribute name="displayName">
<indexRules>
<indexRule id="displayName_rule_01">
<description>
<![CDATA[
Index displayName as-is
]]>
</description>
<valuePattern>
<![CDATA[^.*$]]>
</valuePattern>
<indexValues>
<indexValue>
<![CDATA[$0]]>
</indexValue>
</indexValues>
</indexRule>
<indexRule id="displayName_rule_02">
<description>
<![CDATA[
Handle last name first name searches
]]>
</description>
<valuePattern>
<![CDATA[(.+)(\s+)(.+)]]>
</valuePattern>
<indexValues>
<indexValue>
<![CDATA[$3 $1]]>
</indexValue>
</indexValues>
</indexRule>
</indexRules>
</attribute>

Here, you can see that we are simply looking for one or more white space characters between the names and reversing it.  There is no need to add an index for [$1 $3] because the first index rule will cover that.  If you try this out, you will see that “Zooga Ziggy” is now searchable.

Of course, there are limitations to this approach.  Besides the pain of setting this on every front end and restarting the service there, the solution is limited by the following.

1) This will obviously only work for two names.  You can, of course, create additional rules for three and four names, but at some point a user will simply have too many names.

2) This approach will not work for languages that do not use spaces.  In particular Chinese and Thai have this issue and the solution will not work there.

Nevertheless, this solution can be used to improve searches in your organization, even if it does not cover every name.