Skip to main content

Update Lync Notes With Twitter Status

·5 mins

Tired of seeing What’s happening now? in the Lync client. Sick of seeing the same old status update because you or your colleagues never pulled down that holiday message or #GoHawks update? Well, I was sick of it. I wanted a quick and dirty way to automate updating the Lync status message (aka ProfileNote).

After hours of scouring the internet and messing with multiple twitter libraries and outdated twitter code, I ran across this gem by Github: MyTwitter.psm1. While it was meant to post to twitter, a bit of hacking and this was born. Coupled with some other Lync Client work I’ve done, I had a workable solution.

This of course, wouldn’t be complete with a working example. All you need is to create the Oauth tokens on twitter, setup a scheduled task, and you’ll be auto updating Lync with everyone’s favorite infosec professional, Infosec Taylor Swift (@SwiftOnSecurity).

Steps:
#

  1. Go to https://apps.twitter.com/app/new and fill in the basic required information.
  2. Once created, click on the “Keys and Access Tokens” menu item
  3. At the bottom under “Token Actions”, select “Create my access token”
  4. Copy the tokens into the script.

Note:

  • The secret tokens are sensitive. Be like Taylor and protect your secrets.
  • Your app permissions only need to be read-only. Be like Taylor and follow the principle of least privilege.
#requires –Version 3.0
<#
.SYNOPSIS 
Sets Lync 2013 Client's PersonalNote field with latest tweet from your favorite twitter personality:
@SwiftOnSecurity

.DESCRIPTION
Tired of What's happening today? Find out with the Set-LyncNoteWithTwitter.ps1 script. It sets the 
Lync 2013 Client's personal note to match the latest tweet from your favorite twitter personality. 
Authentication and authorization are handled throughTwitter's Oauth implementation. Everything else is 
via their REST API. The Lync COM is used to update the Lync client.

The secret tokens are sensitive. Be like Taylor and protect your secrets.
Your app permissions only need to be read-only. Be like Taylor and follow the principle of least privilege. 

****Requires Lync 2013 SDK.**** The SDK install requires Visual Studio 2010 SP1. To avoid installing 
Visual Studio, download the SDK, use 7-zip to extract the files from the install, and install the MSI 
relevant to your Lync Client build (x86/x64).

.INPUTS
None. You cannot pipe objects to Set-LyncNoteWithTwitter.ps1.

.OUTPUTS
None. Set-LyncNoteWithTwitter.ps1 does not generate any output.

.NOTES
Author Name:   Andrew Healey (@healeyio)
Creation Date: 2015-02-02
Version Date:  2015-02-02

.LINK
Author:          https://healey.io/writing/update-lync-notes-with-twitter-status/
Lync 2013 SDK:   http://www.microsoft.com/en-us/download/details.aspx?id=36824
Some code referenced from:
   MyTwitter:    https://github.com/MyTwitter/MyTwitter

.EXAMPLE
PS C:\PS> .\Set-LyncNoteWithTwitter.ps1

#>

## Parameters
[string]$Consumer_Key =        'abcdefghijklmnopqrstuvwxyz'
[string]$Consumer_Secret =     'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'
[string]$Access_Token =        'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'
[string]$Access_Token_Secret = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst'
[string]$screen_name =         'SwiftOnSecurity'
[int]   $count =                1
[string]$exclude_replies =     'true'
[string]$include_rts =         'false'
[string]$HttpEndPoint =        'https://api.twitter.com/1.1/statuses/user_timeline.json'

[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("System.Net") | Out-Null

## Generate a random 32-byte string. Strip out '=' per twitter req's
$OauthNonce = [System.Convert]::ToBase64String(([System.Text.Encoding]::ASCII.GetBytes("$([System.DateTime]::Now.Ticks.ToString())12345"))).Replace('=', 'g')
Write-Verbose "Generated Oauth none string '$OauthNonce'"
			
## Find the total seconds since 1/1/1970 (epoch time)
$EpochTimeNow = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null)
Write-Verbose "Generated epoch time '$EpochTimeNow'"
$OauthTimestamp = [System.Convert]::ToInt64($EpochTimeNow.TotalSeconds).ToString();
Write-Verbose "Generated Oauth timestamp '$OauthTimestamp'"
			
## Build the signature
$SignatureBase = "$([System.Uri]::EscapeDataString($HttpEndPoint))&"
$SignatureParams = @{
	'oauth_consumer_key' =     $Consumer_Key;
	'oauth_nonce' =            $OauthNonce;
	'oauth_signature_method' = 'HMAC-SHA1';
	'oauth_timestamp' =        $OauthTimestamp;
	'oauth_token' =            $Access_Token;
	'oauth_version' =          '1.0';
}

## Add Signature Params
$SignatureParams.screen_name =     $screen_name
$SignatureParams.exclude_replies = $exclude_replies
$SignatureParams.include_rts =     $include_rts
$SignatureParams.count =           $count
			
## Create a string called $SignatureBase that joins all URL encoded 'Key=Value' elements with a &
## Remove the URL encoded & at the end and prepend the necessary 'POST&' verb to the front
$SignatureParams.GetEnumerator() | sort name | foreach { 
    Write-Verbose "Adding '$([System.Uri]::EscapeDataString(`"$($_.Key)=$($_.Value)&`"))' to signature string"
    $SignatureBase += [System.Uri]::EscapeDataString("$($_.Key)=$($_.Value)&".Replace(',','%2C').Replace('!','%21'))
}
$SignatureBase = $SignatureBase.TrimEnd('%26')
$SignatureBase = 'GET&' + $SignatureBase
Write-Verbose "Base signature generated '$SignatureBase'"
			
## Create the hashed string from the base signature
$SignatureKey = [System.Uri]::EscapeDataString($Consumer_Secret) + "&" + [System.Uri]::EscapeDataString($Access_Token_Secret);
			
$hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
$hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($SignatureKey);
$OauthSignature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($SignatureBase)));
Write-Verbose "Using signature '$OauthSignature'"
			
## Build the authorization headers using most of the signature headers elements.  This is joining all of the 'Key=Value' elements again
## and only URL encoding the Values this time while including non-URL encoded double quotes around each value
$AuthorizationParams = $SignatureParams
$AuthorizationParams.Add('oauth_signature', $OauthSignature)
			
## Remove any REST API call-specific params from the authorization params
$AuthorizationParams.Remove('exclude_replies')
$AuthorizationParams.Remove('include_rts')
$AuthorizationParams.Remove('screen_name')
$AuthorizationParams.Remove('count')
			
$AuthorizationString = 'OAuth '
$AuthorizationParams.GetEnumerator() | sort name | foreach { $AuthorizationString += $_.Key + '="' + [System.Uri]::EscapeDataString($_.Value) + '",' }
$AuthorizationString = $AuthorizationString.TrimEnd(',')
Write-Verbose "Using authorization string '$AuthorizationString'"

## Build URI Body
$URIBody = "?count=$count&exclude_replies=$exclude_replies&include_rts=$include_rts&screen_name=$screen_name"
Write-Verbose "Using GET URI: $($HttpEndPoint + $Body)"
$tweet = Invoke-RestMethod -URI $($HttpEndPoint + $URIBody) -Method Get -Headers @{ 'Authorization' = $AuthorizationString } -ContentType "application/x-www-form-urlencoded"

## Verify lync 2013 object model dll is either in script directory or SDK is installed
$lyncSDKPath = "Microsoft Office\Office15\LyncSDK\Assemblies\Desktop\Microsoft.Lync.Model.dll"
$lyncSDKError = "Lync 2013 SDK is required. Download here and install: http://www.microsoft.com/en-us/download/details.aspx?id=36824"

if (-not (Get-Module -Name Microsoft.Lync.Model)) {
    if (Test-Path (Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath $lyncSDKPath)) {
        $lyncPath = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath $lyncSDKPath
    }
    elseif (Test-Path (Join-Path -Path ${env:ProgramFiles} -ChildPath $lyncSDKPath)) {
        $lyncPath = Join-Path -Path ${env:ProgramFiles} -ChildPath $lyncSDKPath
    }
    else {
        $fileError = New-Object System.io.FileNotFoundException("SDK Not Found: $lyncSDKError")
        throw $fileError
    } # End SDK/DLL check
    try {
        Import-Module -Name $lyncPath -ErrorAction Stop
    }
    catch {
        $fileError = New-Object System.io.FileNotFoundException ("Import-Module Error: $lyncSDKError")
        throw $fileError
    } # End object model import
} # End dll check

## Check if Lync is signed in, otherwise, nothing to do
$Client = [Microsoft.Lync.Model.LyncClient]::GetClient()
if ($Client.State -eq "SignedIn") {
    ## Set PersonalNote in Lync
    $LyncInfo = New-Object 'System.Collections.Generic.Dictionary[Microsoft.Lync.Model.PublishableContactInformationType, object]'
    $LyncInfo.Add([Microsoft.Lync.Model.PublishableContactInformationType]::PersonalNote, "@$($screen_name): $($tweet.text)")
    $Self = $Client.Self
    $Publish = $Self.BeginPublishContactInformation($LyncInfo, $null, $null)
    $Self.EndPublishContactInformation($Publish)
}
else {
    Write-Warning "Lync must be signed in."
} # End client sign-in check

Related

Update Lync Client Location with IP GeoLocation

·3 mins
I regularly bounce around on different networks and vpn connections. I got tired of manually setting the location in Lync and found myself just ignoring it altogether. After doing some poking around, I decided to throw a powershell script together to just do the dirty work for me. The script uses Telize for geoip data and DNSOMatic Telize for the external IP. The script requires the Microsoft.Lync.Model.dll from the Lync 2013 SDK (15.0.4603.1000 as of this post). You can find the Lync Client 2013 SDK here.

C# Express - Create a Dummy or Placeholder Windows Service for Monitoring

The IT ecosystem is rich with network monitoring systems (NMS). Each NMS has different capabilities, costs, and purposes in life. It is commonplace for me to come into a business that has invested in an NMS that doesn’t fit all their needs. You might ask, “What does this have to do with creating a Windows service?” Here is the scenario that brought this up. A client has a monitoring solution for their Windows servers and some basic network up/down stats. Their internet connection had been flaky for a month or two. As we worked with their ISP, their connection continued to stay up but latency would spike and often drop packets. The monitoring never sees the link as down but the level of service is degraded and mostly unusable. The ISP can quickly reset the ports and fix the issue, but we want to know right when this happens to minimize downtime.

Installing Exchange 2010 Service Pack 1 Fails At Mailbox Role: Database is mandatory on UserMailbox.

In a recent incident, an Exchange server had a complete volume failure during testing. Exchange 2010 was reinstalled but when installing Service Pack 1, it failed upgrading the Mailbox Role. Upon reviewing the log, I found the following line: Database is mandatory on UserMailbox. Property Name: Database The error doesn’t explain the problem very well but it is basically saying that there is a UserMailbox without a database, which should never happen. The failure of the volume and subsequent reinstall of 2010 left the arbitration mailboxes (and one or two user mailboxes) orphaned. Most of the suggestions to resolve this problem list doing things like deleting the system mailboxes and running “setup.com /PrepareAD”. After looking around, I was able to parse together a few other options and find a fix.