Lync call handling script updated

Articles in the "Lync call handling script" series

  1. Trigger something whenever you’re on a Lync call
  2. Update to Lync call handling script
  3. Lync call handling script updated [This article]

The call handling script has been updated to version 1.4.4.  It resolves an issue that took me quite awhile to diagnose, where it would sometimes hang when signing out of Lync.  When the client state change event occurs, the corresponding function would check if you are signed in or not.  If not, it will unregister the off-hook handler event so it can be created anew when you sign in again.

The problem was because the client state change event fires multiple times when you sign out: once when signing out and again when you are signed out.  Since I was running the cmdlet to unregister the off-hook handler for any state that wasn’t equal to being signed in, the client state handler function was being called again (and attempting to unregister the off-hook handler event) before it could finish doing so the first time.  This caused the PowerShell session to hang.  The script now only unregisters the event if you are signed out, rather than on any event that isn’t equal to being signed in.

The script has also been updated to work with Lync 2013.  You will want to comment line 62 (and uncomment 63) if you are using Communicator or Lync 2010, or leave it as is if you are using Lync 2013.

  Monitor-LyncSelfActivityChange.zip (640.5 KiB)

Update to Lync call handling script

Articles in the "Lync call handling script" series

  1. Trigger something whenever you’re on a Lync call
  2. Update to Lync call handling script [This article]
  3. Lync call handling script updated

Yesterday I discovered that my Lync call handling script breaks when the Lync client is closed. I thought I had tested it and found that the client didn’t actually need to be running for the Lync client class object to be created and used. Not only was I wrong about that, but the existing client class object becomes invalid when Lync is closed. I suppose it isn’t that big of a deal to say that you need to rerun the script if you close Lync (and not run it until after Lync has been started), but I took it as a challenge to overcome that need.

I need to know both when Lync has started and when it has been closed. I started with the ClientState enumeration used in the LyncClient class. Since I already have an event for the client state, I figured I could act when the ShuttingDown state occurs. Uh, no, since that state never happens when Lync is running as an interactive process, but only when you are programmatically running Lync without a UI. The client state becomes Invalid when Lync is closed, so then I thought I could act when that state occurs. Also no, since an event doesn’t fire when the state becomes Invalid. I am now forced to look outside of the Microsoft.Lync.Model namespace.

What I decided to do is monitor the Lync process itself. I moved the creation of the client object into a function that can be called as needed, instead of just the one time when the script is run.

function Connect-LyncClient
	{
	$i = 1
	do
		{
		#Attach to Lync process, if running
		$lyncProcess = [System.Diagnostics.Process]::GetProcessesByName('communicator')
		if ($lyncProcess.Length -eq 0) #Process is not running
			{
			if ($i -eq 1) #Report status only on first attempt
				{
				Write-Host "$((Get-Date).ToLongTimeString()) - Waiting for Lync process to start (15-second intervals)..." -ForegroundColor Yellow
				}
			$i++
			Start-Sleep 15
			}
		}
	until ($lyncProcess.Length -eq 1)

	#Register for when Lync process exits
	Register-ObjectEvent -InputObject $lyncProcess[0] -EventName "Exited" -SourceIdentifier "LyncProcessHandler" -Action {LyncProcess-Handler} | Out-Null

	#Wait for client object initialization to complete
	do
		{
		$global:client = [Microsoft.Lync.Model.LyncClient]::GetClient()
		}
	while ($client.State -eq [Microsoft.Lync.Model.ClientState]::Invalid)
	}

The function checks to see if the communicator.exe process is running. If not, it will check every 15 seconds to see if it is. When the process has started, an event is registered that will fire when the process has exited. Now that the process has started, the client can be created. The LyncProcess-Handler function that is called by the event action unregisters the script events and then calls the above function to start the monitoring of the Lync process again. This way the script only has to be run once and it will accommodate whether Lync isn’t running in the first place and if it closes later.

The original code that creates the initial event registrations for contact activity and client state have also been moved into a function so it can be called again whenever the Lync process is started.

The script has been updated (available below), as well as the full version that is included in the body of the first post.

  Monitor-LyncSelfActivityChange.zip (640.5 KiB)

Trigger something whenever you’re on a Lync call

Articles in the "Lync call handling script" series

  1. Trigger something whenever you’re on a Lync call [This article]
  2. Update to Lync call handling script
  3. Lync call handling script updated

A coworker of mine works out of his home office and wanted his family to know when he is on a phone call so they won’t interrupt him. He already had a network-controllable light that he put into the hallway outside the office. What he needed was a way to trigger the light when he is on the phone (he is enterprise voice-enabled in Lync). Here are the details of the PowerShell script that I wrote for him.

The script leverages the API in the Lync SDK. You only need to install the Lync SDK runtime library, but there isn’t an individual download for that. To install the Lync SDK, however, requires that Visual Studio is installed. That may not be the case for many of you, so I have included the redistributable runtime library (a mere 750KB MSI) in the script download.

The runtime library installs the assemblies into the global access cache (GAC), so to use one of them in a script, you need to load the assembly:

$apiPath = "C:\Windows\assembly\GAC_MSIL\Microsoft.Lync.Model\4.0.0.0__31bf3856ad364e35\Microsoft.Lync.Model.dll"
Add-Type -Path $apiPath

You can then create an object for the Lync client:

$client = [Microsoft.Lync.Model.LyncClient]::GetClient()

In order to know someone’s current presence state (or activity in Lync parlance), you work against a contact object. Therefore, to know your own activity, you first need to create an object for yourself as a contact:

$selfContact = $client.Self.Contact

To get your current activity, you use the GetContactInformation() method, with the argument being the type of information to retrieve:

$selfContact.GetContactInformation([Microsoft.Lync.Model.ContactInformationType]::Activity)

The activities that indicate you are on the phone, or off-hook in telecom parlance, are In a call and In a conference call. To use this information to do something, you need to know when Lync changes to and/or from these activities, and for that you can use .NET Framework object events. Instead of constantly querying for your activity, you can use events to have PowerShell act when something happens. The Contact class has an event for when contact information changes, and here is the list of what properties will result in that event firing.

To register for an event, you have to include the object whose class contains the event, as well what action to take:

Register-ObjectEvent -InputObject $selfContact -EventName "ContactInformationChanged" -SourceIdentifier "OffHookHandler" -Action {Offhook-Handler $event}

$event is one of the built-in variables that the event can include in the response. And this is the function that will run as a result of the event firing:

function Offhook-Handler ($event)
	{
	#Act if what has changed is activity
	if ($event.SourceEventArgs.ChangedContactInformation -contains 'Activity')
		{
		$newActivity = $selfContact.GetContactInformation([Microsoft.Lync.Model.ContactInformationType]::Activity)
		#Act only on true activity change
		if ($newActivity -ne $currentActivity)
			{
			#Act if off- or on-hook
			if ($newActivity -eq 'In a call' -or $newActivity -eq 'In a conference call')
				{
				if ($offhook -eq $false) #Only run off-hook action if not already on a call
					{
					Write-VerboseEvent $newActivity
					OffHook-Action 'offhook'
					$global:offhook = $true #Stateful tracking of status in successive changes
					}
				}
			else
				{
				if ($offhook)
					{
					OffHook-Action 'onhook'
					Write-VerboseEvent "No longer on the phone ($newActivity)"
					$global:offhook = $false
					}
				else
					{
					Write-VerboseEvent "Non-phone activity change: $newActivity"
					}
				}
			#Global variable provides stateful tracking of activity change
			$global:currentActivity = $newActivity
			}
		}
	}

Since the list of changed properties that triggers a changed contact event is numerous, the first thing the function does is check to see if the changed property is Activity. The event doesn’t include what the activity is, so you have to get the current activity. If the activity is one of the values that indicates you are on the phone, it calls a function to run your code that does something. (I did it this way, instead of having you put your code inside this function, so that it is easier to paste your code into a function at the beginning of the script and for doing so if there are future versions of the script.) A global variable of $offhook is set. This allows the script to keep track of whether you are off the phone, after having been on the phone, in subsequent event firings. If you are no longer on the phone, after having been on the phone, it calls another function to run your code that “undoes” whatever it did when you got on the phone.

The script will normally not output anything to the screen, allowing you to run it in a shell you are using for something else. If you are testing your code or just want to see more information about your activity changes, you can include the -Verbose parameter. This will result in more details being output to the screen as they occur, such as current activity, functions being called, and whether the client is signed in. If you do this, you will likely want to run the script in its own shell so it doesn’t get in the way of what you might be doing in the first shell.

Now, to put all of this into a reusable script, there are other things to consider. If you sign out, or get otherwise disconnected from Lync, the contact object is no longer valid and has to be created again when you sign in. To account for this, I use another event that fires on client state changes. If not signed in, it unregisters the contact change event, and when you get signed in again it creates the contact object and registers the event.

Another consideration is the scope of the script. When you run a script, its code is run in its own scope, terminating when the script reaches the end. Since the registered events will call functions after the script has completed, they need to be run in a scope that stays resident upon completion, i.e., global scope. To do this, you dot-source the script by entering a period and a space before the path to the script. To account for this, the script checks to ensure that it was dot-sourced when executed, otherwise it presents an error.

As for using the -Verbose parameter, I found that it worked fine when using Write-Verbose in the script’s main block and initial functions. It did not work in the functions called by the events. The function would just hang at the point of running the cmdlet. I have not been able to find out why this is the case, so I wrote a function to mimic the output of the Write-Verbose cmdlet. This function is used in the functions called by the events.

To make use of this script in your environment, you will want to modify the first function, called OffHook-Action, by replacing lines 13 and 19 with code to “do” something and “undo” something, respectively. If those lines of code are dependent on other functions, you can put them in the subsequent region that is labeled for your dependent functions.

As a side note, since I don’t have a network-controllable light at work, I needed something else for it to do so I could be sure it was running correctly. I use Winamp, so it was appropriate for me to have it pause when I am on the phone, and resume if I am off the phone (but only if it is already paused). The code in the script, therefore, includes functions for controlling Winamp and reference to a COM object the provides this. I left it in so you can see an example of the custom code that you will need to put in your version. It is worth nothing that the COM object for controlling Winamp is a 32-bit library. If you are running PowerShell on a 64-bit system and reference a library that is only compiled for a 32-bit system, you will need to run the x86 version of PowerShell (there is a Start Menu item for it). (The Lync SDK runtime is accessible in both versions of the shell.)

The download of the full version of the script (and the Lync SDK runtime library) is below, but you can also see the full script by expanding the code block below:

  Monitor-LyncSelfActivityChange.zip (640.5 KiB)

#Take an action when Lync presence indicates you are on a phone call
#v1.4.4 5/30/13

[CmdletBinding()]
param()

#Put your custom code in this function in response to being on or off the phone
function OffHook-Action ($action)
	{
	if ($action -eq 'offhook')
		{
		#Do something
		Pause-Winamp
		Write-VerboseEvent "Off-hook action executed"
		}
	else
		{
		#Undo something
		Play-Winamp
		Write-VerboseEvent "On-hook action executed"
		}
	}

#Region Your dependent function(s) for off-hook actions
$winamp = New-Object -ComObject ActiveWinamp.Application

function Play-Winamp
	{
	#Play only if Winamp currently paused
	if ($winamp.PlayState -eq 3)
		{
		$winamp.Play()
		}
	}
	
function Pause-Winamp
	{
	#Pause only if Winamp currently playing
	if ($winamp.PlayState -eq 1)
		{
		$winamp.Pause()
		}
	}
#EndRegion	

#Region Core functions
function Write-VerboseEvent ($text)
	{
	if ($verboseEvent)
		{
		Write-Host "VERBOSE: $((Get-Date).ToLongTimeString()) - $text" -ForegroundColor Yellow -BackgroundColor Black
		}
	}

function Connect-LyncClient
	{
	$i = 1
	do
		{
		#Attach to Lync process, if running
		#Choose 'lync' line for 2013, 'communicator' line for 2010
		$lyncProcess = [System.Diagnostics.Process]::GetProcessesByName('lync')
		#$lyncProcess = [System.Diagnostics.Process]::GetProcessesByName('communicator')
		
		if ($lyncProcess.Length -eq 0) #Process is not running
			{
			if ($i -eq 1) #Report status only on first attempt
				{
				Write-Host "$((Get-Date).ToLongTimeString()) - Waiting for Lync process to start (15-second intervals)..." -ForegroundColor Yellow
				}
			$i++
			Start-Sleep 15
			}
		}
	until ($lyncProcess.Length -eq 1)

	#Register for when Lync process exits
	Register-ObjectEvent -InputObject $lyncProcess[0] -EventName "Exited" -SourceIdentifier "LyncProcessHandler" -Action {LyncProcess-Handler} | Out-Null

	#Wait for client object initialization to complete
	do
		{
		$global:client = [Microsoft.Lync.Model.LyncClient]::GetClient()
		}
	while (-not $client -or $client.State -eq [Microsoft.Lync.Model.ClientState]::Invalid)
	}

function Register-ContactChange
	{
	#Create self object as a contact
	$global:selfContact = $client.Self.Contact
	#Register for contact changes
	Register-ObjectEvent -InputObject $selfContact -EventName "ContactInformationChanged" -SourceIdentifier "OffHookHandler" -Action {Offhook-Handler $event} | Out-Null
	#Get initial off-hook status and set state variable
	$activity = $selfContact.GetContactInformation([Microsoft.Lync.Model.ContactInformationType]::Activity)
	if ($activity -eq 'In a call' -or $activity -eq 'In a conference call')
		{
		$global:offhook = $true
		}
	else
		{
		$global:offhook = $false
		}
	}

function Register-ClientStateChange
	{
	#Register for sign-in changes
	Register-ObjectEvent -InputObject $client -EventName "StateChanged" -SourceIdentifier "ClientStateHandler" -Action {ClientState-Handler $event} | Out-Null
	}

function Offhook-Handler ($event)
	{
	#Act if what has changed is activity
	if ($event.SourceEventArgs.ChangedContactInformation -contains 'Activity')
		{
		$newActivity = $selfContact.GetContactInformation([Microsoft.Lync.Model.ContactInformationType]::Activity)
		#Act only on true activity change
		if ($newActivity -ne $currentActivity)
			{
			#Act if off- or on-hook
			if ($newActivity -eq 'In a call' -or $newActivity -eq 'In a conference call')
				{
				if ($offhook -eq $false) #Only run off-hook action if not already on a call
					{
					Write-VerboseEvent $newActivity
					OffHook-Action 'offhook'
					$global:offhook = $true #Stateful tracking of status in successive changes
					}
				}
			else
				{
				if ($offhook)
					{
					OffHook-Action 'onhook'
					Write-VerboseEvent "No longer on the phone ($newActivity)"
					$global:offhook = $false
					}
				else
					{
					Write-VerboseEvent "Non-phone activity change: $newActivity"
					}
				}
			#Global variable provides stateful tracking of activity change
			$global:currentActivity = $newActivity
			}
		}
	}
	
function ClientState-Handler ($event)
	{
	#Get current client state
	$newState = $event.SourceEventArgs.NewState
	if ($newState -eq 'SignedIn')
		{
		Register-ContactChange
		Write-VerboseEvent "Activity changes now being monitored."
		}
	elseif ($newState -eq 'SignedOut') 
		{
		$subscriptionSource = Get-EventSubscriber | Select-Object -ExpandProperty SourceIdentifier
		if ($subscriptionSource -contains "OffHookHandler")
			{
			#If subscription currently is registered, remove it so it can
			#be successfully created again when signed in
			Unregister-Event OffHookHandler
			Write-VerboseEvent "Activity changes will be monitored when the client signs in."
			}
		}
	}

function LyncProcess-Handler
	{
	Write-Host "$((Get-Date).ToLongTimeString()) - Lync client has shut down." -ForegroundColor Yellow
	#Client object is invalid if Lync process stops
	Stop-Monitoring
	
	#Restart connection and registration steps
	Connect-LyncClient
	Initialize-Registration
	}

function Initialize-Registration
	{
	#Register for contact changes if client is already signed in
	if ($client.State -eq [Microsoft.Lync.Model.ClientState]::SignedIn)
		{
		Register-ContactChange
		Write-Host "$((Get-Date).ToLongTimeString()) - Activity changes now being monitored." -ForegroundColor Green
		Register-ClientStateChange
		}
	#Register for client changes, which will handle contact change registration
	else
		{
		Register-ClientStateChange
		Write-VerboseEvent "Activity changes will be monitored when the client has signed in."
		}
	}

function Stop-Monitoring
	{
	Unregister-Event OffHookHandler -ErrorAction SilentlyContinue
	Unregister-Event ClientStateHandler -ErrorAction SilentlyContinue
	Unregister-Event LyncProcessHandler -ErrorAction SilentlyContinue
	Write-VerboseEvent "Events unregistered"
	$global:client = $null
	$global:lyncProcess = $null
	$global:currentActivity = $null
	}

#EndRegion

#Region Script body

#Check for dot sourcing
if ($MyInvocation.InvocationName -ne '.')
	{
	Write-Error "Script was not dot-sourced.  This script is designed to be executed by dot sourcing it: . " -Category InvalidOperation
	break
	}

#Check for SDK installation
$apiPath = "C:\Windows\assembly\GAC_MSIL\Microsoft.Lync.Model\4.0.0.0__31bf3856ad364e35\Microsoft.Lync.Model.dll"
if (Test-Path $apiPath)
	{
	Add-Type -Path $apiPath
	#Connect to local Lync client
	Connect-LyncClient
	}
else
	{
	Write-Error "This script requires the Lync SDK runtime library." -Category NotInstalled
	break
	}

#Check for Verbose parameter for event functions
if ($MyInvocation.BoundParameters['verbose'])
	{
	$global:verboseEvent = $true
	}
else
	{
	$global:verboseEvent = $false
	}
	
#Start event registration
Initialize-Registration

#EndRegion

TechEd 2011: WP7 Mango update a snoozer

Microsoft announced more details of the Windows Phone 7 update (Mango), due out later this year.  They hyped the enterprise features, which are nothing to be proud of, IMO.  Support for searching you server-side mailbox.  Already in WM 6.x?  Check.  Support for IRM.  Already in WM 6.x?  Check.  Lync client. Communicator Mobile for WM 6.x to connect to OCS already?  Check.  A mobile client for Lync should have been released with Lync server RTM.  Conversation views.  Already in WM 6.x?  Check.

Where is the at-rest device encryption?  It is already a cliche to say that it is ironic when Microsoft’s own mobile OS doesn’t support all of their Exchange ActiveSync policies, one of them being the very thing whose absence will keep the OS out of the enterprise.  Because my company deals with PHI/PII, we require at-rest encryption, which means the only devices that we allow via EAS are iDevices, Android with Touchdown installed, and Windows Mobile 6.x.

One may argue that WP7 is a consumer device.  But MS has abandoned everything for WM 6.x.  They may say that WP7 is a consumer device, but they treat as their one-and-only OS for home and business.  They didn’t even release a TechEd app for WM for 6.x, only WP7 (and Android).  So if you release an app for 10,000 enterprise geeks at your premier technical conference for IT Pros and Developers on only your consumer-targeted mobile OS, what message are you really sending about who you target audience is?