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

  Sends IM via S4B
    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)
  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
    Author: Adam Beardwood
    Date: January 13, 2016
    v1.0: Initial Release
    v1.1: Bugfix for multiple recipients


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/"

    $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
    write-verbose "Unable to send message invite"
    return $false

  write-verbose "#Check state & get Conversation ID"


    $data = Invoke-WebRequest -Uri "$rootappurl/$appid/events?ack=$ackid" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/JSON" -UseBasicParsing

    $state = (($data.content | ConvertFrom-JSON)

    if($state.gettype().name -eq "string"){

      $state = $state[-1]


    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 = ($ | ?{$_.rel -eq "conversation"}).href.split("/")[-1]


  write-verbose "#Send Messages"

    $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
    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"

    $data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/terminate" -Method POST -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
    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"

  $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
  write-output "Unable to get autodiscover information"
  exit 1
write-verbose "#Authenticate to server"

  $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
  write-output "Unable to authenticate, verify credentials and try again"
  exit 1
write-verbose "#Get application URLs"

  $data = Invoke-WebRequest -Uri "$oauthurl" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing

  $rootappurl = ($data.content | ConvertFrom-JSON)._links.applications.href
  write-output "Unable to get Application URLs"
  exit 1

write-verbose "#Create App Instance"

  $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
  write-output "Unable to create application instance"
  exit 1

write-verbose "#Allow HTML messages to be sent"

  $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
  write-verbose "HTML Messaging Already Configured"

write-verbose "#Send messages"

$i = 0

foreach($recipient in $recipients){

  if($recipient -notmatch "^sip:\S+"){
    $recipient = "sip:$recipient"
  $msgresult = sendmessage $operationID $rootappurl $appid $authcwt $recipient $messagesubject $messagebody $messagefooter $i
    write-verbose "Message sent to $recipient"
    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