Teams.ps1
# This file contains functions for Teams # Gets a skype token using Teams accesstoken # Oct 3rd 2020 function Get-SkypeToken { <# .SYNOPSIS Gets SkypeToken. .DESCRIPTION Gets SkypeToken. .Parameter AccessToken The access token used to get the token .EXAMPLE PS\:>Get-AADIntAccessTokenForTeams -SaveToCache PS\:>$skypeToken = Get-AADIntSkypeToken #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://api.spaces.skype.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" $response = Get-TeamsInformation -AccessToken $AccessToken $response.tokens.SkypeToken } } # Sets user's availability status # Oct 3rd 2020 function Set-TeamsAvailability { <# .SYNOPSIS Sets the availability status of the user. .DESCRIPTION Sets the availability status of the user. .Parameter AccessToken The access token used to set the availability .Parameter Status The status, one of Available, Busy, DoNotDisturb, BeRightBack, or Away .EXAMPLE Get-AADIntAccessTokenForTeams -SaveToCache PS\:>Set-AADIntTeamsAvailability -Status Busy #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken, [Parameter(Mandatory=$False)] [ValidateSet("Available","Busy","DoNotDisturb","BeRightBack","Away")] [String]$Status="Available" ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://api.spaces.skype.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" $body="{""availability"":""$Status""}" $headers = @{ "Authorization" = "Bearer $AccessToken" "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.24755 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36" } Invoke-RestMethod -Method Put -Uri "https://presence.teams.microsoft.com/v1/me/forceavailability/" -Headers $headers -Body $body -ContentType "application/json" } } # Sets user's Teams statusmessage # Oct 3rd 2020 function Set-TeamsStatusMessage { <# .SYNOPSIS Sets the Teams status message status of the user. .DESCRIPTION Sets the Teams status message status of the user. .Parameter AccessToken The access token used to set the availability .Parameter Message The status message .Parameter Expires Expiration time of the message .EXAMPLE Get-AADIntAccessTokenForTeams -SaveToCache PS\:>Set-AADIntTeamsStatusMessage -Message "Out of office til noon" #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken, [Parameter(Mandatory=$False)] [String]$Message, [Parameter(Mandatory=$False)] [DateTime]$Expires ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://api.spaces.skype.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" if($Expires) { $expiry = $Expires.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":") } else { $expiry = "9999-12-31T08:00:00.000Z" } $body=[ordered]@{ "message" = $Message "expiry" = $expiry } $headers = @{ "Authorization" = "Bearer $AccessToken" "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.24755 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36" } Invoke-RestMethod -Method Put -Uri "https://presence.teams.microsoft.com/v1/me/publishnote" -Headers $headers -Body ($body | ConvertTo-Json -Compress) -ContentType "application/json;charset=utf-8" } } # Searches a teams user # Oct 3rd 2020 function Search-TeamsUser { <# .SYNOPSIS Searhes users with the given searchstring. .DESCRIPTION Searhes users with the given searchstring. .Parameter AccessToken The access token used to perform the search .Parameter SearchString Search string. .EXAMPLE Get-AADIntAccessTokenForTeams -Resource https://outlook.com -SaveToCache PS C:\>Search-AADIntTeamsUser -SearchString "user" | Format-Table UserPrincipalName,DisplayName UserPrincipalName DisplayName ----------------- ----------- first.user@company.com First User second.user@company.com Second User #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken, [Parameter(Mandatory=$False)] [String]$SearchString ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://outlook.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" $body=@" { "EntityRequests": [{ "Query": { "QueryString": "$SearchString", "DisplayQueryString": "" }, "EntityType": "People", "Provenances": ["Mailbox", "Directory"], "From": 0, "Size": 500, "Fields": ["Id", "DisplayName", "EmailAddresses", "CompanyName", "JobTitle", "ImAddress", "UserPrincipalName", "ExternalDirectoryObjectId", "PeopleType", "PeopleSubtype", "ConcatenatedId", "Phones", "MRI"], } ], "Cvid": "$((New-Guid).ToString())", "AppName": "Microsoft Teams", "Scenario": { "Name": "staticbrowse" } } "@ $headers = @{ "Authorization" = "Bearer $AccessToken" "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.24755 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36" } $response=Invoke-RestMethod -Method Post -Uri "https://substrate.office.com/search/api/v1/suggestions" -Headers $headers -Body $body -ContentType "application/json" $response.Groups.Suggestions } } # Sends a message using teams # Oct 16th 2020 function Send-TeamsMessage { <# .SYNOPSIS Sends a Teams message to given recipients.. .DESCRIPTION Sends a Teams message to given recipients. .Parameter AccessToken The access token used to send the message. .Parameter Recipients Email addresses of the recipients .Parameter Message Message to be sent. If in html, use -Html switch .Parameter MessageId The client message id of the message. If exists, the content is replaced with the given message. .EXAMPLE Get-AADIntAccessTokenForTeams -SaveToCache PS C:\>Send-AADIntTeamsMessage -Recipients user@company.com -Message "Hi user!" Sent MessageID ---- --------- 16/10/2020 14.40.23 132473328207053858 #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken, [Parameter(Mandatory=$True)] [String[]]$Recipients, [Parameter(Mandatory=$True)] [String]$Message, [switch]$Html, [Parameter(Mandatory=$False)] [String]$MessageId=(Get-Date).ToFileTimeUtc() ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://api.spaces.skype.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" if($Html) { $messagetype = "RichText/Html" $contenttype = "text" } else { $messagetype = "Text" $contenttype = "text" } # Get the settings $teamsSettings = Get-TeamsInformation -AccessToken $AccessToken $chatService = $teamsSettings.regionGtms.chatService $apiUrl = $teamsSettings.regionGtms.middleTier $skypeToken = $teamsSettings.tokens.SkypeToken # Construct the headers $headers = @{ "Authorization" = "Bearer $AccessToken" "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.24755 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36" "Authentication" = "skypetoken=$skypeToken" } # Parse AccessToken to get sender details $parsedToken = Read-Accesstoken $AccessToken # Get information for the recipient (add empty string to make sure its an array) $Recipients += "" $recipientInfo = Invoke-RestMethod -Method Post -Uri "$apiUrl/beta/users/fetch?isMailAddress=true&canBeSmtpAddress=false&enableGuest=true&includeIBBarredUsers=true&skypeTeamsInfo=true" -Headers $headers -Body ([String[]]$Recipients|ConvertTo-Json) -ContentType "application/json" if($recipientInfo.Value.Count -lt 1) { Write-Verbose $recipientInfo Throw "Recipient not found" } # Get a new thread for the conversation $members=@( @{ "id" = "8:orgid:$($parsedToken.oid)" "role" = "Admin" } ) foreach($recipient in $recipientInfo.Value) { $members += @{ "id" = $recipient.mri "role" = "Admin" } } $threadBody = @{ "members" = $members "properties" = @{ "threadType" = "chat" "chatFilesIndexId" = "2" "uniquerosterthread" = ($members.Count -eq 2).ToString().ToLower() "fixedRoster" = "true" } } $threadResponse = Invoke-WebRequest -Method Post -Uri "$chatService/v1/threads" -Headers $headers -Body ($threadBody | ConvertTo-Json) -ContentType "application/json" -MaximumRedirection 0 $threadUrl = $threadResponse.Headers["location"] $thread = $threadUrl.Substring($threadUrl.LastIndexOf("/")+1) # Links $links=@() # Check if we have any links if($Html -and $Message.IndexOf("href") -gt -1) { # Try to convert to xml for parsing.. try { [xml]$xmlHtml = $Message $messageLinks = Select-Xml -Xml $xmlHtml -XPath "//a" for($a = 0; $a -lt $messageLinks.Count ; $a++) { $linkUrl = $messageLinks[$a].Node.href $links += @{ "@type" = "http://schema.skype.com/HyperLink" "itemid"= $a "url"= $linkUrl "previewenabled" = "false" "preview" = @{ "previewurl" = "" "previewmeta" = "" "title" = "" "description" = "" "isLinkUnsafe" = "false" } } } } catch { Write-Warning "The message contains link(s), but it was not well-formed html. Check the syntax of the message!" } } # Send the message $messageBody=@{ "content" = $Message "messagetype"= $messagetype "contenttype"= $contenttype "amsreferences" = @() "clientmessageid" = $MessageId "imdisplayname" = $parsedToken.name "properties" = @{"importance" = ""; "subject"= $null; "links" = $links} } $response=Invoke-RestMethod -Method Post -Uri "$chatService/v1/users/ME/conversations/$thread/messages" -Headers $headers -Body ($messageBody | ConvertTo-Json -Depth 5) -ContentType "application/json; charset=utf-8" $posted=$epoch.AddMilliseconds($response.OriginalArrivalTime) return New-Object psobject -Property @{"MessageID" = $MessageId; "Sent" = $posted} } } # Get the latest Teams messages # Oct 16th 2020 function Get-TeamsMessages { <# .SYNOPSIS Gets user's latest Teams messages. .DESCRIPTION Gets user's latest Teams messages. .Parameter AccessToken The access token used to get the messages .EXAMPLE Get-AADIntAccessTokenForTeams -SaveToCache PS C:\>Get-AADIntTeamsMessages | Format-Table id,content,deletiontime,*type*,DisplayName Id Content DeletionTime MessageType Type DisplayName -- ------- ------------ ----------- ---- ----------- 1602842299338 1602846853687 RichText/Html MessageUpdate Bad User 1602844861358 1602858789696 RichText/Html MessageUpdate Bad User 1602846167606 1602858792943 Text MessageUpdate Bad User 1602846853687 1602858795517 Text MessageUpdate Bad User 1602833251951 1602833251951 Text MessageUpdate Bad User 1602833198442 1602833198442 Text MessageUpdate Bad User 1602859223294 Hola User! Text NewMessage Bad User 1602859423019 Hi User! Text NewMessage Bad User 1602859423019 Hi User! Text MessageUpdate Bad User 1602859473083 <div><div>Hi User!</div></div> RichText/Html NewMessage Bad User 1602859484420 Hey User! Text NewMessage Bad User 1602859528028 Hy User! Text NewMessage Bad User 1602859484420 Hey User! Text MessageUpdate Bad User 1602859590916 Hi User! Text NewMessage Bad User #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://api.spaces.skype.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" # Endpoint $endPoint = (New-Guid).ToString() # Get the settings $teamsSettings = Get-TeamsInformation -AccessToken $AccessToken $chatService = $teamsSettings.regionGtms.chatService $skypeToken = $teamsSettings.tokens.SkypeToken # Construct the headers $headers = @{ "Authorization" = "Bearer $AccessToken" "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.24755 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36" "Authentication" = "skypetoken=$skypeToken" } $body=@" { "startingTimeSpan": 0, "endpointFeatures": "Agent,Presence2015,MessageProperties,CustomUserProperties,NotificationStream,SupportsSkipRosterFromThreads", "subscriptions": [{ "channelType": "HttpLongPoll", "interestedResources": ["/v1/users/ME/conversations/ALL/properties", "/v1/users/ME/conversations/ALL/messages", "/v1/threads/ALL"] } ] } "@ $response = Invoke-RestMethod -Method Put -Uri "$chatService/v2/users/ME/endpoints/$endPoint" -Headers $headers -Body $body -ContentType "application/json; charset=utf-8" $pollUrl = $response.subscriptions[0].longPollUrl if($pollUrl.contains("?")) { $pollUrl = $pollUrl.Split("?")[0] } $pollResponse = Invoke-RestMethod -Method Get -Uri "$pollUrl" -Headers $headers foreach($message in $pollResponse.eventMessages) { if($message.resourceType -like "*Message*" -and $message.resource.contenttype -eq "text") { $attributes = [ordered]@{ "ClientMessageId" = $message.resource.clientmessageid "Id" = $message.resource.id "MessageType" = $message.resource.messageType "DisplayName" = $message.resource.imdisplayname "ArrivalTime" = $message.resource.originalarrivaltime "DeletionTime" = $message.resource.properties.deletetime "Link" = $message.resource.conversationLink.Substring($message.resource.conversationLink.LastIndexOf("/")+1) "Content" = $message.resource.content "Type" = $message.resourceType } New-Object psobject -Property $attributes } } } } # Deletes Teams messages # Oct 16th 2020 function Remove-TeamsMessages { <# .SYNOPSIS Deletes given Teams messages. .DESCRIPTION Deletes given Teams messages. .Parameter AccessToken The access token used to get the messages .Parameter MessageIDs List of IDs of the messages to be deleted .Parameter DeleteType Deletion type, can be either SoftDelete or HardDelete. Defaults to HardDelete. Soft deleted messages can be restored from the UI. .EXAMPLE Get-AADIntAccessTokenForTeams -SaveToCache PS C:\>Get-AADIntTeamsMessages | Format-Table id,content,deletiontime,*type*,DisplayName Id Content DeletionTime MessageType Type DisplayName -- ------- ------------ ----------- ---- ----------- 1602842299338 1602846853687 RichText/Html MessageUpdate Bad User 1602844861358 1602858789696 RichText/Html MessageUpdate Bad User 1602846167606 1602858792943 Text MessageUpdate Bad User 1602846853687 1602858795517 Text MessageUpdate Bad User 1602833251951 1602833251951 Text MessageUpdate Bad User 1602833198442 1602833198442 Text MessageUpdate Bad User 1602859223294 Hola User! Text NewMessage Bad User 1602859423019 Hi User! Text NewMessage Bad User 1602859423019 Hi User! Text MessageUpdate Bad User 1602859473083 <div><div>Hi User!</div></div> RichText/Html NewMessage Bad User 1602859484420 Hey User! Text NewMessage Bad User 1602859528028 Hy User! Text NewMessage Bad User 1602859484420 Hey User! Text MessageUpdate Bad User 1602859590916 Hi User! Text NewMessage Bad User PS C:\>Remove-AADIntTeamsMessages -MessageIDs 1602859590916,1602859484420 #> [cmdletbinding()] Param( [Parameter(Mandatory=$False)] [String]$AccessToken, [Parameter(Mandatory=$True)] [String[]]$MessageIDs, [Parameter(Mandatory=$False)] [ValidateSet("HardDelete","SoftDelete")] [String]$DeleteType="HardDelete" ) Process { # Get from cache if not provided $AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://api.spaces.skype.com" -ClientId "1fec8e78-bce4-4aaf-ab1b-5451cc387264" # Get the settings $teamsSettings = Get-TeamsInformation -AccessToken $AccessToken $chatService = $teamsSettings.regionGtms.chatService $skypeToken = $teamsSettings.tokens.SkypeToken # Construct the headers $headers = @{ "Authorization" = "Bearer $AccessToken" "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.3.00.24755 Chrome/69.0.3497.128 Electron/4.2.12 Safari/537.36" "Authentication" = "skypetoken=$skypeToken" } # Get the latest messages $messages = Get-TeamsMessages -AccessToken $AccessToken # Loop through the messages and delete when the correct ones if found foreach($message in $messages) { if($MessageIDs -contains $message.Id) { try { $response = Invoke-RestMethod -Method Delete -Uri "$chatService/v1/users/ME/conversations/$($message.Link)/messages/$($message.Id)`?behavior=$DeleteType" -Headers $headers -ErrorAction SilentlyContinue } catch{ Write-Warning "MessageId $($message.Id):`n$(($_.ErrorDetails.Message | ConvertFrom-Json).message)" } } } } } |