From 02be0b1aed028647848c982e19ae5779c7596822 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 10 Sep 2023 13:22:04 -0500 Subject: [PATCH] v5.0.4 (#475) * Allow plus addressing in Multi Inbox (#464) Extends regex pattern support on MultipleMailboxes.xaml * Remove "Resolved By User" Relationship when WorkItem is reactivated (#467) * Update smletsExchangeConnector.ps1 * Update smletsExchangeConnector.ps1 * Remove-SCSMRelationshipObject one liner After testing it does not appear there is a scenario wherein the try/catch will engage. --------- Co-authored-by: Adam * logging, cireson integration (#469) Introduce logging events for functions that are responsible for obtaining suggestion URLs (KB/RO) from one's Cireson portal * escape potential regex (#473) in the event the Suggestions are enabled and the email subject/body contain regex patterns, this will ensure they are escaped correctly * update version notes and contributors (#474) * version notes update inline notes and contributors * contributors updating contributors per release --------- Co-authored-by: Konstantin Slavin-Borovskij Co-authored-by: SimonZein <128088337+SimonZein@users.noreply.github.com> --- .../Forms/AboutForm.xaml | 2 +- .../Forms/MultipleMailboxes.xaml | 2 +- smletsExchangeConnector.ps1 | 169 +++++++++++------- 3 files changed, 108 insertions(+), 65 deletions(-) diff --git a/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/AboutForm.xaml b/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/AboutForm.xaml index 055b0369..3afc59ae 100644 --- a/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/AboutForm.xaml +++ b/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/AboutForm.xaml @@ -14,7 +14,7 @@ + Peter Miklian, Daniel Polivka, Alexander Axberg, Simon Zeinhofer, Konstantin Slavin-Borovskij" /> diff --git a/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/MultipleMailboxes.xaml b/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/MultipleMailboxes.xaml index 32b82e25..59d4e0fd 100644 --- a/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/MultipleMailboxes.xaml +++ b/ManagementPack/2016/SMLetsExchangeConnectorSettingsUI/Forms/MultipleMailboxes.xaml @@ -43,7 +43,7 @@ - + diff --git a/smletsExchangeConnector.ps1 b/smletsExchangeConnector.ps1 index 61f1f397..a30157bc 100644 --- a/smletsExchangeConnector.ps1 +++ b/smletsExchangeConnector.ps1 @@ -10,7 +10,7 @@ enabling other organizational level processes via email .NOTES Author: Adam Dzyacky -Contributors: Martin Blomgren, Leigh Kilday, Tom Hendricks, nradler2, Justin Workman, Brad Zima, bennyguk, Jan Schulz, Peter Miklian, Daniel Polivka, Alexander Axberg, Simon Zeinhofer +Contributors: Martin Blomgren, Leigh Kilday, Tom Hendricks, nradler2, Justin Workman, Brad Zima, bennyguk, Jan Schulz, Peter Miklian, Daniel Polivka, Alexander Axberg, Simon Zeinhofer, Konstantin Slavin-Borovskij Reviewers: Tom Hendricks, Brian Wiest Inspiration: The Cireson Community, Anders Asp, Stefan Roth, and (of course) Travis Wright for SMlets examples Requires: PowerShell 4+, SMlets, and Exchange Web Services API (already installed on SCSM workflow server by virtue of stock Exchange Connector). @@ -20,6 +20,10 @@ Requires: PowerShell 4+, SMlets, and Exchange Web Services API (already installe Signed/Encrypted option: .NET 4.5 is required to use MimeKit.dll Misc: The Release Record functionality does not exist in this as no out of box (or 3rd party) Type Projection exists to serve this purpose. You would have to create your own Type Projection in order to leverage this. +Version: 5.0.4 = #464 - Enhancement, Allow plus addressing in multi-mailbox + #467 - Bug, Resolved by User relationship should be nulled when reactivating a Work Item + #469 - Enhancement, More logging events around Cireson based integration and suggesting KA/RO + #473 - Bug, Emails with signature graphics that feature alt text prevent the suggestion feature from executing Version: 5.0.3 = #443 - Bug, Custom Events are incorrectly tied to a Logging Level #453 - Enhancement, Merge Reply functionality supports multi-language environments #452 - Bug, Fix for updating Work Items when the Comment is greater than 4000 characters @@ -3059,15 +3063,22 @@ function Get-CiresonPortalUser ) $isAuthUserAPIurl = "api/V3/User/IsUserAuthorized?userName=$username&domain=$domain" - if ($ciresonPortalWindowsAuth -eq $true) - { - $ciresonPortalUserObject = Invoke-RestMethod -Uri ($ciresonPortalServer+$isAuthUserAPIurl) -Method post -UseDefaultCredentials + try { + if ($ciresonPortalWindowsAuth -eq $true) + { + $ciresonPortalUserObject = Invoke-RestMethod -Uri ($ciresonPortalServer+$isAuthUserAPIurl) -Method post -UseDefaultCredentials + } + else + { + $ciresonPortalUserObject = Invoke-RestMethod -Uri ($ciresonPortalServer+$isAuthUserAPIurl) -Method post -Headers @{"Authorization"=Get-CiresonPortalAPIToken} + } + if ($loggingLevel -ge 4) {New-SMEXCOEvent -Source "Get-CiresonPortalUser" -EventID 0 -Severity Information -LogMessage "User/Sender found in Cireson Portal"} + return $ciresonPortalUserObject } - else - { - $ciresonPortalUserObject = Invoke-RestMethod -Uri ($ciresonPortalServer+$isAuthUserAPIurl) -Method post -Headers @{"Authorization"=Get-CiresonPortalAPIToken} + catch { + if ($loggingLevel -ge 3) {New-SMEXCOEvent -Source "Get-CiresonPortalUser" -EventID 1 -Severity Error -LogMessage $_.Exception} + return $null } - return $ciresonPortalUserObject } #retrieve a group from SCSM through the Cireson Web Portal API @@ -3081,17 +3092,24 @@ function Get-CiresonPortalGroup $adGroup = Get-ADGroup @adParams -Filter "Mail -eq $groupEmail" - if($ciresonPortalWindowsAuth) - { - #wanted to use a get groups style request, but "api/V3/User/GetConsoleGroups" feels costly instead of a search - $cwpGroupResponse = Invoke-RestMethod -Uri ($ciresonPortalServer+"api/V3/User/GetUserList?userFilter=$($adGroup.Name)&filterByAnalyst=false&groupsOnly=true&maxNumberOfResults=25") -UseDefaultCredentials + try { + if($ciresonPortalWindowsAuth) + { + #wanted to use a get groups style request, but "api/V3/User/GetConsoleGroups" feels costly instead of a search + $cwpGroupResponse = Invoke-RestMethod -Uri ($ciresonPortalServer+"api/V3/User/GetUserList?userFilter=$($adGroup.Name)&filterByAnalyst=false&groupsOnly=true&maxNumberOfResults=25") -UseDefaultCredentials + } + else + { + $cwpGroupResponse = Invoke-RestMethod -Uri ($ciresonPortalServer+"api/V3/User/GetUserList?userFilter=$($adGroup.Name)&filterByAnalyst=false&groupsOnly=true&maxNumberOfResults=25") -Headers @{"Authorization"=Get-CiresonPortalAPIToken} + } + $ciresonPortalGroup = $cwpGroupResponse | select-object @{Name='AccessGroupId'; Expression={$_.Id}}, name | Where-Object{$_.name -eq $($adGroup.Name)} + if ($loggingLevel -ge 4) {New-SMEXCOEvent -Source "Get-CiresonPortalGroup" -EventID 0 -Severity Information -LogMessage "Group/Sender found in Cireson Portal"} + return $ciresonPortalGroup } - else - { - $cwpGroupResponse = Invoke-RestMethod -Uri ($ciresonPortalServer+"api/V3/User/GetUserList?userFilter=$($adGroup.Name)&filterByAnalyst=false&groupsOnly=true&maxNumberOfResults=25") -Headers @{"Authorization"=Get-CiresonPortalAPIToken} + catch { + if ($loggingLevel -ge 3) {New-SMEXCOEvent -Source "Get-CiresonPortalGroup" -EventID 1 -Severity Error -LogMessage $_.Exception} + return $null } - $ciresonPortalGroup = $cwpGroupResponse | select-object @{Name='AccessGroupId'; Expression={$_.Id}}, name | Where-Object{$_.name -eq $($adGroup.Name)} - return $ciresonPortalGroup } #retrieve all the announcements on the portal @@ -3128,36 +3146,47 @@ function Search-AvailableCiresonPortalOffering ) $serviceCatalogAPIurl = "api/V3/ServiceCatalog/GetServiceCatalog?userId=$($ciresonPortalUser.id)&isScoped=$($ciresonPortalUser.Security.IsServiceCatalogScoped)" - if ($ciresonPortalWindowsAuth -eq $true) - { - $serviceCatalogResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$serviceCatalogAPIurl) -Method get -UseDefaultCredentials - } - else - { - $serviceCatalogResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$serviceCatalogAPIurl) -Method get -Headers @{"Authorization"=Get-CiresonPortalAPIToken} - } + try { + if ($ciresonPortalWindowsAuth -eq $true) + { + $serviceCatalogResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$serviceCatalogAPIurl) -Method get -UseDefaultCredentials + } + else + { + $serviceCatalogResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$serviceCatalogAPIurl) -Method get -Headers @{"Authorization"=Get-CiresonPortalAPIToken} + } - #### If the user has access to some Request Offerings, find which RO Titles/Description contain words from their original message #### - if ($serviceCatalogResults) - { - #prepare the results by generating a URL array for the email - $matchingRequestURLs = @() - foreach ($serviceCatalogResult in $serviceCatalogResults) + #### If the user has access to some Request Offerings, find which RO Titles/Description contain words from their original message #### + if ($serviceCatalogResults) { - $wordsMatched = ($searchQuery.Trim().Split() | Where-Object{($serviceCatalogResult.RequestOfferingTitle -match "\b$_\b") -or ($serviceCatalogResult.RequestOfferingDescription -match "\b$_\b")}).count - if ($wordsMatched -ge $numberOfWordsToMatchFromEmailToRO) + #prepare the results by generating a URL array for the email + $matchingRequestURLs = @() + foreach ($serviceCatalogResult in $serviceCatalogResults) { - $ciresonPortalRequestURL = "`"" + $ciresonPortalServer + "SC/ServiceCatalog/RequestOffering/" + $serviceCatalogResult.RequestOfferingId + "," + $serviceCatalogResult.ServiceOfferingId + "`"" - $RequestOfferingURL = "$($serviceCatalogResult.RequestOfferingTitle)
" - $requestOfferingSuggestion = [PSCustomObject] @{ - RequestOfferingURL = $RequestOfferingURL - WordsMatched = $wordsMatched + $wordsMatched = ($searchQuery.Trim().Split() | ForEach-Object {[regex]::Escape($_)} | Where-Object{($serviceCatalogResult.RequestOfferingTitle -match "\b$_\b") -or ($serviceCatalogResult.RequestOfferingDescription -match "\b$_\b")}).count + if ($wordsMatched -ge $numberOfWordsToMatchFromEmailToRO) + { + $ciresonPortalRequestURL = "`"" + $ciresonPortalServer + "SC/ServiceCatalog/RequestOffering/" + $serviceCatalogResult.RequestOfferingId + "," + $serviceCatalogResult.ServiceOfferingId + "`"" + $RequestOfferingURL = "$($serviceCatalogResult.RequestOfferingTitle)
" + $requestOfferingSuggestion = [PSCustomObject] @{ + RequestOfferingURL = $RequestOfferingURL + WordsMatched = $wordsMatched + } + $matchingRequestURLs += $requestOfferingSuggestion } - $matchingRequestURLs += $requestOfferingSuggestion } + $matchingRequestURLs = ($matchingRequestURLs | sort-object WordsMatched -Descending).RequestOfferingURL + if ($loggingLevel -ge 4) {New-SMEXCOEvent -Source "Search-AvailableCiresonPortalOffering" -EventID 0 -Severity Information -LogMessage "$($matchingRequestURLs.Count) relevant ROs found"} + return $matchingRequestURLs + } + else { + if ($loggingLevel -ge 2) {New-SMEXCOEvent -Source "Search-AvailableCiresonPortalOffering" -EventID 1 -Severity Warning -LogMessage "No relevant ROs were found"} + return $null } - $matchingRequestURLs = ($matchingRequestURLs | sort-object WordsMatched -Descending).RequestOfferingURL - return $matchingRequestURLs + } + catch { + if ($loggingLevel -ge 3) {New-SMEXCOEvent -Source "Search-AvailableCiresonPortalOffering" -EventID 2 -Severity Error -LogMessage $_.Exception} + return $null } } @@ -3171,35 +3200,46 @@ function Search-CiresonKnowledgeBase ) $kbAPIurl = "api/V3/Article/FullTextSearch?&searchValue=$searchQuery" - if ($ciresonPortalWindowsAuth -eq $true) - { - $kbResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$kbAPIurl) -UseDefaultCredentials - } - else - { - $kbResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$kbAPIurl) -Headers @{"Authorization"=Get-CiresonPortalAPIToken} - } + try { + if ($ciresonPortalWindowsAuth -eq $true) + { + $kbResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$kbAPIurl) -UseDefaultCredentials + } + else + { + $kbResults = Invoke-RestMethod -Uri ($ciresonPortalServer+$kbAPIurl) -Headers @{"Authorization"=Get-CiresonPortalAPIToken} + } - $kbResults = $kbResults | Where-Object{$_.endusercontent -ne ""} | select-object articleid, title + $kbResults = $kbResults | Where-Object{$_.endusercontent -ne ""} | select-object articleid, title - if ($kbResults) - { - $matchingKBURLs = @() - foreach ($kbResult in $kbResults) + if ($kbResults) { - $wordsMatched = ($searchQuery.Trim().Split() | Where-Object{($kbResult.title -match "\b$_\b")}).count - if ($wordsMatched -ge $numberOfWordsToMatchFromEmailToKA) + $matchingKBURLs = @() + foreach ($kbResult in $kbResults) { - $KnowledgeArticleURL = "$($kbResult.title)
" - $knowledgeSuggestion = [PSCustomObject] @{ - KnowledgeArticleURL = $KnowledgeArticleURL - WordsMatched = $wordsMatched + $wordsMatched = ($searchQuery.Trim().Split() | ForEach-Object {[regex]::Escape($_)} | Where-Object{($kbResult.title -match "\b$_\b")}).count + if ($wordsMatched -ge $numberOfWordsToMatchFromEmailToKA) + { + $KnowledgeArticleURL = "$($kbResult.title)
" + $knowledgeSuggestion = [PSCustomObject] @{ + KnowledgeArticleURL = $KnowledgeArticleURL + WordsMatched = $wordsMatched + } + $matchingKBURLs += $knowledgeSuggestion } - $matchingKBURLs += $knowledgeSuggestion } + $matchingKBURLs = ($matchingKBURLs | sort-object WordsMatched -Descending).KnowledgeArticleURL + if ($loggingLevel -ge 4) {New-SMEXCOEvent -Source "Search-CiresonKnowledgeBase" -EventID 0 -Severity Information -LogMessage "$($matchingKBURLs.Count) relevant KBs were found"} + return $matchingKBURLs + } + else { + if ($loggingLevel -ge 2) {New-SMEXCOEvent -Source "Search-CiresonKnowledgeBase" -EventID 1 -Severity Warning -LogMessage "No relevant KBs were found"} + return $null } - $matchingKBURLs = ($matchingKBURLs | sort-object WordsMatched -Descending).KnowledgeArticleURL - return $matchingKBURLs + } + catch { + if ($loggingLevel -ge 3) {New-SMEXCOEvent -Source "Search-CiresonKnowledgeBase" -EventID 2 -Severity Error -LogMessage $_.Exception} + return $null } } @@ -3236,6 +3276,8 @@ function Get-CiresonSuggestionURL return $null } + if ($loggingLevel -ge 4) {New-SMEXCOEvent -Source "Get-CiresonSuggestionURL" -EventID 0 -Severity Information -LogMessage "Retrieving Suggestion URLs from Cireson Portal. Suggest KA: $SuggestKA. SuggestRO: $SuggestRO."} + #check string length $wiTitle = if ($WorkItem.title.length -ge 1) {$WorkItem.title.trim()} else {$null} $wiDesc = if ($WorkItem.description.length -ge 1) {$WorkItem.description.trim()} else {$null} @@ -3678,6 +3720,7 @@ function Undo-WorkItemResolution "System.WorkItem.Problem" {$enumValue = "ProblemStatusEnum.Active$"; $resName = "Resolution"} } Set-SCSMObject -SMObject $WorkItem -propertyhashtable @{"Status" = $enumValue; $resName = $null; "ResolutionDescription" = $null; "ResolvedDate" = $null} @scsmMGMTParams; + Get-SCSMRelationshipObject -BySource $workItem -Filter "RelationshipId -eq '$($workResolvedByUserRelClass.Id)'" @scsmMGMTParams | Remove-SCSMRelationshipObject } function Read-MIMEMessage