Getting a Lync Attendee List When Lync Web Access is the Client

When holding Lync meetings with customers who cannot join our meetings through the Lync client they are forced to use the Lync Web Access client, which provides them with a browser based client to join and participate when they otherwise would not be able to do so due to corporate security requirements.

While this works well most of the time there are limitations. I’m not a Lync engineer so I won’t go into other challenges, but this post is to address a single limitation regarding the attendee list. I discovered a while back that OneNote and Lync integrate to the extent that you can get an auto-populated attendee list. The challenge is that when the attendees use LWA, they only show in OneNote as “Guest” as shown below.

OneNote_Lync Integration

So when I need a list of attendees, it works great if everyone is using the full Lync client. When they use LWA, it requires a little more work to get the list. This is where saving conversation history in Outlook really helps and some Powershell.

ConversationHistory

Open the item in Outlook and copy the TO: line.

**NOTE: I have noticed that sometimes you actually get user names here in addition to ‘guest’, but sometimes not. I’m not sure what determines that, but will update if I discover the reasons. If all you see are ‘guest’ entries, then this process will not work as intended.**

Paste this into Notepad (or other text editor and save as rawdata.txt). This will provide you with a semi-colon delimited list of contacts.

Grab the items and pull them into a Powershell variable and then split them into an array of strings

# Read entire list as a single string from the file
$contacts = Get-Content .\rawdata.txt

# Split into an array of strings on the ‘;’ character
$contacts = $contacts.Split(“;”)

Now, you may notice that there are some duplicates due to the way that attendees enter a lync meeting via LWA and there are entries that you obviously do not want such as “Guest” and phone numbers. So we need to parse those out.

# Remove duplicates
$contacts = $contacts | Select –Unique

# Parse out unwanted entries
$newContacts = @()
$contacts | %{ if(($_.ToLower().Contains("guest")) -or ($_.Contains("+1"))){ write-host 'do nothing with' $_} else { write-host 'adding ' $_ ' to new array'; $newContacts = $newContacts + $_}}

You will also notice that saving this conversation history to Outlook has also attempted to resolve each of the remaining names to your local Global Address List (GAL). So in my case everyone shows up as JoeUser@Microsoft.com. This is obviously not ideal when you are working with contacts outside of your organization so we need to remove that information as well.

# Remove incorrect email domains
$newContacts2 = @()
$newContacts | %{$newContacts2 = $newContacts2 + $_.SubString(0, $_.IndexOf("<"))}

What this should leave you is an array of names that you can then put into your meeting notes or send in an email update regarding the meeting attendees.

Now, obviously you shouldn’t need this much automation if your meeting only had a few attendees, but I’ve been hosting some rather large presentations lately (20 – 80 attendees) and manually massaging the attendee list is tedious at best. Hope this helps!

Advertisements

Least Privilege Remote Configuration of Search for SharePoint Server 2013

My customer was going through the process of creating scripts to configure their latest SharePoint Server 2013 (SP2013) farm and encountered an issue while provisioning the Search topology. It was quite a sticking point for them so I began some research. I’ve simplified the scenario in order to make the repro easier and this is my configuration:

  • DC – a Windows Server 2012 VM hosting Active Directory and SQL Server 2012 SP1
  • APP1 – a Windows Server 2012 VM with SP2013 installed, but not configured
  • WFE1 – a Windows Server 2012 VM with SP2013 installed, but not configured

The objective is to create and configure a new SP2013 farm and provision the entire new (non-default) Search topology on APP1 from Powershell scripts running on WFE1. So the steps are basically:

  1. Create farm on WFE1
  2. From APP1, join farm
  3. From WFE1, provision Search entirely on APP1

The part that makes this a bit tricky in addition to the remote provisioning is that it is a least privileged configuration. (I’ve mentioned before how much I love/loathe this type of setup.) So the accounts that I have are:

  • contoso\SetupAcct
  • contoso\SearchSvc
  • contoso\SearchContent

The initial pieces of creating and configuring the farm are rather straight-forward and indeed “old-hat” to a lot of us since we’ve been running with Powershell scripts since the inception of SP2010 so I won’t re-hash that part here; however the piece that was causing a problem with this issue is the remote provisioning of Search. At the point where we create and activate the topology, Powershell returns the following:

Exception calling "Activate" with "0" argument(s): "Timed out waiting for search service 'SPSearchHostController' provisioning timer job to complete"
At C:\Scripts\Provision-Search-Svc-App-and-Topology2.ps1:164 char:4
+    $clone.Activate()
+    ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : TimeoutException

There were also errors in the event log on APP1 (the target search server):

  1. Application: hostcontrollerservice.exe

    Framework Version: v4.0.30319

    Description: The process was terminated due to an unhandled exception.

    Exception Info: System.ServiceModel.CommunicationException

    Stack:

    at Microsoft.Ceres.HostController.WcfServer.WcfService.StartService()

    at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)

    at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)

    at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)

    at System.Threading.ThreadHelper.ThreadStart()

  2. Faulting application name: hostcontrollerservice.exe, version: 15.0.4521.1000, time stamp: 0x519e34b6

    Faulting module name: KERNELBASE.dll, version: 6.2.9200.16451, time stamp: 0x50988aa6

    Exception code: 0xe0434352

    Fault offset: 0x000000000003811c

    Faulting process id: 0x1288

    Faulting application start time: 0x01cf1250582d79f8

    Faulting application path: C:\Program Files\Microsoft Office Servers\15.0\Search\HostController\hostcontrollerservice.exe

    Faulting module path: C:\Windows\system32\KERNELBASE.dll

    Report Id: 9a6eb0a6-7e43-11e3-9411-00155d8d78e4

    Faulting package full name:

    Faulting package-relative application ID:

and in the ULS logs on APP1:

System.ServiceModel.CommunicationException: The service endpoint failed to listen on the URI ‘net.tcp://localhost/ceres/hostcontroller/’ because access was denied. Verify that the current user is granted access in the appropriate allowAccounts section of SMSvcHost.exe.config. —> System.ComponentModel.Win32Exception: Access is denied

at System.ServiceModel.Activation.SharedMemory.Read(String name, String& content)

at System.ServiceModel.Channels.SharedConnectionListener.SharedListenerProxy.ReadEndpoint(String sharedMemoryName, String& listenerEndpoint) –

— End of inner exception stack trace —

at System.ServiceModel.Channels.SharedConnectionListener.SharedListenerProxy.ReadEndpoint(String sharedMemoryName, String& listenerEndpoint)

at System.ServiceModel.Channels.SharedConnectionListener.SharedListenerProxy.HandleServiceStart(Boolean isReconnecting)

at System.ServiceModel.Channels.SharedConnectionListener.SharedListenerProxy.Open(Boolean isReconnecting)

at System.ServiceModel.Channels.SharedConnectionListener.StartListen(Boolean isReconnecting)

at System.ServiceModel.Channels.SharedTcpTransportManager.OnOpenInternal(Int32 queueId, Guid token)

at System.ServiceModel.Channels.SharedTcpTransportManager.OnOpen()

at System.ServiceModel.Channels.TransportManager.Open(TransportChannelListener channelListener)

at System.ServiceModel.Channels.TransportManagerContainer.Open(SelectTransportManagersCallback selectTransportManagerCallback)

at System.ServiceModel.Channels.TcpChannelListener`2.OnOpen(TimeSpan timeout)

at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)

at System.ServiceModel.Dispatcher.ChannelDispatcher.OnOpen(TimeSpan timeout)

at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)

at System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)

at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)

at Microsoft.Ceres.HostController.WcfServer.WcfService.StartServiceEndpoint()

at Microsoft.Ceres.HostController.WcfServer.WcfService.StartService()

at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading.ThreadHelper.ThreadStart() StackTrace:

at Microsoft.Ceres.Diagnostics.Native.dll: (sig=11fe9943-9e8b-44e9-b239-aa54f65e251f|2|microsoft.ceres.diagnostics.native.pdb, offset=A75E)

at Microsoft.Ceres.Diagnostics.Native.dll: (offset=19159)

There has been a lot of research and blog posting on the error from the ULS log, but all that I found simply said “to resolve reboot the server” or “to resolve add the SearchSvc account to the local administrators group”. While both of these did in fact resolve the issue, they didn’t go far enough to actually tell me what was happening. After extensive repros of this issue, the core of the problem can be found in the following blog:

http://blogs.msdn.com/b/joncole/archive/2010/06/10/tcp-port-sharing-access-is-denied.aspx

Basically what happens is that when APP1 joins the SP2013 farm, the config file for Smsvchost.exe gets updated to add the local WSS_WPG group to the AllowAccounts element. At this point, the SearchSvc account is not a member of that group so the security token that the Smsvchost service (TcpPortSharing) has for this group does not include it. As Jon states in his blog referenced above, basically this service (and any other service that depends on this service) needs to be restarted to get a new security token for the WSS_WPG group. My new steps to resolve this are as follows:

  • On WFE1, create the farm.
  • On APP1, join the farm and execute “net start sptimerv4” since the timer service is not started through this process.
  • On WFE1, run the search provisioning script up to the point where the Search service instance on APP1 is started.
  • On APP1, “net stop/start” each of the following:

    • NetTcpActivator
    • NetPipeActivator
    • NetTcpPortSharing

  • On WFE1, complete the Search configuration and activate the new topology.

In Powershell terms, that means to either use PS Remoting to run the net stop/start commands immediately after the call to Start-SPEnterpriseSearchServiceInstance for APP1 or to insert a pause so you can go directly to APP1 to make restart the services.

Initial post…

Hi all, this is a new blog for my professional endeavors. The title of the blog – ITGrinder – is a reflection on how my life in IT goes. If you subscribe or frequent this blog you’ll see a variety of posts around my work life and some of the non-corporate things I’m working on. For the most part this blog is for me as a reference to things I need to remember and can refer back to from time to time.

At the time of this writing I’m an employee of Microsoft Corporation as a Premier Field Engineer and you can find my MS blog at http://blogs.msdn.com/b/briangre. Most of what I post there will be duplicated here, but there will be additional posts here that are related to my journey around becoming a better developer and possible getting into app development as a hobby.

Let me know if you have questions or comments and I’d be happy to expand on anything that is unclear and sometimes you may even get me to change my mind… maybe.