diff --git a/.gitignore b/.gitignore index d174bbe7..1e48fdbf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ TestPad.ps1 **.insyncdl .vscode API-to-Function-Map.md -QueueExample.ps1 \ No newline at end of file +QueueExample.ps1 +ChatBotLoop.ps1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b0b44c..331809c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - [Changelog](#changelog) + - [2.13.0](#2130) - [2.12.1](#2121) - [2.12.0](#2120) - [2.11.0](#2110) @@ -45,6 +46,18 @@ +## 2.13.0 + +* Fixed: Private list functions to check if a value is actually returned before adding members to the returned objects ([Issue #77](https://github.com/scrthq/PSGSuite/issues/77)) +* Added: `Update-GSChatMessage` to allow updating existing messages in Chat (i.e. on Card Clicked events) +* Updated: Order of parameters in `Get-GSToken` to place `Scopes` first, as it's the only required parameter +* Updated: `Get-GSChatSpace` now updates the config with Space names/shortnames for ease of use +* Updated: `Send-GSChatMessage` to also support calling the REST API as an additional option. This is necessary for PoshBot due to the deserialization of objects passed back to result parser breaking the Google SDK type references +* Updated: `Get-GSChatConfig` to always fetch the latest config if no ConfigName is passed instead of using `Show-PSGSuiteConfig` +* Updated: `Set-PSGSuiteConfig` to refresh the Spaces dictionary each time in order to remove stale spaces (i.e. on removal of bot from a Room or DM) +* Fixed: `Add-GSChatOnClick` now properly builds the hashtable for the Webhook object +* Updated: `Get-GSUser` to allow passing User ID's instead of emails by checking if value passed is a `decimal` before concatenating the domain name. + ## 2.12.1 * Fixed: `Get-GSDrivePermission` now returns all fields (including EmailAddress) diff --git a/PSGSuite/PSGSuite.psd1 b/PSGSuite/PSGSuite.psd1 index f6ee965f..1a13645a 100644 --- a/PSGSuite/PSGSuite.psd1 +++ b/PSGSuite/PSGSuite.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSGSuite.psm1' # Version number of this module. - ModuleVersion = '2.12.1' + ModuleVersion = '2.13.0' # ID used to uniquely identify this module GUID = '9d751152-e83e-40bb-a6db-4c329092aaec' diff --git a/PSGSuite/Private/Legacy/Get-GSToken.ps1 b/PSGSuite/Private/Legacy/Get-GSToken.ps1 index bac921b6..e5fe5968 100644 --- a/PSGSuite/Private/Legacy/Get-GSToken.ps1 +++ b/PSGSuite/Private/Legacy/Get-GSToken.ps1 @@ -11,14 +11,14 @@ function Get-GSToken { #> Param ( - [parameter(Mandatory = $false,HelpMessage = "What is the full path to your Google Service Account's P12 key file?")] - [ValidateNotNullOrEmpty()] - [String] - $P12KeyPath = $Script:PSGSuite.P12KeyPath, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]] $Scopes, + [parameter(Mandatory = $false,HelpMessage = "What is the full path to your Google Service Account's P12 key file?")] + [ValidateNotNullOrEmpty()] + [String] + $P12KeyPath = $Script:PSGSuite.P12KeyPath, [parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String] diff --git a/PSGSuite/Private/ListPrivate/Get-GSGroupListPrivate.ps1 b/PSGSuite/Private/ListPrivate/Get-GSGroupListPrivate.ps1 index 5c6c1ac7..e0832d39 100644 --- a/PSGSuite/Private/ListPrivate/Get-GSGroupListPrivate.ps1 +++ b/PSGSuite/Private/ListPrivate/Get-GSGroupListPrivate.ps1 @@ -54,7 +54,9 @@ function Get-GSGroupListPrivate { [int]$i = 1 do { $result = $request.Execute() - $result.GroupsValue | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force + if ($null -ne $result.GroupsValue) { + $result.GroupsValue | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force + } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.GroupsValue.Count) - 1 Write-Verbose "Retrieved $retrieved groups..." diff --git a/PSGSuite/Private/ListPrivate/Get-GSGroupMemberListPrivate.ps1 b/PSGSuite/Private/ListPrivate/Get-GSGroupMemberListPrivate.ps1 index 80182705..df94ee09 100644 --- a/PSGSuite/Private/ListPrivate/Get-GSGroupMemberListPrivate.ps1 +++ b/PSGSuite/Private/ListPrivate/Get-GSGroupMemberListPrivate.ps1 @@ -43,7 +43,9 @@ function Get-GSGroupMemberListPrivate { [int]$i = 1 do { $result = $request.Execute() - $result.MembersValue | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Id -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force + if ($null -ne $result.MembersValue) { + $result.MembersValue | Add-Member -MemberType NoteProperty -Name 'Group' -Value $Id -PassThru | Add-Member -MemberType ScriptMethod -Name ToString -Value {$this.Email} -PassThru -Force + } $request.PageToken = $result.NextPageToken [int]$retrieved = ($i + $result.MembersValue.Count) - 1 Write-Verbose "Retrieved $retrieved members..." diff --git a/PSGSuite/Private/ListPrivate/Get-GSShortUrlListPrivate.ps1 b/PSGSuite/Private/ListPrivate/Get-GSShortUrlListPrivate.ps1 index e0225fbc..9898653c 100644 --- a/PSGSuite/Private/ListPrivate/Get-GSShortUrlListPrivate.ps1 +++ b/PSGSuite/Private/ListPrivate/Get-GSShortUrlListPrivate.ps1 @@ -29,7 +29,10 @@ function Get-GSShortUrlListPrivate { try { Write-Verbose "Getting Short Url list for User '$U'" $request = $service.Url.List() - $request.Execute() | Select-Object -ExpandProperty Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + $result = $request.Execute() + if ($null -ne $result.Items) { + $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + } } catch { if ($ErrorActionPreference -eq 'Stop') { diff --git a/PSGSuite/Private/ListPrivate/Get-GSUserASPListPrivate.ps1 b/PSGSuite/Private/ListPrivate/Get-GSUserASPListPrivate.ps1 index b0662bb3..90f21680 100644 --- a/PSGSuite/Private/ListPrivate/Get-GSUserASPListPrivate.ps1 +++ b/PSGSuite/Private/ListPrivate/Get-GSUserASPListPrivate.ps1 @@ -26,7 +26,10 @@ } Write-Verbose "Getting ASP list for User '$U'" $request = $service.Asps.List($U) - $request.Execute() | Select-Object -ExpandProperty Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + $result = $request.Execute() + if ($null -ne $result.Items) { + $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + } } catch { if ($ErrorActionPreference -eq 'Stop') { diff --git a/PSGSuite/Private/ListPrivate/Get-GSUserSchemaListPrivate.ps1 b/PSGSuite/Private/ListPrivate/Get-GSUserSchemaListPrivate.ps1 index 19c37bfd..3b190f9e 100644 --- a/PSGSuite/Private/ListPrivate/Get-GSUserSchemaListPrivate.ps1 +++ b/PSGSuite/Private/ListPrivate/Get-GSUserSchemaListPrivate.ps1 @@ -11,7 +11,10 @@ function Get-GSUserSchemaListPrivate { Process { try { $request = $service.Schemas.List($Script:PSGSuite.CustomerId) - $request.Execute() | Select-Object -ExpandProperty SchemasValue + $result = $request.Execute() + if ($null -ne $result.SchemasValue) { + $result.SchemasValue + } } catch { if ($ErrorActionPreference -eq 'Stop') { diff --git a/PSGSuite/Private/ListPrivate/Get-GSUserTokenListPrivate.ps1 b/PSGSuite/Private/ListPrivate/Get-GSUserTokenListPrivate.ps1 index 6041af67..7f2571cd 100644 --- a/PSGSuite/Private/ListPrivate/Get-GSUserTokenListPrivate.ps1 +++ b/PSGSuite/Private/ListPrivate/Get-GSUserTokenListPrivate.ps1 @@ -26,7 +26,10 @@ } Write-Verbose "Getting Token list for User '$U'" $request = $service.Tokens.List($U) - $request.Execute() | Select-Object -ExpandProperty Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + $result = $request.Execute() + if ($null -ne $result.Items) { + $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + } } catch { if ($ErrorActionPreference -eq 'Stop') { diff --git a/PSGSuite/Public/Chat/Get-GSChatSpace.ps1 b/PSGSuite/Public/Chat/Get-GSChatSpace.ps1 index a7391272..9e01ce3d 100644 --- a/PSGSuite/Public/Chat/Get-GSChatSpace.ps1 +++ b/PSGSuite/Public/Chat/Get-GSChatSpace.ps1 @@ -90,15 +90,23 @@ function Get-GSChatSpace { } End { Write-Verbose "Updating PSGSuite Config with Space list" - $spaceHash = $spaceArray | ForEach-Object { + $spaceHashArray = @() + $spaceArray | ForEach-Object { if ($_.DisplayName) { - Set-PSGSuiteConfig -Space @{$_.DisplayName = $_.Name} -Verbose:$false + $spaceHashArray += @{$_.DisplayName = $_.Name} } else { - Set-PSGSuiteConfig -Space @{DM = $_.Name} -Verbose:$false + $member = Get-GSChatMember -Space $_.Name -Verbose:$false + $id = $member.Member.Name + $primaryEmail = (Get-GSUser -User ($id.Replace('users/',''))).PrimaryEmail + $spaceHashArray += @{ + $id = $_.Name + $member.Member.DisplayName = $_.Name + $primaryEmail = $_.Name + } } } - + Set-PSGSuiteConfig -Space $spaceHashArray -Verbose:$false } } \ No newline at end of file diff --git a/PSGSuite/Public/Chat/Send-GSChatMessage.ps1 b/PSGSuite/Public/Chat/Send-GSChatMessage.ps1 index cdb1fd8c..ad606ed6 100644 --- a/PSGSuite/Public/Chat/Send-GSChatMessage.ps1 +++ b/PSGSuite/Public/Chat/Send-GSChatMessage.ps1 @@ -123,8 +123,12 @@ function Send-GSChatMessage { [string[]] $Parent, [parameter(Mandatory = $false,ParameterSetName = "SDK")] + [parameter(Mandatory = $false,ParameterSetName = "Rest")] [string] $ThreadKey, + [parameter(Mandatory = $true,ParameterSetName = "Rest")] + [string[]] + $RestParent, [parameter(Mandatory = $true,ParameterSetName = "Webhook")] [string[]] $Webhook, @@ -142,14 +146,17 @@ function Send-GSChatMessage { } })] [Object[]] - $MessageSegment + $MessageSegment, + [parameter(Mandatory = $true,ParameterSetName = "BodyPassThru")] + [switch] + $BodyPassThru ) Begin { $addlSections = @() $addlCardActions = @() $addlSectionWidgets = @() switch ($PSCmdlet.ParameterSetName) { - Webhook { + default { $body = @{} foreach ($key in $PSBoundParameters.Keys) { switch ($key) { @@ -224,10 +231,10 @@ function Send-GSChatMessage { } Process { foreach ($segment in $MessageSegment) { - switch ($segment.PSTypeNames[0]) { - 'PSGSuite.Chat.Message.Card' { + switch -RegEx ($segment['SDK'].PSTypeNames[0]) { + '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Card' { switch ($PSCmdlet.ParameterSetName) { - Webhook { + default { if (!$body['cards']) { $body['cards'] = @() } @@ -241,13 +248,14 @@ function Send-GSChatMessage { } } } - 'PSGSuite.Chat.Message.Card.Section' { + '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Section' { $addlSections += $segment } - 'PSGSuite.Chat.Message.Card.CardAction' { + '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.CardAction' { $addlCardActions += $segment } default { + Write-Verbose "Matched a $($segment['SDK'].PSTypeNames[0]) in the MessageSegments!" $addlSectionWidgets += $segment } } @@ -255,7 +263,7 @@ function Send-GSChatMessage { } End { switch ($PSCmdlet.ParameterSetName) { - Webhook { + default { if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { if (!$body['cards']) { $cardless = $true @@ -290,22 +298,70 @@ function Send-GSChatMessage { } } } - $body = $body | ConvertTo-Json -Depth 15 - foreach ($hook in $Webhook) { - try { - if ($hook -notlike "https://chat.googleapis.com/v1/spaces/*") { - $hook = Get-GSChatConfig -WebhookName $hook -ErrorAction Stop + switch ($PSCmdlet.ParameterSetName) { + Webhook { + $body = $body | ConvertTo-Json -Depth 15 + foreach ($hook in $Webhook) { + try { + if ($hook -notlike "https://chat.googleapis.com/v1/spaces/*") { + $hook = Get-GSChatConfig -WebhookName $hook -ErrorAction Stop + } + Write-Verbose "Sending Chat Message via Webhook to '$($hook -replace "\?key\=.*",'')'" + Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'Webhook' -Value $hook -PassThru + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } } - Write-Verbose "Sending Chat Message via Webhook to '$($hook -replace "\?key\=.*",'')'" - Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'Webhook' -Value $hook -PassThru } - catch { - if ($ErrorActionPreference -eq 'Stop') { - $PSCmdlet.ThrowTerminatingError($_) + Rest { + $body = $body | ConvertTo-Json -Depth 15 + foreach ($restPar in $RestParent) { + try { + if ($restPar -notlike "spaces/*") { + try { + $restPar = Get-GSChatConfig -SpaceName $restPar -ErrorAction Stop + } + catch { + $restPar = "spaces/$restPar" + } + } + $header = @{ + Authorization = "Bearer $(Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false)" + } + $hook = "https://chat.googleapis.com/v1/$($restPar)/messages" + if ($PSBoundParameters.Keys -contains 'ThreadKey') { + $hook = "$($hook)?threadKey=$ThreadKey" + $addlText = " in ThreadKey '$ThreadKey'" + } + else { + $addlText = "" + } + Write-Verbose "Sending Chat Message via REST API to parent '$restPar'$addlText" + Invoke-RestMethod -Method Post -Uri ([Uri]$hook) -Headers $header -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'RestParent' -Value $restPar -PassThru + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } } - else { - Write-Error $_ + } + BodyPassThru { + $newBody = @{ + token = (Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false) + body = $body } + $newBody = $newBody | ConvertTo-Json -Depth 20 -Compress + return $newBody } } } diff --git a/PSGSuite/Public/Chat/Update-GSChatMessage.ps1 b/PSGSuite/Public/Chat/Update-GSChatMessage.ps1 new file mode 100644 index 00000000..4532a428 --- /dev/null +++ b/PSGSuite/Public/Chat/Update-GSChatMessage.ps1 @@ -0,0 +1,353 @@ +function Update-GSChatMessage { + <# + .SYNOPSIS + Updates a Chat message, i.e. for a CardClicked response + + .DESCRIPTION + Updates a Chat message, i.e. for a CardClicked response + + .PARAMETER MessageId + Resource name, in the form "spaces/messages". + + Example: spaces/89L51AAAAAE/messages/kbZTbcol8H4.kbZTbcol8H4 + + .PARAMETER UpdateMask + Required. The field paths to be updated. + + Currently supported field paths: "text", "cards". + + .PARAMETER Text + Plain-text body of the message. + + .PARAMETER FallbackText + A plain-text description of the message's cards, used when the actual cards cannot be displayed (e.g. mobile notifications). + + .PARAMETER PreviewText + Text for generating preview chips. This text will not be displayed to the user, but any links to images, web pages, videos, etc. included here will generate preview chips. + + .PARAMETER ActionResponseType + Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted. + + The ActionResponseType is the type of bot response. + + Available values are: + * NEW_MESSAGE: Post as a new message in the topic. + * UPDATE_MESSAGE: Update the bot's own message. (Only after CARD_CLICKED events.) + * REQUEST_CONFIG: Privately ask the user for additional auth or config. + + .PARAMETER ActionResponseUrl + Part of the ActionResponse. Parameters that a bot can use to configure how its response is posted. + + The ActionResponseUrl is the URL for users to auth or config. (Only for REQUEST_CONFIG response types.) + + .PARAMETER MessageSegment + Any Chat message segment objects created with functions named `Add-GSChat*` passed through the pipeline or added directly to this parameter as values. + + If section widgets are passed directly to this function, a new section without a SectionHeader will be created and the widgets will be added to it + + .EXAMPLE + Send-GSChatMessage -Text "Post job report:" -Cards $cards -Webhook (Get-GSChatWebhook JobReports) + + Sends a simple Chat message using the JobReports webhook + + .EXAMPLE + Add-GSChatTextParagraph -Text "Guys...","We NEED to stop spending money on crap!" | + Add-GSChatKeyValue -TopLabel "Chocolate Budget" -Content '$5.00' -Icon DOLLAR | + Add-GSChatKeyValue -TopLabel "Actual Spending" -Content '$5,000,000!' -BottomLabel "WTF" -Icon AIRPLANE | + Add-GSChatImage -ImageUrl "https://media.tenor.com/images/f78545a9b520ecf953578b4be220f26d/tenor.gif" -LinkImage | + Add-GSChatCardSection -SectionHeader "Dollar bills, y'all" -OutVariable sect1 | + Add-GSChatButton -Text "Launch nuke" -OnClick (Add-GSChatOnClick -Url "https://github.com/scrthq/PSGSuite") -Verbose -OutVariable button1 | + Add-GSChatButton -Text "Unleash hounds" -OnClick (Add-GSChatOnClick -Url "https://admin.google.com/?hl=en&authuser=0") -Verbose -OutVariable button2 | + Add-GSChatCardSection -SectionHeader "What should we do?" -OutVariable sect2 | + Add-GSChatCard -HeaderTitle "Makin' moves with" -HeaderSubtitle "DEM GOODIES" -OutVariable card | + Add-GSChatTextParagraph -Text "This message sent by PSGSuite via WebHook!" | + Add-GSChatCardSection -SectionHeader "Additional Info" -OutVariable sect2 | + Send-GSChatMessage -Text "Got that report, boss:" -FallbackText "Mistakes have been made..." -Webhook ReportRoom + + This example shows the pipeline capabilities of the Chat functions in PSGSuite. Starting from top to bottom: + 1. Add a TextParagraph widget + 2. Add a KeyValue with an icon + 3. Add another KeyValue with a different icon + 4. Add an image and create an OnClick event to open the image's URL by using the -LinkImage parameter + 5. Add a new section to encapsulate the widgets sent through the pipeline before it + 6. Add a TextButton that opens the PSGSuite GitHub repo when clicked + 7. Add another TextButton that opens Google Admin Console when clicked + 8. Wrap the 2 buttons in a new Section to divide the content + 9. Wrap all widgets and sections in the pipeline so far in a Card + 10. Add a new TextParagraph as a footer to the message + 11. Wrap that TextParagraph in a new section + 12. Send the message and include FallbackText that's displayed in the mobile notification. Since the final TextParagraph and Section are not followed by a new Card addition, Send-GSChatMessage will create a new Card just for the remaining segments then send the completed message via Webhook. The Webhook short-name is used to reference the full URL stored in the encrypted Config so it's not displayed in the actual script. + + .EXAMPLE + Get-Service | Select-Object -First 5 | ForEach-Object { + Add-GSChatKeyValue -TopLabel $_.DisplayName -Content $_.Status -BottomLabel $_.Name -Icon TICKET + } | Add-GSChatCardSection -SectionHeader "Top 5 Services" | Send-GSChatMessage -Text "Service Report:" -FallbackText "Service Report" -Webhook Reports + + This gets the first 5 Services returned by Get-Service, creates KeyValue widgets for each, wraps it in a section with a header, then sends it to the Reports Webhook + #> + [cmdletbinding(DefaultParameterSetName = "Update")] + Param + ( + [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Update")] + [string] + $MessageId, + [parameter(Mandatory = $true,ParameterSetName = "BodyPassThru")] + [switch] + $BodyPassThru, + [parameter(Mandatory = $false,Position = 1)] + [ValidateSet("text","cards")] + [string[]] + $UpdateMask, + [parameter(Mandatory = $false)] + [string[]] + $Text, + [parameter(Mandatory = $false)] + [string[]] + $FallbackText, + [parameter(Mandatory = $false)] + [string] + $PreviewText, + [parameter(Mandatory = $false)] + [ValidateSet('NEW_MESSAGE','UPDATE_MESSAGE','REQUEST_CONFIG')] + [string] + $ActionResponseType = 'UPDATE_MESSAGE', + [parameter(Mandatory = $false)] + [string] + $ActionResponseUrl, + [parameter(Mandatory = $false,ParameterSetName = "Update")] + [switch] + $UseRest, + [parameter(Mandatory = $false,ValueFromPipeline = $true)] + [Alias('InputObject')] + [ValidateScript({ + $allowedTypes = "PSGSuite.Chat.Message.Card","PSGSuite.Chat.Message.Card.Section","PSGSuite.Chat.Message.Card.CardAction","PSGSuite.Chat.Message.Card.Section.TextParagraph","PSGSuite.Chat.Message.Card.Section.Button","PSGSuite.Chat.Message.Card.Section.Image","PSGSuite.Chat.Message.Card.Section.KeyValue" + foreach ($item in $_) { + if ([string]$($item.PSTypeNames) -match "($(($allowedTypes|ForEach-Object{[RegEx]::Escape($_)}) -join '|'))") { + $true + } + else { + throw "This parameter only accepts the following types: $($allowedTypes -join ", "). The current types of the value are: $($item.PSTypeNames -join ", ")." + } + } + })] + [Object[]] + $MessageSegment + ) + Begin { + $addlSections = @() + $addlCardActions = @() + $addlSectionWidgets = @() + if ($UseRest -or $BodyPassThru) { + $body = @{ + actionResponse = @{ + type = $ActionResponseType + } + } + foreach ($key in $PSBoundParameters.Keys) { + switch ($key) { + Text { + if ($UpdateMask -notcontains 'text') { + $UpdateMask += 'text' + } + $body['text'] = ($Text -join "`n") + } + PreviewText { + $body['previewText'] = $PSBoundParameters[$key] + } + FallbackText { + $body['fallbackText'] = ($PSBoundParameters[$key] -join "`n") + } + ActionResponseUrl { + if (!$body['actionResponse']) { + $body['actionResponse'] = @{} + } + $body['actionResponse']['url'] = $PSBoundParameters[$key] + } + } + } + } + else { + $serviceParams = @{ + Scope = 'https://www.googleapis.com/auth/chat.bot' + ServiceType = 'Google.Apis.HangoutsChat.v1.HangoutsChatService' + } + $service = New-GoogleService @serviceParams + $body = New-Object 'Google.Apis.HangoutsChat.v1.Data.Message' + $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse' + $body.ActionResponse.Type = $ActionResponseType + foreach ($key in $PSBoundParameters.Keys) { + switch ($key) { + Text { + if ($UpdateMask -notcontains 'text') { + $UpdateMask += 'text' + } + $body.Text = ($PSBoundParameters[$key] -join "`n") + } + PreviewText { + $body.PreviewText = $PSBoundParameters[$key] + } + FallbackText { + $body.FallbackText = ($PSBoundParameters[$key] -join "`n") + } + ActionResponseUrl { + if (!$body.ActionResponse) { + $body.ActionResponse = New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionResponse' + } + $body.ActionResponse.Url = $PSBoundParameters[$key] + } + } + } + } + } + Process { + if ($MessageSegment) { + if ($UpdateMask -notcontains 'cards') { + $UpdateMask += 'cards' + } + foreach ($segment in $MessageSegment) { + switch -RegEx ($segment['SDK'].PSTypeNames[0]) { + '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Card' { + if ($UseRest -or $BodyPassThru) { + if (!$body['cards']) { + $body['cards'] = @() + } + $body['cards'] += $segment['Webhook'] + } + else { + if (!$body.Cards) { + $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]' + } + $body.Cards.Add($segment['SDK']) | Out-Null + } + } + '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.Section' { + $addlSections += $segment + } + '(.*?)Google\.Apis\.HangoutsChat\.v1\.Data\.CardAction' { + $addlCardActions += $segment + } + default { + Write-Verbose "Matched a $($segment['SDK'].PSTypeNames[0]) in the MessageSegments!" + $addlSectionWidgets += $segment + } + } + } + } + } + End { + if ($UseRest -or $BodyPassThru) { + if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { + if (!$body['cards']) { + $cardless = $true + $body['cards'] = @() + } + if ($addlSections) { + $body['cards'] += ($addlSections | Add-GSChatCard)['Webhook'] + $cardless = $false + } + if ($addlSectionWidgets) { + if ($cardless) { + $body['cards'] += ($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['Webhook'] + } + else { + $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['Webhook'] + if (!$body['cards'][-1]['sections']) { + $body['cards'][-1]['sections'] = @() + } + $body['cards'][-1]['sections'] += $newSection + } + $cardless = $false + } + if ($addlCardActions) { + if ($cardless) { + $body['cards'] += ($addlCardActions | Add-GSChatCard)['Webhook'] + } + elseif (!$body['cards'][-1]['cardActions']) { + $body['cards'][-1]['cardActions'] = @() + } + foreach ($cardAction in $addlCardActions) { + $body['cards'][-1]['cardActions'] += $cardAction['Webhook'] + } + } + } + if ($BodyPassThru) { + $newBody = @{ + token = (Get-GSToken -Scopes "https://www.googleapis.com/auth/chat.bot" -Verbose:$false) + body = $body + updateMask = ($UpdateMask -join ',') + } + $newBody = $newBody | ConvertTo-Json -Depth 20 -Compress + return $newBody + } + else { + $body = $body | ConvertTo-Json -Depth 20 + try { + $header = @{ + Authorization = "Bearer $(Get-GSToken -P12KeyPath $Script:PSGSuite.P12KeyPath -Scopes "https://www.googleapis.com/auth/chat.bot" -AppEmail $Script:PSGSuite.AppEmail -AdminEmail $Script:PSGSuite.AdminEmail -Verbose:$false)" + } + $hook = "https://chat.googleapis.com/v1/$($MessageId)?updateMask=$($UpdateMask -join ',')" + Write-Verbose "Updating Chat Message via REST API to parent '$MessageId'" + Invoke-RestMethod -Method Put -Uri ([Uri]$hook) -Headers $header -Body $body -ContentType 'application/json' -Verbose:$false | Add-Member -MemberType NoteProperty -Name 'MessageId' -Value $MessageId -PassThru + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } + } + else { + if ($addlCardActions -or $addlSections -or $addlSectionWidgets) { + if (!$body.Cards) { + $cardless = $true + $body.Cards = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Card]' + } + if ($addlSections) { + $body.Cards.Add(($addlSections | Add-GSChatCard)['SDK']) | Out-Null + $cardless = $false + } + if ($addlSectionWidgets) { + if ($cardless) { + $body.Cards.Add(($addlSectionWidgets | Add-GSChatCardSection | Add-GSChatCard)['SDK']) | Out-Null + } + else { + $newSection = ($addlSectionWidgets | Add-GSChatCardSection)['SDK'] + if (!$body.Cards[-1].Sections) { + $body.Cards[-1].Sections = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.Section]' + } + $body.Cards[-1].Sections.Add($newSection) | Out-Null + } + $cardless = $false + } + if ($addlCardActions) { + if ($cardless) { + $body.Cards.Add(($addlCardActions | Add-GSChatCard)['SDK']) + } + elseif (!$body.Cards[-1].CardActions) { + $body.Cards[-1].CardActions = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.CardAction]' + } + foreach ($cardAction in $addlCardActions) { + $body.Cards[-1].CardActions.Add($cardAction['SDK']) | Out-Null + } + } + } + try { + $request = $service.Spaces.Messages.Update($body,$MessageId) + $request.UpdateMask = $UpdateMask + Write-Verbose "Updating Chat Message Id '$MessageId'" + $request.Execute() | Add-Member -MemberType NoteProperty -Name 'MessageId' -Value $MessageId -PassThru + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } + } +} \ No newline at end of file diff --git a/PSGSuite/Public/Configuration/Get-GSChatConfig.ps1 b/PSGSuite/Public/Configuration/Get-GSChatConfig.ps1 index 9fd246df..b830836a 100644 --- a/PSGSuite/Public/Configuration/Get-GSChatConfig.ps1 +++ b/PSGSuite/Public/Configuration/Get-GSChatConfig.ps1 @@ -37,7 +37,7 @@ function Get-GSChatConfig { $currentConfig = Get-PSGSuiteConfig -ConfigName $ConfigName -PassThru -NoImport } else { - $currentConfig = Show-PSGSuiteConfig + $currentConfig = Get-PSGSuiteConfig -PassThru } switch ($PSCmdlet.ParameterSetName) { Webhooks { diff --git a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 index ee67fbfa..f6c69f54 100644 --- a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 @@ -178,6 +178,7 @@ function Set-PSGSuiteConfig { Spaces = @{} } } + $configHash["$ConfigName"]['Chat']['Spaces'] = @{} foreach ($cWebhook in $PSBoundParameters[$key]) { foreach ($cWebhookKey in $cWebhook.Keys) { $configHash["$ConfigName"]['Chat']['Spaces'][$cWebhookKey] = (Encrypt $cWebhook[$cWebhookKey]) diff --git a/PSGSuite/Public/Helpers/Add-GSChatOnClick.ps1 b/PSGSuite/Public/Helpers/Add-GSChatOnClick.ps1 index 5a944a32..be9fdf09 100644 --- a/PSGSuite/Public/Helpers/Add-GSChatOnClick.ps1 +++ b/PSGSuite/Public/Helpers/Add-GSChatOnClick.ps1 @@ -96,7 +96,10 @@ function Add-GSChatOnClick { $onClickObject['SDK'].Action.Parameters = New-Object 'System.Collections.Generic.List[Google.Apis.HangoutsChat.v1.Data.ActionParameter]' foreach ($dict in $ActionParameters) { if ($dict.Keys.Count -eq 2 -and $dict.Keys -contains 'key' -and $dict.Keys -contains 'value') { - $onClickObject['Webhook']['action']['parameters'] += $dict + $onClickObject['Webhook']['action']['parameters'] += ([PSCustomObject]@{ + key = "$($dict['key'])" + value = "$($dict['value'])" + }) $onClickObject['SDK'].Action.Parameters.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionParameter' -Property @{ Key = $dict['key'] Value = $dict['value'] @@ -104,10 +107,10 @@ function Add-GSChatOnClick { } else { foreach ($key in $dict.Keys) { - $onClickObject['Webhook']['action']['parameters'] += @{ - key = $key - value = $dict[$key] - } + $onClickObject['Webhook']['action']['parameters'] += ([PSCustomObject]@{ + key = "$key" + value = "$($dict[$key])" + }) $onClickObject['SDK'].Action.Parameters.Add((New-Object 'Google.Apis.HangoutsChat.v1.Data.ActionParameter' -Property @{ Key = $key Value = $dict[$key] diff --git a/PSGSuite/Public/Users/Get-GSUser.ps1 b/PSGSuite/Public/Users/Get-GSUser.ps1 index d38eb2b5..91f86969 100644 --- a/PSGSuite/Public/Users/Get-GSUser.ps1 +++ b/PSGSuite/Public/Users/Get-GSUser.ps1 @@ -163,13 +163,19 @@ function Get-GSUser { Get { if ($MyInvocation.InvocationName -ne 'Get-GSUserList') { foreach ($U in $User) { - if ($U -ceq 'me') { - $U = $Script:PSGSuite.AdminEmail + try { + [decimal]$U | Out-Null + Write-Verbose "Getting User ID '$U'" } - elseif ($U -notlike "*@*.*") { - $U = "$($U)@$($Script:PSGSuite.Domain)" + catch { + if ($U -ceq 'me') { + $U = $Script:PSGSuite.AdminEmail + } + elseif ($U -notlike "*@*.*") { + $U = "$($U)@$($Script:PSGSuite.Domain)" + } + Write-Verbose "Getting User '$U'" } - Write-Verbose "Getting User '$U'" $request = $service.Users.Get($U) $request.Projection = $Projection $request.ViewType = ($ViewType -replace '_','') diff --git a/README.md b/README.md index 38243255..e0caea46 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,18 @@ Update-GSSheetValue Export-GSSheet ### Most recent changes +#### 2.13.0 + +* Fixed: Private list functions to check if a value is actually returned before adding members to the returned objects ([Issue #77](https://github.com/scrthq/PSGSuite/issues/77)) +* Added: `Update-GSChatMessage` to allow updating existing messages in Chat (i.e. on Card Clicked events) +* Updated: Order of parameters in `Get-GSToken` to place `Scopes` first, as it's the only required parameter +* Updated: `Get-GSChatSpace` now updates the config with Space names/shortnames for ease of use +* Updated: `Send-GSChatMessage` to also support calling the REST API as an additional option. This is necessary for PoshBot due to the deserialization of objects passed back to result parser breaking the Google SDK type references +* Updated: `Get-GSChatConfig` to always fetch the latest config if no ConfigName is passed instead of using `Show-PSGSuiteConfig` +* Updated: `Set-PSGSuiteConfig` to refresh the Spaces dictionary each time in order to remove stale spaces (i.e. on removal of bot from a Room or DM) +* Fixed: `Add-GSChatOnClick` now properly builds the hashtable for the Webhook object +* Updated: `Get-GSUser` to allow passing User ID's instead of emails by checking if value passed is a `decimal` before concatenating the domain name. + #### 2.12.1 * Fixed: `Get-GSDrivePermission` now returns all fields (including EmailAddress)