This script uses UCWA to send IMs via Skype For Business. Unlike the commonly documented methods using Microsoft.Lync.Model from the Lync SDK which require the Lync/S4B client to be installed, running and logged in to work, this will run from any machine on your internal network (and in theory could be used externally with some tweaking too). It still needs some cleaning up because none of the code examples I could find were for Powershell so there was a bit of trial and error and I’m sure things can be done more efficiently, but at a basic level it does what it’s supposed to.
The help should cover most of the arguments, it’s all fairly self-explanatory. You’ll probably want to update the following line in the script with your own Application name and GUID:
$postparams = @{UserAgent="My UCWA Application";EndpointId="75d0449f-aa09-4f5d-add5-eeefc518c48a";Culture="en-US"} | ConvertTo-JSON
You can use ([guid]::NewGuid()).guid
to generate a new GUID for your application.
You can also edit the default $messageheader, $messagefooter, $messagebody & $messagesubject values. The Subject and Header must be plaintext, the Body and Footer support HTML.
#Requires -version 4
<#
.SYNOPSIS
Sends IM via S4B
.DESCRIPTION
Sends IM via S4B using UCWA
.PARAMETER username
Username of account used to send messages in UPN format
.PARAMETER password
Password of account as plaintext (exclusive of pwd)
.PARAMETER pwd
Password of account as securestring (exclusive of password)
.PARAMETER recipients
List of recipient SIP addresses, comma separated
.PARAMETER messagesubject
Message subject (Plain text only)
.PARAMETER messagebody
Message body (HTML allowed)
.PARAMETER messageheader
Message header (Plain text only), sent as part of the invitation
.PARAMETER messagefooter
Message footer (HTML allowed), sent after the message body
.EXAMPLE
C:\>Send-S4BMessage.ps1
.NOTES
Author: Adam Beardwood
Date: January 13, 2016
v1.0: Initial Release
v1.1: Bugfix for multiple recipients
#>
[CmdletBinding(DefaultParameterSetName="secure")]
param(
[Parameter(Mandatory=$true)][string]$username,
[Parameter(Mandatory=$true,ParameterSetName='secure')][System.Security.SecureString]$pwd,
[Parameter(Mandatory=$true,ParameterSetName='plain')][string]$password,
[Parameter(Mandatory=$true)][array]$recipients,
[Parameter(Mandatory=$false)][string]$messagesubject,
[Parameter(Mandatory=$false)][string]$messagebody,
[Parameter(Mandatory=$false)][string]$messageheader,
[Parameter(Mandatory=$false)][string]$messagefooter
)
function sendmessage ($operationID, $rootappurl, $appid, $authcwt, $recipient, $messagesubject, $messagebody, $messagefooter, $ackid) {
$statecount = 0
$ackid = $ackid.tostring()
write-verbose "#Send Message Invite"
write-verbose "$rootappurl/$appid/communication/"
try{
$postparams = @{"importance"="Normal";"sessionContext"="$(([guid]::NewGuid()).guid)";"subject"="$messagesubject";"telemetryId"=$null;"to"=$recipient;"operationId"=$operationID;"_links"=@{"message"=@{"href"="data:text/plain,$messageheader"}}} | convertto-json
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/messagingInvitations" -Method POST -Body "$postparams" -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/json" -UseBasicParsing
}catch{
write-verbose "Unable to send message invite"
return $false
}
write-verbose "#Check state & get Conversation ID"
do{
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/events?ack=$ackid" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/JSON" -UseBasicParsing
$state = (($data.content | ConvertFrom-JSON).sender.events._embedded.messaging.state)
if($state.gettype().name -eq "string"){
}else{
$state = $state[-1]
}
$statecount++
if(($statecount -ge 25) -or ($state -eq "Disconnected")){
write-verbose "No response from endpoint or conversation declined"
return $false
}
start-sleep 1
}while($state -notcontains "Connected")
$JSONdata = $data.content | ConvertFrom-JSON
$conversationID = ($JSONdata.sender.events.link | ?{$_.rel -eq "conversation"}).href.split("/")[-1]
$conversationID
write-verbose "#Send Messages"
try{
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/messages" -Method POST -Body $messagebody -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "text/HTML" -UseBasicParsing
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/messages" -Method POST -Body $messagefooter -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "text/HTML" -UseBasicParsing
}catch{
write-verbose "Unable to send message"
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/terminate" -Method POST -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
return $false
}
write-verbose "#Terminate Conversation"
try{
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/terminate" -Method POST -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
}catch{
write-verbose "Failed to terminate conversation"
return $false
}
rv conversationID, recipient, ackid, state
return $true
}
if($pwd){[string]$password = (New-Object System.Management.Automation.PSCredential('dummy',$pwd)).getnetworkcredential().password}
if(!$messagesubject){$messagesubject = "S4B Automated Message"}
if(!$messagebody){$messagebody = "<font face='calibri'>Someone forgot to set a message so you're seeing this default instead</font>"}
if(!$messageheader){$messageheader = "This is an automated alert from <system>"}
if(!$messagefooter){$messagefooter = "<font face='calibri'><i>This message was sent by a bot, there's no point in replying to it.</i></font>"}
write-verbose "#Get Autodiscover Information"
try{
$data = Invoke-WebRequest -Uri "https://lyncdiscoverinternal.$($env:userdnsdomain)" -Method GET -ContentType "application/json" -UseBasicParsing
$baseurl = (($data.content | ConvertFrom-JSON)._links.user.href).split("/")[0..2] -join "/"
$oauthurl = ($data.content | convertfrom-json)._links.user.href
}catch{
write-output "Unable to get autodiscover information"
exit 1
}
write-verbose "#Authenticate to server"
try{
$postParams = @{grant_type="password";username=$username;password=$password}
$data = Invoke-WebRequest -Uri "$baseurl/WebTicket/oauthtoken" -Method POST -Body $postParams -UseBasicParsing
$authcwt = ($data.content | ConvertFrom-JSON).access_token
}catch{
write-output "Unable to authenticate, verify credentials and try again"
exit 1
}
write-verbose "#Get application URLs"
try{
$data = Invoke-WebRequest -Uri "$oauthurl" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
$rootappurl = ($data.content | ConvertFrom-JSON)._links.applications.href
}catch{
write-output "Unable to get Application URLs"
exit 1
}
write-verbose "#Create App Instance"
try{
$postparams = @{UserAgent="My UCWA Application";EndpointId="75d0449f-aa09-4f5d-add5-eeefc518c48a";Culture="en-US"} | ConvertTo-JSON
$data = Invoke-WebRequest -Uri "$rootappurl" -Method POST -Body "$postparams" -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/json" -UseBasicParsing
$appurl = $(($data.content | ConvertFrom-JSON)._links.self.href)
$appurl = "$($rootappurl.split("/")[0..2] -join "/")$(($data.content | ConvertFrom-JSON)._links.self.href)"
$appid = $appurl.split("/")[-1]
$operationID = (($data.content | ConvertFrom-JSON)._embedded.communication | GM -Type Noteproperty)[0].name
}catch{
write-output "Unable to create application instance"
exit 1
}
write-verbose "#Allow HTML messages to be sent"
try{
$postparams = @{"supportedMessageFormats"="Plain","Html"} | ConvertTo-JSON
$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/makeMeAvailable" -Method POST -Body $postparams -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/json" -UseBasicParsing
}catch{
write-verbose "HTML Messaging Already Configured"
}
write-verbose "#Send messages"
$i = 0
foreach($recipient in $recipients){
if($recipient -notmatch "^sip:\S+"){
$recipient = "sip:$recipient"
}
$i++
$msgresult = sendmessage $operationID $rootappurl $appid $authcwt $recipient $messagesubject $messagebody $messagefooter $i
if($msgresult){
write-verbose "Message sent to $recipient"
}else{
write-verbose "Message not sent to $recipient"
}
start-sleep 1
}
write-verbose "#Delete App Instance"
$deleteapp = Invoke-WebRequest -Uri "$rootappurl/$appid" -Method DELETE -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing