Functions/Public/Alerts.ps1
|
# Alert configuration functions Function Get-NectarAlertConfig { <# .SYNOPSIS Returns a list of Nectar DXP alert configurations .DESCRIPTION Returns a list of Nectar DXP alert configurations .PARAMETER AlertType The type of alert to return data on. Choose from 'NectarScore-Users', 'NectarScore-Locations', or 'PoorCalls' .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER ResultSize The number of results to return. Defaults to 10000. .EXAMPLE Get-NectarAlertConfig Returns the first 10000 alert configurations .EXAMPLE Get-NectarAlertConfig -ResultSize 100 Returns the first 100 alert configurations .NOTES Version 1.0 #> [Alias("gna")] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ClientNotificationType")] [ValidateSet('NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER','ROOMS_AND_DEVICES_ALERT', IgnoreCase=$True)] [string]$AlertType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName, [Parameter(Mandatory=$False)] [ValidateRange(1,100000)] [int]$ResultSize=10000 ) Begin { Connect-NectarCloud } Process { Try { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } $Params = @{ 'tenant' = $TenantName 'orderByField' = 'name' 'orderDirection' = 'asc' 'pageSize' = $ResultSize } Switch ($AlertType) { {$_ -In 'NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION'} { $URI = "https://$Global:NectarCloud/aapi/client/notification/configurations" $Params.Add('clientNotificationTypes','NECTAR_SCORE_ALERT_BY_LOCATION,REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION') } {$_ -In 'NECTAR_SCORE_ALERT_BY_USER','NECTAR_SCORE_ALERT_BY_USER'} { $URI = "https://$Global:NectarCloud/aapi/client/notification/uhs/configurations" $Params.Add('types','NECTAR_SCORE_ALERT_BY_USER,REALTIME_NECTAR_SCORE_ALERT_BY_USER') } {$_ -In 'ROOMS_AND_DEVICES_ALERT'} { $URI = "https://$Global:NectarCloud/aapi/client/alert/configuration" $Params.Add('clientNotificationTypes','ROOMS_AND_DEVICES_ALERT') } } Write-Verbose $URI If (!$NectarError) { $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Global:NectarAuthHeader -Body $Params If ($TenantName) { $JSON.elements | Add-Member -Name 'TenantName' -Value $TenantName -MemberType NoteProperty -Force } # Add the tenant name to the output which helps pipelining $JSON.elements } } Catch { Write-Error "Unable to retrieve alert information. $($_.Exception.Message)" If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ } } } } Function Set-NectarAlertConfig { <# .SYNOPSIS Updates an existing Nectar alert configuration, including enabling or disabling it .DESCRIPTION Updates an existing Nectar alert configuration. Any combination of settings can be changed in one call. Use -Enabled to enable or disable the alert. If -Enabled is the only change, the alert's dedicated enable/disable endpoint is used and no other settings are touched. Accepts pipeline input from Get-NectarAlertConfig. .PARAMETER Name The name of the alert to update .PARAMETER ID The ID of the alert to update. If supplied, the alert is matched by ID rather than by name. .PARAMETER AlertType The type of alert. Choose from 'NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER' .PARAMETER MinNSThreshold The NectarScore percentage below which the alert triggers .PARAMETER MinTotalSessions The minimum number of sessions required in the period before the alert can trigger .PARAMETER AlertHeader The header text used in the alert email .PARAMETER AlertSubject The subject line used in the alert email .PARAMETER SendEmail $True to send email notifications, $False to disable them .PARAMETER EmailList One or more email addresses to send the alert to .PARAMETER EventAlert $True to create an event when the alert triggers, $False to disable event creation .PARAMETER EventActiveMessage The event message used when the alert becomes active .PARAMETER EventClearMessage The event message used when the alert clears .PARAMETER Time The time of day the alert is evaluated. Only applies to non-realtime alerts. .PARAMETER TimeZone The timezone the alert schedule runs in .PARAMETER TimePeriod The time period the alert evaluates. Select from 'LAST_HOUR','LAST_DAY','DYNAMIC'. DYNAMIC makes the alert realtime. .PARAMETER DynamicPeriodMinutes The number of minutes a realtime (DYNAMIC) alert looks back over .PARAMETER Enabled $True to enable the alert, $False to disable it .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Set-NectarAlertConfig -Name 'Teams NS under 60' -AlertType REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION -Enabled $True Enables the 'Teams NS under 60' location alert without changing any other settings .EXAMPLE Set-NectarAlertConfig -Name 'Teams NS under 60' -AlertType REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION -Enabled $False Disables the 'Teams NS under 60' location alert .EXAMPLE Set-NectarAlertConfig -Name 'Teams NS under 60' -AlertType REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION -MinNSThreshold 50 -Enabled $True Changes the threshold to 50% and enables the alert .EXAMPLE Get-NectarAlertConfig -AlertType REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION | Where-Object {$_.name -like '* NS under 60'} | Set-NectarAlertConfig -Enabled $True Enables every location alert whose name ends in 'NS under 60' .NOTES Version 1.1 #> [CmdletBinding(DefaultParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [int]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ClientNotificationType")] [ValidateSet('NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER', IgnoreCase=$True)] [string]$AlertType, [Parameter(Mandatory=$False)] [int]$MinNSThreshold, [Parameter(Mandatory=$False)] [int]$MinTotalSessions, [Parameter(Mandatory=$False)] [string]$AlertHeader, [Parameter(Mandatory=$False)] [string]$AlertSubject, [Parameter(Mandatory=$False)] [bool]$SendEmail, [Parameter(Mandatory=$False)] [string[]]$EmailList, [Parameter(Mandatory=$False)] [bool]$EventAlert, [Parameter(Mandatory=$False)] [string]$EventActiveMessage, [Parameter(Mandatory=$False)] [string]$EventClearMessage, [Parameter(Mandatory=$False)] [datetime]$Time, [Parameter(Mandatory=$False)] [string]$TimeZone, [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','DYNAMIC', IgnoreCase=$True)] [string]$TimePeriod, [Parameter(Mandatory=$False)] [int]$DynamicPeriodMinutes, [Parameter(Mandatory=$False)] [bool]$Enabled, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } $AlertConfig = Get-NectarAlertConfig -AlertType $AlertType -TenantName $TenantName | Where-Object { If ($ID) { $_.id -eq $ID } Else { $_.Name -eq $Name } } If (!$AlertConfig) { Throw "Could not find an alert config with the name `'$Name`'. Please verify the alert config exists." } # Work out whether any alert settings (other than the enabled state) are being changed $SettingParams = @('MinNSThreshold','MinTotalSessions','AlertHeader','AlertSubject','SendEmail','EmailList','EventAlert','EventActiveMessage','EventClearMessage','Time','TimeZone','TimePeriod','DynamicPeriodMinutes') $HasSettingChanges = [bool]($PSBoundParameters.Keys | Where-Object { $_ -in $SettingParams }) # If only the enabled state is changing, use the dedicated enable/disable endpoint and skip the full update If (!$HasSettingChanges) { If ($PSBoundParameters.ContainsKey('Enabled')) { Set-NectarAlertEnabledState -ID $AlertConfig.id -AlertType $AlertType -Enabled $Enabled -TenantName $TenantName $AlertConfig.enabled = $Enabled } Return $AlertConfig } # Remove items that are not needed for the update $ExcludedItems = @('clientNotificationTypeId', 'cronExpression', 'createdDate', 'createdByPortalUserId', 'updatedDate', 'updatedByPortalUserId', 'triggeredCount', 'enabled') $IncludedItems = $AlertConfig.PSObject.Properties.Name | Where-Object { $_ -notin $ExcludedItems } $AlertConfig = $AlertConfig | Select-Object -Property $IncludedItems $State = @{ 'filters' = $Filters 'graphsFilter' = @('TOTAL','AUDIO','VIDEO','APP_SHARING') 'quality' = @{'modality' = @('audio','video')} 'sessionList' = @{'modality' = 'ALL' 'orderByField' = 'startDate' 'orderDirection' = 'desc' 'pageNumber' = 1 'pageSize' = 10 } } $Params = @{ 'page' = 'callDetails' 'state' = $State } $JSONParams = $Params | ConvertTo-Json -Depth 7 $URI = "https://$Global:NectarCloud/dapi/link/share?tenant=$TenantName" Write-Verbose $URI $LinkID = Invoke-RestMethod_ErrorHandling -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONParams -ContentType 'application/json; charset=utf-8' Write-Verbose "LinkID: $LinkID" Write-Verbose 'PSBoundParameters:' Write-Verbose $PSBoundParameters # Reduce down to all that needs to be imported back in $UpdatePayload = @{ name = $AlertConfig.name thresholdFilter = @{ poorSessionPercentage = $AlertConfig.thresholdFilter.poorSessionPercentage minimumTotalSessions = $AlertConfig.thresholdFilter.minimumTotalSessions } criticalAlertMessage = $AlertConfig.criticalAlertMessage clearAlertMessage = $AlertConfig.clearAlertMessage dataFilter = @{ timePeriod = $AlertConfig.dataFilter.timePeriod dynamicPeriodMinutes = $AlertConfig.dataFilter.dynamicPeriodMinutes locations = $AlertConfig.dataFilter.locations platforms = $AlertConfig.dataFilter.platforms sessionTypes = $AlertConfig.dataFilter.sessionTypes modalities = $AlertConfig.dataFilter.modalities } clientNotificationType = $AlertConfig.clientNotificationType recipients = $AlertConfig.recipients hour = $AlertConfig.hour minute = $AlertConfig.minute linkId = $AlertConfig.linkId timeZone = $AlertConfig.timeZone sendEmail = $AlertConfig.sendEmail createEvent = $AlertConfig.createEvent } # Create a property map of all possible values and their final resting location in the configmap $PropertyMap = @{ Name = @{ Path = ''; Property = 'name' } ID = @{ Path = ''; Property = 'id' } AlertType = @{ Path = ''; Property = 'clientNotificationType' } MinNSThreshold = @{ Path = 'thresholdFilter'; Property = 'poorSessionPercentage' } MinTotalSessions = @{ Path = 'thresholdFilter'; Property = 'minimumTotalSessions' } AlertHeader = @{ Path = ''; Property = 'subject' } AlertSubject = @{ Path = ''; Property = 'name' } # If different from $Name, change this SendEmail = @{ Path = ''; Property = 'sendEmail' } EmailList = @{ Path = ''; Property = 'recipients' } EventAlert = @{ Path = ''; Property = 'createEvent' } EventActiveMessage = @{ Path = ''; Property = 'criticalAlertMessage' } EventClearMessage = @{ Path = ''; Property = 'clearAlertMessage' } Time = @{ Path = ''; Property = 'minute' } # Or split into hour/minute if needed TimeZone = @{ Path = 'dataFilter'; Property = 'timeZone' } TimePeriod = @{ Path = 'dataFilter'; Property = 'timePeriod' } DynamicPeriodMinutes = @{ Path = 'dataFilter'; Property = 'dynamicPeriodMinutes' } } Switch ($AlertType) { {$_ -In 'NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION'} { $URI = "https://$Global:NectarCloud/aapi/client/notification/configuration/$($AlertConfig.id)?tenant=$TenantName" } {$_ -In 'NECTAR_SCORE_ALERT_BY_USER','NECTAR_SCORE_ALERT_BY_USER'} { $URI = "https://$Global:NectarCloud/aapi/client/notification/uhs/configuration/$($AlertConfig.id)?tenant=$TenantName" } } Write-Verbose $URI # Remove AlertType from PSBoundParameters. Otherwise, realtime alerts will not be updated properly $PSBoundParameters.Remove('AlertType') # Iterate through $PSBoundParameters and update the configmap ForEach ($Key in $PSBoundParameters.Keys) { If ($PropertyMap.ContainsKey($Key)) { $Map = $PropertyMap[$Key] $Path = $Map.Path $Prop = $Map.Property $Value = $PSBoundParameters[$Key] If ($Path -eq '') { $UpdatePayload.$Prop = $Value } Else { # Create the subobject if missing If (-not $AC.$Path) { $UpdatePayload.$Path = @{} } $UpdatePayload.$Path.$Prop = $Value } } } $JSONParams = $UpdatePayload | ConvertTo-Json -Depth 7 Write-Verbose $JSONParams $JSON = Invoke-RestMethod_ErrorHandling -Method PUT -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONParams -ContentType 'application/json; charset=utf-8' # The update payload does not carry the enabled state, so apply it via the dedicated endpoint If ($PSBoundParameters.ContainsKey('Enabled')) { Set-NectarAlertEnabledState -ID $AlertConfig.id -AlertType $AlertType -Enabled $Enabled -TenantName $TenantName If ($JSON.PSObject.Properties.Name -contains 'enabled') { $JSON.enabled = $Enabled } } Return $JSON } } Function New-NectarAlertConfig { <# .SYNOPSIS Creates a new Nectar alert configuration .DESCRIPTION Creates a new Nectar alert configuration .PARAMETER TimePeriod The time period to show session data from. Select from 'LAST_HOUR','LAST_DAY' .PARAMETER SessionQualities Show sessions that match a given quality rating. Case sensitive. Choose one or more from: 'GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN' .PARAMETER NectarScore Show sessions that match the given range of NectarScores. Use format aa-xx or aa.bb-xx.yy, where aa is less than xx, and xx is less than 100. Eg. 70-90 or 95.00-95.59 .PARAMETER DurationFrom The shortest call length (in seconds) to show session data. .PARAMETER DurationTo The longest call length (in seconds) to show session data. .PARAMETER Modalities Show sessions that match one or more modality types. Not case sensitive. Choose one or more from: 'AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS','UNKNOWN' .PARAMETER Protocols Show sessions that match one or more network protocol types. Case sensitive. Choose one or more from: 'TCP','UDP','Unknown' .PARAMETER ResponseCodes Show sessions that match one or more SIP response codes. Accepts numbers from 200 to 699 .PARAMETER SessionScenarios Show sessions that match one or more session scenarios. Not case sensitive. Choose one or more from: 'External','Internal','Internal-External','External-Internal','Federated','Internal-Federated','External-Federated','Unknown' .PARAMETER SessionTypes Show sessions that match one or more session scenarios. Case sensitive. Choose one or more from: 'Conference','Peer To Peer','Peer To Peer (Multimedia)','PSTN/External' .PARAMETER Codecs Show sessions where the selected codec was used by either caller or callee. Can query for multiple codecs. Case sensitive. Use Get-NectarCodecs for a list of valid codecs. .PARAMETER Devices Show sessions where the selected device was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarSupportedDevice for a list of valid devices. .PARAMETER DeviceVersions Show sessions where the selected device version was used by either caller or callee. Can query for multiple devices. Case sensitive. Use Get-NectarClientVersion for a list of valid client versions. .PARAMETER AgentVersions Show sessions where the selected agent version was used by either caller or callee. Can query for multiple agent. Case sensitive. Use Get-NectarAgentVersion for a list of valid agent versions. .PARAMETER IPAddresses Show sessions where the selected IP address was used by either caller or callee. Can query for multiple IPs. .PARAMETER Locations Show sessions where the selected location was used by either caller or callee. Can query for multiple locations. .PARAMETER ExtCities Show sessions where the caller or callee was located in the selected city (as detected via geolocating the user's external IP address). Can query for multiple cities. .PARAMETER ExtCountries Show sessions where the caller or callee was located in the selected country (as detected via geolocating the user's external IP address). Can query for multiple countries. .PARAMETER ExtISPs Show sessions where the caller or callee was located in the selected ISP (as detected via geolocating the user's external IP address). Can query for multiple ISPs. .PARAMETER ExtConnectionTypes Show sessions that match the caller or callee external connection type (as detected via geolocating the user's external IP address). Can query for multiple types. .PARAMETER NetworkTypes Show sessions where the selected network type was used by either caller or callee. Can query for multiple network types. Case sensitive. Choose one or more from: 'CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED' .PARAMETER Platforms Show sessions where the selected platform was used by either caller or callee. Can query for multiple platforms. Case sensitive. Choose one or more from: 'SKYPE','CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','SKYPE_ONLINE','CISCO_CMS_VKM','AVAYA_AURA_CM','RIG','LYNC_VKM','SKYPE_FOR_BUSINESS_VKM','CISCO_UNITY','CISCO_EXPRESSWAY','AVAYA_MEDIA_GATEWAY','AVAYA_SESSION_MANAGER','AVAYA_VOICE_PORTAL','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','GENESYS','JABRA','MISCELLANEOUS' .PARAMETER Servers Shows sessions where the selected server was used by either caller or callee. Can query for multiple servers. Must follow pattern of PLATFORM:SERVERNAME. Eg. 'CISCO:Server1', 'AVAYA_AURA_CM:Server2' .PARAMETER EndpointTypes Show sessions where one or more selected Avaya endpoint types have been used. Choose one or more from: 'medsvr','mgdsp','node','phone','unknown' .PARAMETER Users Show sessions where the selected user was either caller or callee. Can query for multiple users. .PARAMETER UserIDs Show sessions where the selected user ID was either caller or callee. Can query for multiple user IDs. .PARAMETER FromUserIDs Show sessions where the selected user ID was the caller. Can query for multiple user IDs. .PARAMETER ToUserIDs Show sessions where the selected user ID was the callee. Can query for multiple user IDs. .PARAMETER ConfOrganizers Show sessions hosted by a specified conference organizer. Can query for multiple organizers. .PARAMETER VPN Show sessions where the selected VPN was used by either caller or callee. .PARAMETER ParticipantsMinCount Show sessions where the number of participants is greater than or equal to the entered value .PARAMETER ParticipantsMaxCount Show sessions where the number of participants is less than or equal to the entered value .PARAMETER FeedbackRating Show sessions where users provided specific feedback ratings from BAD to EXCELLENT. Allowed values are BAD, POOR, FAIR, GOOD, EXCELLENT. Corresponds to ratings from 1 to 5 stars. .PARAMETER Insights Show sessions that match one or more given insights. Choose from NORMAL_SESSION, VOICE_MAIL, HIGH_JITTER, HIGH_PACKET_LOSS, HIGH_ROUNDTRIP_DELAY .PARAMETER OrderByField Sort the output by the selected field .PARAMETER OrderDirection Sort direction. Use with OrderByField. Not case sensitive. Choose from: ASC, DESC .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .PARAMETER PageSize The size of the page used to return data. Defaults to 1000 .PARAMETER ResultSize The total number of results to return. Defaults to 1000. Maximum result size is 9,999,999 results .EXAMPLE Get-NectarSession -TimePeriod LAST_HOUR -Platforms TEAMS -Modalities AUDIO -NectarScore 50-70 Returns a list of all Teams audio sessions for the past hour where the NectarScore is between 50% and 70% .EXAMPLE (Get-NectarSession -SessionTypes CONFERENCE -TimePeriod CUSTOM -TimePeriodFrom '2021-05-06' -TimePeriodTo '2021-05-07').Count Returns a count of all conferences between May 6 and 7 (all times/dates UTC) .EXAMPLE Get-NectarSession -SessionTypes PEER2PEER,PEER2PEER_MULTIMEDIA -TimePeriod CUSTOM -TimePeriodFrom '2021-05-06 14:00' -TimePeriodTo '2021-05-06 15:00' Returns a list of all P2P calls between 14:00 and 15:00 UTC on May 6 .EXAMPLE Get-NectarSession -TimePeriod LAST_WEEK -SessionTypes CONFERENCE | Select-Object confOrganizerOrSpace | Group-Object confOrganizerOrSpace | Select-Object Name, Count | Sort-Object Count -Descending Returns a list of conference organizers and a count of the total conferences organized by each, sorted by count. .NOTES Version 1.0 #> [CmdletBinding(DefaultParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] Param ( [Parameter(Mandatory=$True)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [Alias("ClientNotificationType")] [ValidateSet('NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER', 'ROOMS_AND_DEVICES_ALERT', IgnoreCase=$True)] [string]$AlertType, [Parameter(Mandatory=$False)] [int]$MinNSThreshold = 60, [Parameter(Mandatory=$False)] [int]$MinTotalSessions = 10, [Parameter(Mandatory=$False)] [string]$AlertHeader = 'This automatically generated report shows the average Nectar Score for last time period.', [Parameter(Mandatory=$False)] [string]$AlertSubject = 'Nectar Score Alert', [Parameter(Mandatory=$False)] [bool]$SendEmail = $False, [Parameter(Mandatory=$False)] [string[]]$EmailList, [Parameter(Mandatory=$False)] [bool]$EventAlert = $False, [Parameter(Mandatory=$False)] [string]$EventActiveMessage = 'Nectar Score in $location_name is below $uhs_threshold %. User experience for this location is degraded.', [Parameter(Mandatory=$False)] [string]$EventClearMessage = 'Nectar Score in $location_name is above $uhs_threshold %. User experience for this location is restored to normal.', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [datetime]$Time = '12:05', [Parameter(Mandatory=$False)] [string]$TimeZone = (Get-NectarLocalTimeZone), [Parameter(Mandatory=$False)] [ValidateSet('LAST_HOUR','LAST_DAY','DYNAMIC', IgnoreCase=$True)] [string]$TimePeriod = 'LAST_HOUR', [Parameter(Mandatory=$False)] [int]$DynamicPeriodMinutes = 30, [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$NectarScore = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('GOOD','POOR_0_25','PARTIALLY_GOOD_25_50','PARTIALLY_GOOD_50_75','PARTIALLY_GOOD_75_100','UNAVAILABLE','UNKNOWN','ALL', IgnoreCase=$True)] [string[]]$SessionQualities = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateRange(0,99999999)] [int]$DurationFrom = 1, [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateRange(0,99999999)] [int]$DurationTo, [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('AUDIO','VIDEO','APP_SHARING','IM','UNKNOWN','TOTAL','VBSS','REMOTE_ASSISTANCE','APP_INVITE','FOCUS', IgnoreCase=$True)] [string[]]$Modalities = @(), [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('TCP','UDP','Unknown','ALL', IgnoreCase=$False)] [string[]]$Protocols = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$ResponseCodes = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('INTERNAL','EXTERNAL','FEDERATED','INTERNAL_EXTERNAL','EXTERNAL_INTERNAL','FEDERATED_INTERNAL','INTERNAL_FEDERATED','FEDERATED_EXTERNAL','EXTERNAL_FEDERATED','UNKNOWN', IgnoreCase=$True)] [string[]]$SessionScenarios = @(), [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('CONFERENCE','CONFERENCE_SESSION','CONVERSATION_JOURNEY','PEER2PEER','PEER2PEER_MULTIMEDIA','PSTN', IgnoreCase=$False)] [string[]]$SessionTypes = @(), [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$Codecs = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$Devices = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$DeviceVersions = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$IPAddresses = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$Locations = @(), [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$ExtCities = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$ExtCountries = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$ExtISPs = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('Cable','Dialup','DSL','FTTx','ISDN','Unknown','Wireless','ALL', IgnoreCase=$False)] [string[]]$ExtConnectionTypes = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('CELLULAR','ENTERPRISE_WIFI','ENTERPRISE_WIRED','EXTERNAL_WIFI','EXTERNAL_WIRED','MOBILE','PPP','UNKNOWN','WIFI','WIRED', IgnoreCase=$False)] [string[]]$NetworkTypes = @(), [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('CISCO','CISCO_CMS','CISCO_VKM','CISCO_WEBEX_CALLING','TEAMS','CISCO_CMS_VKM','AVAYA_AURA_CM','ZOOM','ENDPOINT_CLIENT','WEB_RTC_AIR_PHONE','WEB_RTC_AMAZON_CONNECT','WEB_RTC_CISCO_WEBEX','WEB_RTC_FIVE9','WEB_RTC_GENESYS_CLOUD','WEB_RTC_GENESYS_MCPE','WEB_RTC_NICE_CXONE','DIAGNOSTICS','GENESYS','JABRA', IgnoreCase=$True)] [string[]]$Platforms = @(), [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$Servers, [Parameter(Mandatory=$False)] [string[]]$Users = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [string[]]$ConfOrganizers, [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('TRUE','FALSE','UNKNOWN','ALL', IgnoreCase=$True)] [string[]]$VPN = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateRange(0,99999)] [int]$ParticipantsMinCount, [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateRange(0,99999)] [int]$ParticipantsMaxCount, [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('BAD','POOR','FAIR','GOOD','EXCELLENT','ALL', IgnoreCase=$False)] [string[]]$FeedbackRating = 'ALL', [Parameter(Mandatory=$False, ParameterSetName = 'NECTAR_SCORE_ALERT_BY_LOCATION')] [ValidateSet('NORMAL_SESSION','VOICE_MAIL','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY','AGENT', IgnoreCase=$False)] [string[]]$Insights = @('AGENT','NORMAL_SESSION','HIGH_JITTER','HIGH_PACKET_LOSS','HIGH_ROUNDTRIP_DELAY'), [Parameter(Mandatory=$False)] [bool]$Enabled = $True, [switch]$ShowQualityDetails, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } # Have to run this whole link share thing for some reason to return a link GUID to use in the actual creation of the rule $Filters = $PSBoundParameters $Filters.Add('excludeAvayaPpm', $True) $Filters.Add('platformServersOrDataCenters', @('All')) $Filters.Add('ratings', @('All')) $Filters.Add('sessionQualitySources', @('CDS','CDR_CDS')) $State = @{ 'filters' = $Filters 'graphsFilter' = @('TOTAL','AUDIO','VIDEO','APP_SHARING') 'quality' = @{'modality' = @('audio','video')} 'sessionList' = @{'modality' = 'ALL' 'orderByField' = 'startDate' 'orderDirection' = 'desc' 'pageNumber' = 1 'pageSize' = 10 } } $Params = @{ 'page' = 'callDetails' 'state' = $State } $JSONParams = $Params | ConvertTo-Json -Depth 7 $URI = "https://$Global:NectarCloud/dapi/link/share?tenant=$TenantName" Write-Verbose $URI $LinkID = Invoke-RestMethod_ErrorHandling -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONParams -ContentType 'application/json; charset=utf-8' Write-Verbose "LinkID: $LinkID" Switch ($AlertType) { { $_ -in @("NECTAR_SCORE_ALERT_BY_LOCATION", "REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION") } { $URI = "https://$Global:NectarCloud/aapi/client/notification/configuration?tenant=$TenantName" Write-Verbose $URI If ($TimePeriod = 'DYNAMIC') { $FormattedAlertType = 'REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION' } Else { $FormattedAlertType = $AlertType } $DataFilter = @{ 'dynamicPeriodMinutes' = $DynamicPeriodMinutes 'locations' = $Locations 'modalities' = $Modalities 'platforms' = $Platforms 'sessionTypes' = $SessionTypes 'timePeriod' = $TimePeriod } $AlertParams = @{ 'clientNotificationType' = $FormattedAlertType 'enabled' = $Enabled 'createEvent' = $EventAlert 'dataFilter' = $DataFilter 'hour' = $Time.hour 'linkId' = $LinkID 'minute' = $Time.minute 'name' = $Name 'recipients' = $EmailList 'sendEmail' = $SendEmail 'subject' = $AlertSubject 'criticalAlertMessage' = $EventActiveMessage 'clearAlertMessage' = $EventClearMessage 'timeZone' = $TimeZone 'thresholdFilter' = @{'poorSessionPercentage' = $MinNSThreshold 'minimumTotalSessions' = $MinTotalSessions } } } 'NECTAR_SCORE_ALERT' { If ($TimePeriod = 'DYNAMIC') { $FormattedAlertType = 'REALTIME_NECTAR_SCORE_ALERT_BY_USER' } Else { $FormattedAlertType = $AlertType } $URI = "https://$Global:NectarCloud/aapi/client/notification/uhs/configuration/?tenant=$TenantName" Write-Verbose $URI $AlertParams = @{ 'activeDirectoryGroups' = $ADGroupList 'checkEndpointClientUsers' = $EPCUsers 'clientNotificationType' = $FormattedAlertType 'createEvent' = $CreateEvent 'emailBody' = $AlertHeader 'emailRecipients' = $EmailList 'emailSubject' = $AlertSubject 'includeAllUsers' = $AllUsers 'monitoredUsersEmailBody' = $MonitoredUserEmailBody 'monitoredUsersEmailSubject'= $MonitoredUserEmailSubject 'monitoredUsersSmsBody' = $MonitoredUserSMSBody 'name' = $Name 'sendEmail' = $SendEmail 'sendMonitoredUsersEmail' = $SendMonitoredUserEmail 'sendMonitoredUsersSms' = $SendMonitoredUserSMS 'timeZone' = $TimeZone 'ushPercentage' = $MinNSThreshold 'userIds' = $UserIDList 'userNames' = $UserNameList } } 'ROOMS_AND_DEVICES_ALERT' { $URI = "https://$Global:NectarCloud/aapi/client/alert/configuration/?tenant=$TenantName" Write-Verbose $URI $AlertParams = @{ 'name' = $Name 'clientNotificationType' = $AlertType 'createEvent' = $EventAlert 'sendEmail' = $SendEmail 'enabled' = $True 'configJson' = @{ 'timeZone' = $TimeZone 'dataFilter' = @{ 'isDevices' = $false 'isRooms' = $true 'timePeriod' = "DYNAMIC" 'filterRules' = $null 'businessUnits' = @() 'crvLocations' = @() } 'emailOptions' = @{ 'subject' = $AlertSubject 'recipients' = $EmailList 'stateChangeAlertMessage' = "Room `$room_name has changed its health state from `$old_health_state to `$new_health_state. This was caused by the device `$device_name changing from `$old_device_state to `$new_device_state, which was as a result of `$metric_name changing from `$old_metric_state to `$new_metric_state." } 'scheduleOptions' = @{ 'hour' = $null 'minute' = 5 } } } } } Write-Verbose $URI $JSONParams = $AlertParams | ConvertTo-Json -Depth 7 Write-Verbose $JSONParams $JSON = Invoke-RestMethod_ErrorHandling -Method POST -URI $URI -Headers $Global:NectarAuthHeader -Body $JSONParams -ContentType 'application/json; charset=utf-8' # The create call does not always honour the enabled flag, so explicitly disable the alert afterwards if requested If (!$Enabled -And $JSON.id -And $JSON.enabled -And $FormattedAlertType -In @("NECTAR_SCORE_ALERT_BY_LOCATION", "REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION")) { Set-NectarAlertEnabledState -ID $JSON.id -AlertType $FormattedAlertType -Enabled $False -TenantName $TenantName $JSON.enabled = $False } Return $JSON } } Function Remove-NectarAlertConfig { <# .SYNOPSIS Removes an existing Nectar alert configuration .DESCRIPTION Removes an existing Nectar alert configuration. Only the alert ID is required. If the alert type is not supplied, the ID is looked up across the location and user alert lists to determine which endpoint to use. Accepts pipeline input from Get-NectarAlertConfig. .PARAMETER ID The ID of the alert to remove .PARAMETER AlertType The type of alert to delete. Optional; supplying it avoids a lookup. Choose from 'NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER' .PARAMETER TenantName The name of the Nectar DXP tenant. Used in multi-tenant configurations. .EXAMPLE Remove-NectarAlertConfig -ID 42 Removes the alert with ID 42 .EXAMPLE Get-NectarAlertConfig -AlertType REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION | Where-Object {$_.name -like '* NS under 60'} | Remove-NectarAlertConfig Removes every location alert whose name ends in 'NS under 60' .NOTES Version 1.1 #> [CmdletBinding(SupportsShouldProcess=$True)] Param ( [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)] [int]$ID, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [Alias("ClientNotificationType")] [ValidateSet('NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER', IgnoreCase=$True)] [string]$AlertType, [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)] [string]$TenantName ) Begin { Connect-NectarCloud } Process { # Use globally set tenant name, if one was set and not explicitly included in the command If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { $TenantName = $Global:NectarTenantName } ElseIf ($TenantName) { If ($TenantName -NotIn $Global:NectarTenantList) { $TList = $Global:NectarTenantList -join ', ' Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)" } } # If the alert type wasn't supplied, find the ID in the location alerts first, then the user alerts $AlertName = $Null If (!$AlertType) { ForEach ($Type in 'REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_USER') { $Match = Get-NectarAlertConfig -AlertType $Type -TenantName $TenantName -ErrorAction SilentlyContinue | Where-Object { $_.id -eq $ID } If ($Match) { $AlertType = $Match.clientNotificationType $AlertName = $Match.name Break } } If (!$AlertType) { Throw "Could not find an alert config with ID $ID. Please verify the alert config exists." } } Switch ($AlertType) { {$_ -In 'NECTAR_SCORE_ALERT_BY_LOCATION','REALTIME_NECTAR_SCORE_ALERT_BY_LOCATION'} { $URI = "https://$Global:NectarCloud/aapi/client/notification/configuration/$ID`?tenant=$TenantName" } {$_ -In 'NECTAR_SCORE_ALERT_BY_USER','REALTIME_NECTAR_SCORE_ALERT_BY_USER'} { $URI = "https://$Global:NectarCloud/aapi/client/notification/uhs/configuration/$ID`?tenant=$TenantName" } } Write-Verbose $URI $Target = If ($AlertName) { "alert '$AlertName' (ID $ID)" } Else { "alert ID $ID" } If ($PSCmdlet.ShouldProcess($Target, 'Remove')) { $NULL = Invoke-RestMethod_ErrorHandling -Method DELETE -URI $URI -Headers $Global:NectarAuthHeader } } } # SIG # Begin signature block # MIIlcwYJKoZIhvcNAQcCoIIlZDCCJWACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAsoHjy13mBek8Z # uo+UQpruazkkG1Mfm7PQCIQMERdikKCCEvMwggXdMIIDxaADAgECAgh7LJvTFoAy # mTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx # EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G # A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe # Fw0xNjAyMTIxNzM5MzlaFw00MTAyMTIxNzM5MzlaMHwxCzAJBgNVBAYTAlVTMQ4w # DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENv # cnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBB # dXRob3JpdHkgUlNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+Q/d # oyt9y9Aq/uxnhabnLhu6d+Hj9a+k7PpKXZHEV0drGHdrdvL9k+Q9D8IWngtmw1aU # nheDhc5W7/IW/QBi9SIJVOhlF05BueBPRpeqG8i4bmJeabFf2yoCfvxsyvNB2O3Q # 6Pw/YUjtsAMUHRAOSxngu07shmX/NvNeZwILnYZVYf16OO3+4hkAt2+hUGJ1dDyg # +sglkrRueiLH+B6h47LdkTGrKx0E/6VKBDfphaQzK/3i1lU0fBmkSmjHsqjTt8qh # k4jrwZe8jPkd2SKEJHTHBD1qqSmTzOu4W+H+XyWqNFjIwSNUnRuYEcM4nH49hmyl # D0CGfAL0XAJPKMuucZ8POsgz/hElNer8usVgPdl8GNWyqdN1eANyIso6wx/vLOUu # qfqeLLZRRv2vA9bqYGjqhRY2a4XpHsCz3cQk3IAqgUFtlD7I4MmBQQCeXr9/xQiY # ohgsQkCz+W84J0tOgPQ9gUfgiHzqHM61dVxRLhwrfxpyKOcAtdF0xtfkn60Hk7ZT # NTX8N+TD9l0WviFz3pIK+KBjaryWkmo++LxlVZve9Q2JJgT8JRqmJWnLwm3KfOJZ # X5es6+8uyLzXG1k8K8zyGciTaydjGc/86Sb4ynGbf5P+NGeETpnr/LN4CTNwumam # du0bc+sapQ3EIhMglFYKTixsTrH9z5wJuqIz7YcCAwEAAaNjMGEwHQYDVR0OBBYE # FN0ECQei9Xp9UlMSkpXuOIAlDaZZMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw # FoAU3QQJB6L1en1SUxKSle44gCUNplkwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 # DQEBCwUAA4ICAQAgGBGUKfsmnRweHnBh8ZVyk3EkrWiTWI4yrxuzcAP8JSt0hZA9 # eGr0uYullzu1GJG7Hqf5QFuR+VWZrx4R0Fwdp2bjsZQHDDI5puobsHnYHZxwROOK # 3cT5lR+KOEM/AYWlR6c9RrK85SJo93uc2Cw+CiHILTOsv8WBmTF0wXVxxb6x8CNF # 9J1r/BljnaO8BMYYCyW7U4kPs4BQ3kXuRH+rlHhkmNP2KN2H2HBldPsOuRPrpw9h # qTKWzN677WNMGLupQPegVG4giHF1GOp6tDRy4CMnd1y2kOqGJUCr7zMPy5+CvqIg # +/a1LRrmwoWxdA/7yGUCpFIBR91JIsG/2OtrrH7e7GMzFbcjCI/GD41BWt2OxbmP # 5UU/eNu60htAsf5xTT/ggaK6XrTsFeCT3QgffuFVmQsh3pOeCvvmo0m9NjD+53ey # oHWXtS2BiBdlIPfakACfyVLMMso1fPU9D9gr1/UmbMkGNJYW6nBZGjJ5eQu2iH8P # Ukg9v2zYokQu0U63cljTiROV/kSr+NeLG26cvCygW9VqAK9fN+HV+hALmJyG5yaP # zvDsbopXC4DjTrLAoGNhkLpVaDd0araS25+hhiK2ZScO7LafQmDkZ8K12kELxNOL # YRu8+h+RK9dEB166KazZxenvU0ha64DxKFghzbAGVfsnP1OQcKkEHlcnuTCCBnIw # ggRaoAMCAQICCGQzUdPHOJ8IMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVT # MQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NM # IENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlv # biBBdXRob3JpdHkgUlNBMB4XDTE2MDYyNDIwNDQzMFoXDTMxMDYyNDIwNDQzMFow # eDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9u # MREwDwYDVQQKDAhTU0wgQ29ycDE0MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25p # bmcgSW50ZXJtZWRpYXRlIENBIFJTQSBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBAJ+DE3OqsMZtIcvbi3qHdNBx3I6Xcprku4g0tN2AA8YvRaR0mr8e # D1Dqnm1485/6USapPZ3RspRXPvs5iRuRK1bvZ8vmC+MOOYzGNfSMPd0l6QGsF0J9 # WBZA3PnVKEQdlWQwYTpk8pfXc0x9eyMCbfN161U9b6otxK++dKxd/mq2/OpceekP # Q5y1UgUP7z6xsY/QSa2m40IZVD/zLw6hy3z+E/kjOdolHLg+AEo6bzIwN2Qex651 # B9hV0hjJDoq8o1zwfAqnhYHCDq+PmVzTYCW8g1ppHCUTzXL165yAm9wsZ8TdyQmY # 1XPrxCGj5TKOPi9SmMZgN2SMsm9KVHIYzCeH+s11omMhTLU9ZP0rpptVryZMYLS5 # XP6rQ72t0BNmUB8L0omm/9eABvHDEQIzM2EX91Yfji87aOcV8XdWSimeA9rCKyZh # MlugVuVJKY02p/XHUqJWAyAvOHiAvfYGrkE0y5RFvZvHiRgfC7r/qa5qQJkT3e9Q # 3wG68gTW0DHfNDheV1vIOB5W1KxIpu3/+bjBO+3CJL5EYKd3zdU9mFm0Q+qqYH3N # wuUv8ev11CDVlzRuXQRrBRHS05KMCSdE7U81MUZ+dBkFYuyJ4+ojcJjk0S/UihMY # RpNl5Vhz00w9J3oiP8P4o1W3+eaHguxFHsVuOnyxTrmraPebY9WRQbypAgMBAAGj # gfswgfgwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTdBAkHovV6fVJTEpKV # 7jiAJQ2mWTAwBggrBgEFBQcBAQQkMCIwIAYIKwYBBQUHMAGGFGh0dHA6Ly9vY3Nw # cy5zc2wuY29tMBEGA1UdIAQKMAgwBgYEVR0gADATBgNVHSUEDDAKBggrBgEFBQcD # AzA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY3Jscy5zc2wuY29tL3NzbC5jb20t # cnNhLVJvb3RDQS5jcmwwHQYDVR0OBBYEFFTC/hCVAJPNavXnwNfZsku4jwzjMA4G # A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEA9Q8mh3CvmaLK9dbJ8I1m # PTmC04gj2IK/j1SEJ7bTgwfXnieJTYSOVNEg7mBD21dCPMewlfa+zOqjPY5PBsYr # WYZ/63MbyuVAJuA9b8z2vXHGzX0OIEA51gXSr5QIv3/CUbcrtXuDIfBj2uWc4Wku # dR1Oy2Ee9aUz3wKdFdntaZNXukZFLoC8Zb7nEj7eR/+QnBCt9laypNT61vwuvJch # s3aD0pH6BlDRsYAogP7brQ9n7fh93NlwW3q6aLWzSmYXj+fw51fdaf68XuHVjJ8T # u5WaFft5K4XVbT5nR24bB1z7VEUPFhEuEcOwvLVuHDNXlB7+QjRGjjFQTtszV5X6 # OOTmEturWC5Ft9kiyvRaR0ksKOhPjEI8ZGjp5kOsGZGpxxOCX/xxCje3nVB7PF33 # olKCNeS159MKb2v+jfmk19UdS+d9Ygj42desmUnbtYRBFC72LmCXU0ua/vGIenS6 # nnXp4NqnycwsO3tMCnjPlPc2YLaDPIpUy04NaCqUEXUmFOogN8zreRd2VXhxbeJJ # ODM32+RsWccjYua8zi5US/1eAyrI3R5LcUTQdT4xYmWLKabtJOF6HYQ0f6QXfLSs # fT81WMvDvxrdn1RWbUXlU/OIiisxo8o+UNEANOwnCMNnxlzoaL/PLhZluDxm/zuy # lauajZ3MlPDteFB/7GRHo50wggaYMIIEgKADAgECAhBHaHS/7B9x7N3XWwMIISW7 # MA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQ # MA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NT # TC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjEwHhcNMjYw # NzA5MTQzMTE4WhcNMjcxMDEwMTQzMTE4WjB/MQswCQYDVQQGEwJVUzERMA8GA1UE # CAwITmV3IFlvcmsxEDAOBgNVBAcMB0plcmljaG8xHjAcBgNVBAoMFU5lY3RhciBT # ZXJ2aWNlcyBDb3JwLjELMAkGA1UECwwCSVQxHjAcBgNVBAMMFU5lY3RhciBTZXJ2 # aWNlcyBDb3JwLjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJmOMPRo # al1bEwe/HPHB533mkxLFf2LGg8R3yZQfDMkVWS0ME52aHJT33z61eMLqFamuvqY7 # qeb6jcOjqenYUhNmFcnl1Oq2hFWWE7McdgYAc0boWHemqxHWrUXN53sCp1DFVTh3 # NFwTo2PaY/KLdIjsXXvmA9qQBytzxEmeaF5HyCmmTlQ/N4eDeqpMOAbQn1n7lLi7 # xiHq1UAll3uvB+y7Vq/3V/dCO33/aiQif1t6h2R+N2JhoW0vtvd+yMyY58klGfN6 # O9WHto+MAiGoyP8rs9XWAzhIRZ2JjwFjfTqQOxVv0lUH+ZolhJ9uQHvybL3wHwLN # Qu8szoII3tqksiosdPc4Xk1nSyzrBfETafVVpey19HD6RhSIHrnPPHwWOzrdsT9j # O9szScE3i+cfJcKiL98lJkhFq/ZGOIwAPgxPvZZQZqbzEcsR0iddA+KNLxKqzkv9 # 1GtETHdFoU07xY7+2NsY1xhs9jFFNhoeCVh+dk3lIBvpWkuMwM8mZKziUQIDAQAB # o4IBlTCCAZEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRUwv4QlQCTzWr158DX # 2bJLuI8M4zB6BggrBgEFBQcBAQRuMGwwSAYIKwYBBQUHMAKGPGh0dHA6Ly9jZXJ0 # LnNzbC5jb20vU1NMY29tLVN1YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNl # cjAgBggrBgEFBQcwAYYUaHR0cDovL29jc3BzLnNzbC5jb20wUQYDVR0gBEowSDAI # BgZngQwBBAEwPAYMKwYBBAGCqTABAwMBMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8v # d3d3LnNzbC5jb20vcmVwb3NpdG9yeTATBgNVHSUEDDAKBggrBgEFBQcDAzBNBgNV # HR8ERjBEMEKgQKA+hjxodHRwOi8vY3Jscy5zc2wuY29tL1NTTGNvbS1TdWJDQS1D # b2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jcmwwHQYDVR0OBBYEFB7iZgW8t2SQ3I8n # dKd+fgqBvDYxMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAhCFC # ydfNbwEfqJhrp612HM4Qh7JUphMm5ZLNomSEsonhjz1ltmMpB/0nGVH9Ond23hac # 12+xzfnDAB2HsNhA9vVWYbgbxcH1duQbdRatjrtWKd1+YOfsvAS+O652yRQf26OL # +2efdAnzaqsTYFoEJwFcgl1M5Uo+uSht961Jm7McHouRm/zYOnC+NjvGOgIL40KN # P1pLQTxK5zc54+yFQANjiCPNI07AnkYCSgHDJCoKZbJTcUk26kQNqNE9AvjsIlpY # uY8sTXHSIkN3WNuSPYr66GHaYj9m0AGUKGt/ZZePKmOsLu+nON5hwqXx+QX75yc2 # AIlKw/ItfZvBLeRuejoKnbOh6QiyDxn0lfAH8f/upDPCUfvBVADF56itElnCo+fC # 36rzFnfBaVPbLVzqlAGSs7e6Yn669snsTHkKQm1fALEiZyt3dqOvppr8AO1f+oCc # r/0jVezz0VyNUemChoAMBosREctn/fOYfL5NClq/mnI5m+tAHOajVk8LI6h7mlNv # tIFKwfHCIH2EJM4ze+ZrSXWt8bfY+6pQwd4xCB9UmCw2DBBMQ11rvu1rLD+u3kIL # NBAXHQGEnCoP+sY6h17wc/2Jb8BPg+yAvPXevqXmAdAKJrhvMmgsbttRxOQuVjrq # 42h1me0i3Jy0obhODmpXsrVPm02ZBIYgiRcK/q8xghHWMIIR0gIBATCBjDB4MQsw # CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAP # BgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmluZyBJ # bnRlcm1lZGlhdGUgQ0EgUlNBIFIxAhBHaHS/7B9x7N3XWwMIISW7MA0GCWCGSAFl # AwQCAQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB # gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE # MSIEIJPZHcxkXRWRWGJV1OziaIK6miFfH+LLAsZFurtQoaIfMA0GCSqGSIb3DQEB # CwUABIIBgFuC/Gg1rA9wlOEiiJnS6KohQt/AHyJXltxx3vHCkdiLLkMxSb/6Hj4A # LenrrKW13a1I55z3La8lcFh2HVtrWg3odfvCQFPxnXVN6q3gVeeStl+ppTs4G9w5 # yJvEFCxWRKrg8hx45AHxH4uvijSW8w/SFX9Omib2Rsu1E9n5flmTytlZgxAZ+NdO # ne6yG04kCW8C5gbwsOv9yICqke74OOLOrLTbo6Js6VpRHn5Uf3ei7olPi69hUGXZ # 3Zl+WM5ChF5KsBk5pb9zDgCngVTKVepH37xjsBnIjX0bSZ3waVNYGF6cFzs5RL6j # XGAKuUzMgudVlG6GGybD72avk9KuEucdSKzC1cqjgQ8UOBpUp57I9ssOOhVq1VKs # yxvuBsQzt3BLCgXFkOfUwP+cQm3xcL8/WwqSgMVZt0n2EafWiQcQqJtIdnTwNpsB # 1FT/M6V0z4Zrx2HVtgV07M6Qotr05/XlnO61vkeo2RsxBU7TB7bDtRFYPJdQTvvB # uq4yKJrdN6GCDxwwgg8YBgorBgEEAYI3AwMBMYIPCDCCDwQGCSqGSIb3DQEHAqCC # DvUwgg7xAgEDMQ0wCwYJYIZIAWUDBAIBMH0GCyqGSIb3DQEJEAEEoG4EbDBqAgEB # BgwrBgEEAYKpMAEDBgEwLzALBglghkgBZQMEAgEEINpNz5YTqu40fh4TSGz6V1ml # PxnthrIGtC7SscxBQ+lqAggRCuMQARKiAhgPMjAyNjA5MTUxNzI3NDdaMAMCAQEC # BgGgphxLo6CCDAAwggT8MIIC5KADAgECAhAfaxZi0i4bbF3xwMGgYA44MA0GCSqG # SIb3DQEBCwUAMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UE # BwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20g # VGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMB4XDTI1MDIxODE2MzIwMloX # DTM0MTExMjE4NTAwNVowbjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAw # DgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEqMCgGA1UEAwwhU1NM # LmNvbSBUaW1lc3RhbXBpbmcgVW5pdCAyMDI1IEUxMFkwEwYHKoZIzj0CAQYIKoZI # zj0DAQcDQgAEG/tRUcdv5lWW7E9eV8Tczq2DReerx2Jz47e884JGlqVQzW870D4Z # HNJVWPLKAeFisHDrZcsWHWS/t77JF39pNqOCAVowggFWMB8GA1UdIwQYMBaAFAyd # ECWOmqcbmYdDzwh+4b2BkPTPMFEGCCsGAQUFBwEBBEUwQzBBBggrBgEFBQcwAoY1 # aHR0cDovL2NlcnQuc3NsLmNvbS9TU0wuY29tLXRpbWVTdGFtcGluZy1JLVJTQS1S # MS5jZXIwUQYDVR0gBEowSDA8BgwrBgEEAYKpMAEDBgEwLDAqBggrBgEFBQcCARYe # aHR0cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MAgGBmeBDAEEAjAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDBGBgNVHR8EPzA9MDugOaA3hjVodHRwOi8vY3Jscy5z # c2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIxLmNybDAdBgNVHQ4E # FgQUznzZwASAxSQQagnqHKslPRH9qNIwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3 # DQEBCwUAA4ICAQCAc3Ukhb1mU2KnTsV9j2vUsnAspOXWH/L2vUGMOAcwTPtTsnuY # DLfYnEDUovKMIImo2S5F+EMcYUR9m2NM6u7sBAwNIOJQO8IJzeNrPmnL2Ma/Ah7m # emQttepeED5KoLMbvX1RKKDCEeRivu/w2JehpjRe7TenQGJlmt5mWmeCYYH37zo3 # 3gWogXHYjlnmK67t3iPtoA5kE3F9T2MUMggYO1Z9Z4KkXRDyssT/cMcOXMkqzkiX # eL9Wg6XutNT3fyhKvEzDDDoYMGUpfysYfG+SOAhv0xeRWCUlIMew0BkN4JL+KdrE # ocD4KG4Hwrg7EjFrqTV754cHKlqQBjfC43vDs+U+aE3qTkh2pmfcdkezZWOhHzjV # n3CZU8V0YN2QFntc6Zvk5lRoq5+y+0RHRVtjYOTNqoBoi23WRz4j4VTPs+JXPY9T # Ol6CR+1FHG+s/IgvTxuUlOdsxDReuoM3SsR+5Mu/heGGcrIlpeHEJR2M79xG6YzN # nflBNQwi0FbLXEanSLKgVWcDJrak+xUy4Aj6zLXPGU5L2XmJLG8onyCmek6COphN # ru7V7Jmj7gmVwaiKJHXsu2ExOsXWrra07nE6kjy3FRnqC0oa2QlXrB2P69ktzApn # Yz3capWk6jpQGUaPHWwqxVnsAhTMlmWLg0nQzYphyt82eV5uRgqkOdKpbDCCBvww # ggTkoAMCAQICEG1SGHCH6CNNhWAA0ICPk1YwDQYJKoZIhvcNAQELBQAwfDELMAkG # A1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYD # VQQKDA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0 # aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTkxMTEzMTg1MDA1WhcNMzQxMTEy # MTg1MDA1WjBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcM # B0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29tIFRp # bWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMTCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAK5REBPS+TwgoCCF3slQHGTJ4f3F6TT/Cn8xSOhyWsVeqGH9 # 8Yf3UVz7t+bQwcITsD7CY6KoGP04OskBgareubfeMKcdKwIE1YBBjKhq4urwiOqx # LUmVcvb2oM0wx3BnxQ3NBLu9ZkwMnjQlIY2mEwZMgDaqfZuiEa2BFzinXf3kRLKl # Q5oa8ne3QU0vcG4qZvphy0xxBQXayqigzN3z2HQTq6N28EOjpnA2dajGPtiZ9aNJ # eDfcDka5j3KbhBkzk4RWCjx5vP8H6DKHIIs02GHgxv/jG8JMIxWY1isG+IaB09li # vKbxlvzhNAKZK5fQmUstrpYrVo7qqXAhJtv1tUaHzrp6QpuUL9dE/bSAC7UKO9xh # yJSA1OsYWDx/wAmBA84JzX8IJ1olJjCEmlJ2F4o6dCARKA2Zhk+EU4LogpowBReT # lTW2NNwUKAW+8Cte0rhrMBZQ47Vjd92V0gEvouOTMtQJgk2QVeqGwFVw8y4HSdQN # a8sl8+Kay2MnyUXhLoQLFaeVaLs4SVXBOe3Ua1Gp5j3J2+8Yue1T4V5wrsNuocNR # 3frpSt4yRIG3N68Bz1qqhk+eNUyO8WpXWlg6POZOJUdm0BzzRsB8V7kst8nM8joO # e03KqhunBN69Ckeo8M32qo07zeveRrDwD2P4dmJLDYBflwZ1A/SQbS+HN+AHAgMB # AAGjggGBMIIBfTASBgNVHRMBAf8ECDAGAQH/AgEAMB8GA1UdIwQYMBaAFN0ECQei # 9Xp9UlMSkpXuOIAlDaZZMIGDBggrBgEFBQcBAQR3MHUwUQYIKwYBBQUHMAKGRWh0 # dHA6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5L1NTTGNvbVJvb3RDZXJ0aWZpY2F0 # aW9uQXV0aG9yaXR5UlNBLmNydDAgBggrBgEFBQcwAYYUaHR0cDovL29jc3BzLnNz # bC5jb20wPwYDVR0gBDgwNjA0BgRVHSAAMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8v # d3d3LnNzbC5jb20vcmVwb3NpdG9yeTATBgNVHSUEDDAKBggrBgEFBQcDCDA7BgNV # HR8ENDAyMDCgLqAshipodHRwOi8vY3Jscy5zc2wuY29tL3NzbC5jb20tcnNhLVJv # b3RDQS5jcmwwHQYDVR0OBBYEFAydECWOmqcbmYdDzwh+4b2BkPTPMA4GA1UdDwEB # /wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAkhl1DaZaQs8ZB9ny/JT6wJvwFelE # llovcTPdUOUTe5mTdw/E+3JtV8u6ppyLRbpIHbYlMy20KJAychU6xdaci4BsP9oV # NxSRMsEjfHKz7ARqPNdpclhYAINLjsFGMO1iUNbXiAsnF/xboNCgfeMcMYbLyQYk # U6UMobv9isrtQZ8e0EAQNV7qXJn4W0KyuTt0P8iIv/5DdDpIUBIktDZcjz2KEW6B # 1gvvsKIM1esjYwWylAazBcQAake5pANMdSn8t1HdPKsiwuWfOguyRQazAX8oXz6S # lZSIok0Lis9a02vGVtdhEaB0R3HxIyNRMMKWV1yuSeUXFuoexWav3GRPZC0WYb50 # SrW/l+wgrS8doetaMwyZon2L7ioYlIPSy1h9Dq/Q911PsSkbEZ3zrsB1roVnIfBu # 5BJp0xvQrQ/Q4LavuvCoFR7QFoypNrotbNYi2AGMZw5td4zGZtCqUTPZi0BwSuRm # +HRYAEMMThTwbJX/fYV1oC8mBN970yIvadIGKhh7+DmYdRJYBrL8inVFCZAK+YX2 # w1+qWEnCSPL/VTWJtSRMhQFfceDKbJC+pBNksvKzqkva0J1ZyMj1i4vDfSuBmbz4 # rfzsvvJxS+quZDdkmW6MeXevWGBXvqzdbAw+AqTVsAQUyP6tFeKZIL4S/fSFdl2r # Ix2X+KXkqx3S+EYxggJYMIICVAIBATCBhzBzMQswCQYDVQQGEwJVUzEOMAwGA1UE # CAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8w # LQYDVQQDDCZTU0wuY29tIFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQ # H2sWYtIuG2xd8cDBoGAOODALBglghkgBZQMEAgGgggFhMBoGCSqGSIb3DQEJAzEN # BgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwOTE1MTcyNzQ3WjAoBgkq # hkiG9w0BCTQxGzAZMAsGCWCGSAFlAwQCAaEKBggqhkjOPQQDAjAvBgkqhkiG9w0B # CQQxIgQgV3KIYUalowCiiHwTgkgiK/FThlKNhewFVKvJifAhak8wgckGCyqGSIb3 # DQEJEAIvMYG5MIG2MIGzMIGwBCBUKvmhao1yLmYRSXiK6ZTBipqu5aZcs0SiVJr5 # bHnHizCBizB3pHUwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYD # VQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNv # bSBUaW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjECEB9rFmLSLhtsXfHAwaBg # DjgwCgYIKoZIzj0EAwIERzBFAiAiXr7WFUnoSbMdXKImD2i6R9V8wMXVAp+FQR08 # a+l5YwIhAJX1LWqrnqvdFFvusD9ORO+64kfTiFfv07p1MeYGebOl # SIG # End signature block |