PSWinDocumentation.O365HealthService.psm1
function Connect-Graphimo { [cmdletBinding(DefaultParameterSetName = 'ClearText')] param( [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(Mandatory, ParameterSetName = 'ClearText')][string][alias('ClientID')] $ApplicationID, [parameter(Mandatory, ParameterSetName = 'ClearText')][string][alias('ClientSecret')] $ApplicationKey, [parameter(Mandatory, ParameterSetName = 'Encrypted')][string][alias('ClientSecretEncrypted')] $ApplicationKeyEncrypted, [parameter(Mandatory, ParameterSetName = 'Credential')][PSCredential] $Credential, [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(Mandatory, ParameterSetName = 'ClearText')] [parameter(Mandatory, ParameterSetName = 'Credential')] [string] $TenantDomain, [parameter(ParameterSetName = 'Encrypted')] [parameter(ParameterSetName = 'ClearText')] [parameter(ParameterSetName = 'Credential')] [ValidateSet("https://manage.office.com", "https://graph.microsoft.com", "https://graph.microsoft.com/beta", 'https://graph.microsoft.com/.default')] $Resource = 'https://graph.microsoft.com/.default', [int] $ExpiresTimeout = 30, [switch] $ForceRefesh, [parameter(Mandatory, ParameterSetName = 'MsalToken')][System.Collections.IDictionary] $MsalToken ) # Comparison V1/V2 https://nicolgit.github.io/AzureAD-Endopoint-V1-vs-V2-comparison/ if (-not $Script:AuthorizationCache) { $Script:AuthorizationCache = [ordered] @{} } if ($Credential) { $RestSplat = @{ ErrorAction = 'Stop' Method = 'POST' Body = @{ grant_type = "client_credentials" client_id = $Credential.UserName client_secret = $Credential.GetNetworkCredential().Password } } } elseif ($ApplicationKey -or $ApplicationKeyEncrypted) { if ($ApplicationKeyEncrypted) { try { $ApplicationKeyTemp = $ApplicationKeyEncrypted | ConvertTo-SecureString -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-Graphimo - Error: $ErrorMessage" return } $ApplicationKey = [System.Net.NetworkCredential]::new([string]::Empty, $ApplicationKeyTemp).Password } $RestSplat = @{ ErrorAction = 'Stop' Method = 'POST' Body = @{ grant_type = "client_credentials" client_id = $ApplicationID client_secret = $ApplicationKey } } } elseif ($MsalToken) { $Authorization = @{ Splat = $MsalToken } return Connect-MsalToken -Authorization $Authorization -ExpiresTimeout $ExpiresTimeout -ForceRefesh:$ForceRefesh } if ($Script:AuthorizationCache[$ApplicationID] -and -not $ForceRefesh) { if ($Script:AuthorizationCache[$ApplicationID].ExpiresOn -gt [datetime]::UtcNow) { Write-Verbose "Connect-Graphimo - Using cache for $ApplicationID" return $Script:AuthorizationCache[$ApplicationID] } } if ($Resource -in 'https://graph.microsoft.com/.default', "https://graph.microsoft.com/beta") { # V2 Endpoint $RestSplat['Body']['scope'] = $Resource $RestSplat['Uri'] = "https://login.microsoftonline.com/$($TenantDomain)/oauth2/v2.0/token" } else { # V1 Endpoint $RestSplat['Body']['resource'] = $Resource $RestSplat['Uri'] = "https://login.microsoftonline.com/$($TenantDomain)/oauth2/token" } Write-Verbose "Connect-Graphimo - EndPoint $($RestSplat['Uri'])" try { $Authorization = Invoke-RestMethod @RestSplat -Verbose:$false $Key = [ordered] @{ 'Authorization' = "$($Authorization.token_type) $($Authorization.access_token)" 'Extended' = $Authorization 'Error' = '' 'ExpiresOn' = ([datetime]::UtcNow).AddSeconds($Authorization.expires_in - $ExpiresTimeout) 'Splat' = [ordered] @{ ApplicationID = $RestSplat['Body']['client_id'] ApplicationKey = $RestSplat['Body']['client_secret'] TenantDomain = $TenantDomain Resource = $Resource } } $Script:AuthorizationCache[$ApplicationID] = $Key } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Connect-Graphimo - Error: $ErrorMessage" $Key = [ordered] @{ 'Authorization' = $Null 'Extended' = $Null 'Error' = $ErrorMessage 'ExpiresOn' = $null 'Splat' = [ordered] @{ ApplicationID = $RestSplat['Body']['client_id'] ApplicationKey = $RestSplat['Body']['client_secret'] TenantDomain = $TenantDomain Resource = $Resource } } } $Key } function Convert-TimeToDays { [CmdletBinding()] param ( $StartTime, $EndTime, #[nullable[DateTime]] $StartTime, # can't use this just yet, some old code uses strings in StartTime/EndTime. #[nullable[DateTime]] $EndTime, # After that's fixed will change this. [string] $Ignore = '*1601*' ) if ($null -ne $StartTime -and $null -ne $EndTime) { try { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End $EndTime).Days } } catch { } } elseif ($null -ne $EndTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start (Get-Date) -End ($EndTime)).Days } } elseif ($null -ne $StartTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End (Get-Date)).Days } } return $Days } function Find-TypesNeeded { [CmdletBinding()] param ( [Array] $TypesRequired, [Array] $TypesNeeded ) [bool] $Found = $False foreach ($Type in $TypesNeeded) { if ($TypesRequired -contains $Type) { $Found = $true break } } return $Found } function Get-Types { [CmdletBinding()] param ( [Object] $Types ) $TypesRequired = foreach ($Type in $Types) { $Type.GetEnumValues() } return $TypesRequired } function Invoke-Graphimo { [cmdletBinding(SupportsShouldProcess)] param( [alias('PrimaryUri')][uri] $BaseUri = 'https://graph.microsoft.com/v1.0', [uri] $Uri, [parameter(Mandatory)][alias('Authorization')][System.Collections.IDictionary] $Headers, [validateset('GET', 'DELETE', 'POST', 'PATCH', 'PUT')][string] $Method = 'GET', [string] $ContentType = "application/json; charset=UTF-8", [System.Collections.IDictionary] $Body, [System.Collections.IDictionary] $QueryParameter, [switch] $FullUri, [int] $First ) if ($Headers.MsalToken) { if ($Headers.Splat) { $Splat = $Headers.Splat $Headers = Connect-MsalToken -Authorization $Headers } } else { # This forces a reconnect of session in case it's about to time out. If it's not timeouting a cache value is used if ($Headers.Splat) { $Splat = $Headers.Splat $Headers = Connect-Graphimo @Splat } } if ($Headers.Error) { Write-Warning "Invoke-Graphimo - Authorization error. Skipping." return } if ($Headers.MsalToken) { $RestSplat = @{ Headers = @{ Authorization = $Headers.MsalToken.TokenType + ' ' + $Headers.MsalToken.AccessToken } Method = $Method ContentType = $ContentType } } else { $RestSplat = @{ Headers = $Headers Method = $Method ContentType = $ContentType } } if ($Body) { $RestSplat['Body'] = $Body | ConvertTo-Json -Depth 5 } if ($FullUri) { $RestSplat.Uri = $Uri } else { $RestSplat.Uri = Join-UriQuery -BaseUri $BaseUri -RelativeOrAbsoluteUri $Uri -QueryParameter $QueryParameter } if ($RestSplat['Body']) { $WhatIfInformation = "Invoking [$Method] " + [System.Environment]::NewLine + $RestSplat['Body'] + [System.Environment]::NewLine } else { $WhatIfInformation = "Invoking [$Method] " } try { if ($Method -eq 'GET') { Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)over URI $($RestSplat.Uri)" $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false # if ($OutputQuery.value) { # $FoundUsers = $OutputQuery.value # } # if ($First) { # if ($FoundUsers.Count -eq $First) { # return $FoundUsers # } elseif ($FoundUsers.Count -gt $First) { # $FoundUsers = $FoundUsers | Select-Object -First $First # return $FoundUsers # } else { # $First = $First - $FoundUsers.Count # $FoundUsers # } # } else { # $FoundUsers # } $Count = 1 [Array] $FoundUsers = Invoke-InternalGraphimo -OutputQuery $OutputQuery -First $First $CurrentCount = $FoundUsers.Count if ($First -gt 0 -and $CurrentCount -ge $First) { return $FoundUsers } else { $FoundUsers } if ($OutputQuery.'@odata.nextLink') { Do { $RestSplat.Uri = $OutputQuery.'@odata.nextLink' #$MoreData = Invoke-Graphimo @RestSplat -FullUri -First $First Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)NextLink (Page $Count/Current Count: $($CurrentCount))) over URI $($RestSplat.Uri)" $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false [Array] $FoundUsers = Invoke-InternalGraphimo -OutputQuery $OutputQuery -First $First -CurrentCount $CurrentCount $FoundUsers $Count++ $CurrentCount = $CurrentCount + $FoundUsers.Count } Until (($First -gt 0 -and $CurrentCount -ge $First) -or $null -eq $OutputQuery.'@odata.nextLink') } } else { Write-Verbose "Invoke-Graphimo - $($WhatIfInformation)over URI $($RestSplat.Uri)" if ($PSCmdlet.ShouldProcess($($RestSplat.Uri), $WhatIfInformation)) { $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false if ($Method -in 'POST') { $OutputQuery } else { return $true } } } } catch { $RestError = $_.ErrorDetails.Message if ($RestError) { try { $ErrorMessage = ConvertFrom-Json -InputObject $RestError # Write-Warning -Message "Invoke-Graphimo - [$($ErrorMessage.error.code)] $($ErrorMessage.error.message), exception: $($_.Exception.Message)" Write-Warning -Message "Invoke-Graphimo - Error: $($_.Exception.Message) $($ErrorMessage.error.message)" } catch { Write-Warning -Message "Invoke-Graphimo - Error: $($_.Exception.Message)" } } else { Write-Warning -Message "Invoke-Graphimo - Error: $($_.Exception.Message)" } if ($Method -notin 'GET', 'POST') { return $false } } } function Remove-EmptyValue { [alias('Remove-EmptyValues')] [CmdletBinding()] param( [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun, [switch] $DoNotRemoveNull, [switch] $DoNotRemoveEmpty, [switch] $DoNotRemoveEmptyArray, [switch] $DoNotRemoveEmptyDictionary ) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } function Start-TimeLog { [CmdletBinding()] param() [System.Diagnostics.Stopwatch]::StartNew() } function Stop-TimeLog { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time, [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner', [switch] $Continue ) Begin { } Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } } End { if (-not $Continue) { $Time.Stop() } return $TimeToExecute } } function Connect-MsalToken { [cmdletBinding()] param( [System.Collections.IDictionary] $MsalTokenSplat, [System.Collections.IDictionary] $Authorization, [int] $ExpiresTimeout = 30, [switch] $ForceRefesh ) if (-not $Script:AuthorizationCache) { $Script:AuthorizationCache = [ordered] @{} } if ($Authorization.Splat) { $ApplicationID = $Authorization.Splat.ClientId if ($Script:AuthorizationCache[$ApplicationID] -and -not $ForceRefesh) { if ($Script:AuthorizationCache[$ApplicationID].MsalToken.ExpiresOn.UtcDateTime -gt ([datetime]::UtcNow).AddSeconds($ExpiresTimeout)) { Write-Verbose "Connect-MsalToken - Using cache for $ApplicationID" return $Script:AuthorizationCache[$ApplicationID] } } $Splat = $Authorization.Splat try { $MsalToken = Get-MsalToken @Splat -ErrorAction Stop } catch { Write-Warning -Message "Connect-MsalToken - Couldn't execute Get-MsalToken. Error: $($_.Exception.Message)" return } $Script:AuthorizationCache[$ApplicationID] = [ordered] @{ 'MsalToken' = $MsalToken 'Splat' = $Authorization.Splat } $Script:AuthorizationCache[$ApplicationID] } else { Write-Warning -Message "Connect-MsalToken - Using old authorization format without Splatting. No refresh of tokens will be available." } } function Invoke-InternalGraphimo { [CmdletBinding()] param( [Array] $OutputQuery, [int] $First, [int] $CurrentCount ) if ($OutputQuery.value) { $FoundUsers = $OutputQuery.value } if ($First) { if ($CurrentCount) { $First = $First - $CurrentCount } if ($FoundUsers.Count -eq $First) { return $FoundUsers } elseif ($FoundUsers.Count -gt $First) { $FoundUsers = $FoundUsers | Select-Object -First $First return $FoundUsers } else { $First = $First - $FoundUsers.Count $FoundUsers } } else { $FoundUsers } } function Join-UriQuery { <# .SYNOPSIS Provides ability to join two Url paths together including advanced querying .DESCRIPTION Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url (optional) .PARAMETER QueryParameter Parameters and their values in form of hashtable .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-UrlQuery')] [CmdletBinding()] param ( [parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri, [Parameter()][System.Collections.IDictionary] $QueryParameter ) # Join primary url with additional path if needed if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri } # Create a http name value collection from an empty string if ($QueryParameter) { $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) } } # Build the uri $uriRequest = [System.UriBuilder] $Url if ($Collection) { $uriRequest.Query = $Collection.ToString() } #return $uriRequest.Uri.OriginalUri return $uriRequest.Uri.AbsoluteUri } function Join-Uri { <# .SYNOPSIS Provides ability to join two Url paths together .DESCRIPTION Provides ability to join two Url paths together .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url .EXAMPLE Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-Url')] [cmdletBinding()] param( [parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri ) return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/')) #return [Uri]::new([Uri]::new($BaseUri), $RelativeOrAbsoluteUri).ToString() #return [Uri]::new($BaseUri.OriginalString, $RelativeOrAbsoluteUri).ToString() } function Connect-O365ServiceHealth { [cmdletBinding(DefaultParameterSetName = 'ClearText')] param( [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(Mandatory, ParameterSetName = 'ClearText')][string][alias('ClientID')] $ApplicationID, [parameter(Mandatory, ParameterSetName = 'ClearText')][string][alias('ClientSecret')] $ApplicationKey, [parameter(Mandatory, ParameterSetName = 'Encrypted')][string][alias('ClientSecretEncrypted')] $ApplicationKeyEncrypted, [parameter(Mandatory, ParameterSetName = 'Credential')][PSCredential] $Credential, [parameter(Mandatory, ParameterSetName = 'Encrypted')] [parameter(Mandatory, ParameterSetName = 'ClearText')] [parameter(Mandatory, ParameterSetName = 'Credential')] [string] $TenantDomain ) $connectGraphSplat = @{ ApplicationID = $ApplicationID ApplicationKey = $ApplicationKey ApplicationKeyEncrypted = $ApplicationKeyEncrypted Credential = $Credential TenantDomain = $TenantDomain Resource = 'https://graph.microsoft.com/.default' } Remove-EmptyValue -Hashtable $connectGraphSplat Connect-Graphimo @connectGraphSplat } function ConvertFrom-UTCTime { [CmdLetbinding()] param( [Object] $Time, [switch] $ToLocalTime ) if ($null -eq $Script:TimeZoneBias) { try { $TimeZoneBias = (Get-TimeZone -ErrorAction Stop).BaseUtcOffset.TotalMinutes } catch { Write-Warning "ConvertFrom-UTCTime - couldn't get timezone. Please report on GitHub." $TimeZoneBias = 0 } } else { $TimeZoneBias = $Script:TimeZoneBias } if ($Time -is [DateTime]) { $ConvertedTime = $Time } else { if ($null -eq $Time -or $Time -eq '') { return } else { #Write-Verbose -Message "ConvertFrom-UTCTime - Converting time: $Time" $NewTime = $Time -replace ', at', '' -replace ' by', '' -replace 'UTC', '' -replace ' at', '' $NewTIme = $NewTime -replace 'Monday,', '' -replace 'Tuesday,', '' -replace 'Wednesday,', '' -replace 'Thursday,', '' -replace 'Friday,', '' -replace 'Saturday,', '' -replace 'Sunday,', '' try { [DateTime] $ConvertedTime = [DateTime]::Parse($NewTime) } catch { Write-Warning "ConvertFrom-UTCTime - couldn't convert time $Time (after conversion $NewTime). Exception: $($_.Exception.Message). Skipping conversion..." return $Time } } } if ($ToLocal) { $ConvertedTime.AddMinutes($TimeZoneBias) } else { $ConvertedTime } } function Get-Office365ServiceHealthCurrentStatus { [CmdLetbinding()] param( [System.Collections.IDictionary] $Authorization, [string] $TenantDomain, [switch] $ToLocalTime ) try { $CurrentStatus = Invoke-Graphimo -Uri "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews?`$expand=issues" -Method GET -Headers $Authorization -FullUri } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Get-Office365ServiceHealthCurrentStatus - Error: $ErrorMessage" return } $Output = @{ } $Output.Simple = foreach ($Status in $CurrentStatus) { [PSCustomObject][ordered] @{ ID = $Status.ID Service = $Status.Service ServiceStatus = $Status.Status StatusTime = $Script:Today Incidents = ($Status.issues | Where-Object { $_.IsResolved -eq $false }).id } } $Output.Extended = foreach ($Status in $CurrentStatus) { [PSCustomObject][ordered] @{ ID = $Status.ID Service = $Status.Service ServiceStatus = $Status.Status StatusTime = $Script:Today Incidents = ($Status.issues | Where-Object { $_.IsResolved -eq $false }).id FeaturesAffected = ($Status.issues | Where-Object { $_.IsResolved -eq $false }).feature FeaturesAffectedGroup = ($Status.issues | Where-Object { $_.IsResolved -eq $false }).featureGroup } } return $Output } function Get-Office365ServiceHealthIssues { [CmdLetbinding()] param( [System.Collections.IDictionary] $Authorization, [switch] $ToLocalTime ) try { $AllMessages = Invoke-Graphimo -Uri "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues" -Method GET -Headers $Authorization -FullUri } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Get-Office365ServiceHealthIssues - Error: $ErrorMessage" return } $Output = @{ } $Output.Incidents = foreach ($Message in $AllMessages) { [PSCustomObject] @{ Id = $Message.Id Title = $Message.Title Impact = $Message.impactDescription IsResolved = $Message.IsResolved StartTime = ConvertFrom-UTCTime -Time $Message.startDateTime -ToLocalTime:$ToLocalTime.IsPresent EndTime = ConvertFrom-UTCTime -Time $Message.endDateTime -ToLocalTime:$ToLocalTime.IsPresent # HighImpact = $Message.highImpact Classification = $Message.classification Origin = $Message.origin Service = $Message.service LastUpdatedTime = ConvertFrom-UTCTime -Time $Message.lastModifiedDateTime -ToLocalTime:$ToLocalTime.IsPresent LastUpdatedDays = Convert-TimeToDays -StartTime $Message.lastModifiedDateTime -EndTime $Script:Today Status = $Message.status Feature = $Message.feature FeatureGroup = $Message.featureGroup NotifyInApp = ($Message.details | Where-Object { $_.Name -eq 'NotifyInApp' }).Value -eq $true UpdatesCount = $Message.posts.count } } $Output.IncidentsExtended = foreach ($Message in $AllMessages) { [PSCustomObject] @{ Id = $Message.Id Title = $Message.Title Impact = $Message.impactDescription IsResolved = $Message.IsResolved StartTime = ConvertFrom-UTCTime -Time $Message.startDateTime -ToLocalTime EndTime = ConvertFrom-UTCTime -Time $Message.endDateTime -ToLocalTime # HighImpact = $Message.highImpact Classification = $Message.classification Origin = $Message.origin Service = $Message.service LastUpdatedTime = ConvertFrom-UTCTime -Time $Message.lastModifiedDateTime -ToLocalTime:$ToLocalTime.IsPresent LastUpdatedDays = Convert-TimeToDays -StartTime $Message.lastModifiedDateTime -EndTime $Script:Today Status = $Message.status Feature = $Message.feature FeatureGroup = $Message.featureGroup NotifyInApp = ($Message.details | Where-Object { $_.Name -eq 'NotifyInApp' }).Value -eq $true UpdatesCount = $Message.posts.count Updates = $Message.Posts | ForEach-Object { $Object = [ordered] @{} foreach ($SubMessage in $_.description.content.Split("`n")) { if ($SubMessage -like 'Title: *') { $Object.Title = $SubMessage -replace 'Title: ', '' } elseif ($SubMessage -like 'User Impact: *') { $Object.UserImpact = $SubMessage -replace 'User Impact: ', '' } elseif ($SubMessage -like 'More info: *') { $Object.MoreInfo = $SubMessage -replace 'More info: ', '' } elseif ($SubMessage -like 'Current status: *') { $Object.CurrentStatus = $SubMessage -replace 'Current status: ', '' } elseif ($SubMessage -like 'Scope of impact: *') { $Object.ScopeOfImpact = $SubMessage -replace 'Scope of impact: ', '' } elseif ($SubMessage -like 'Start time: *') { $Time = $SubMessage -replace 'Start time: ', '' $Object.StartTime = ConvertFrom-UTCTime -Time $Time -ToLocalTime:$ToLocalTime } elseif ($SubMessage -like 'Preliminary root cause: *') { $Object.PreliminaryRootCause = $SubMessage -replace 'Preliminary root cause: ', '' } elseif ($SubMessage -like 'Root cause: *') { $Object.RootCause = $SubMessage -replace 'Root cause: ', '' } elseif ($SubMessage -like 'Next update by: *') { $Time = ($SubMessage -replace 'Next update by: ', '').Trim() $Object.NextUpdateBy = ConvertFrom-UTCTime -Time $Time -ToLocalTime:$ToLocalTime } elseif ($SubMessage -like 'Final status: *') { $Object.FinalStatus = ($SubMessage -replace 'Final status: ', '').Trim() } else { $Object.Other = $SubMessage.Trim() } } [PSCustomObject] @{ Id = $Message.Id Service = $Message.service Created = if ($_.createdDateTime) { [datetime]::Parse($_.createdDateTime) } else { $null } Type = $_.postType Title = $Object.Title UserImpact = $Object.UserImpact MoreInfo = $Object.MoreInfo CurrentStatus = $Object.CurrentStatus ScopeOfImpact = $Object.ScopeOfImpact StartTime = $Object.StartTime PreliminaryRootCause = $Object.PreliminaryRootCause RootCause = $Object.RootCause FinalStatus = $Object.FinalStatus NextUpdateBy = $Object.NextUpdateBy Other = $Object.Other } } } } $Output.IncidentsUpdates = foreach ($Message in $Output.IncidentsExtended) { foreach ($Post in $Message.Updates) { $Post } } $Output } function Get-Office365ServiceHealthMessages { [CmdLetbinding()] param( [System.Collections.IDictionary] $Authorization ) try { $AllMessages = Invoke-Graphimo -Uri "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/messages" -Method GET -Headers $Authorization -FullUri } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Get-Office365ServiceHealthMessages - Error: $ErrorMessage" return } $Output = @{ } $Output.MessageCenterInformation = foreach ($Message in $AllMessages) { $ActionRequiredDays = Convert-TimeToDays -StartTime $Message.actionRequiredByDateTime -EndTime $Script:Today [PSCustomObject] @{ Id = $Message.Id Title = $Message.Title Service = $Message.services LastUpdatedTime = if ($Message.lastModifiedDateTime) { [DateTime]::Parse($Message.lastModifiedDateTime) } else { $null } LastUpdatedDays = Convert-TimeToDays -StartTime $Message.lastModifiedDateTime -EndTime $Script:Today ActionRequiredByDateTime = if ( $Message.actionRequiredByDateTime) { [DateTime]::Parse($Message.actionRequiredByDateTime) } else { $null } ActionRequiredDays = if ($ActionRequiredDays -eq 0) { $null } else { - $ActionRequiredDays } Tags = $Message.Tags RoadmapId = ($Message.details | Where-Object { $_.name -eq 'roadmapids' }).Value Category = $Message.category } } $Output.MessageCenterInformationExtended = foreach ($Message in $AllMessages) { $ActionRequiredDays = Convert-TimeToDays -StartTime $Message.actionRequiredByDateTime -EndTime $Script:Today [PSCustomObject] @{ Id = $Message.Id Title = $Message.Title Service = $Message.services LastUpdatedTime = if ($Message.lastModifiedDateTime) { [DateTime]::Parse($Message.lastModifiedDateTime) } else { $null } LastUpdatedDays = Convert-TimeToDays -StartTime $Message.lastModifiedDateTime -EndTime $Script:Today ActionRequiredByDateTime = if ($Message.actionRequiredByDateTime) { [DateTime]::Parse($Message.actionRequiredByDateTime) } else { $null } ActionRequiredDays = if ($ActionRequiredDays -eq 0) { $null } else { - $ActionRequiredDays } Tags = $Message.Tags Bloglink = ($Message.details | Where-Object { $_.name -eq 'bloglink' }).Value RoadmapId = ($Message.details | Where-Object { $_.name -eq 'roadmapids' }).Value RoadmapIdLinks = ($Message.details | Where-Object { $_.name -eq 'roadmapids' }).Value | ForEach-Object { "https://www.microsoft.com/en-us/microsoft-365/roadmap?filters=&searchterms=$_" } Category = $Message.category IsMajorChange = $Message.isMajorChange Severity = $Message.Severity StartTime = If ($Message.startDateTime) { [DateTime]::Parse($Message.startDateTime) } else { $null } EndTime = if ($Message.endDateTime) { [DateTime]::Parse($Message.endDateTime) } else { $null } Message = $Message.body.content } } $Output } function Get-Office365ServiceHealthServices { [CmdLetbinding()] param( [System.Collections.IDictionary] $Authorization, [string] $TenantDomain ) try { $Services = Invoke-Graphimo -Uri "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews" -Method GET -Headers $Authorization -FullUri } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning -Message "Get-Office365ServiceHealthServices - Error: $ErrorMessage" return } $Output = @{ } $Output.Simple = foreach ($Service in $Services) { [PSCustomObject][ordered] @{ ID = $Service.ID Service = $Service.service } } return $Output } function Get-Office365Health { [CmdLetbinding()] param( [string][alias('ClientID')] $ApplicationID, [string][alias('ClientSecret')] $ApplicationKey, [string] $TenantDomain, [PSWinDocumentation.Office365Health[]] $TypesRequired = [PSWinDocumentation.Office365Health]::All, [switch] $ToLocalTime ) $StartTime = Start-TimeLog try { $Script:TimeZoneBias = (Get-TimeZone -ErrorAction Stop).BaseUtcOffset.TotalMinutes } catch { Write-Warning "ConvertFrom-UTCTime - couldn't get timezone. Please report on GitHub." $Script:TimeZoneBias = 0 } $Script:Today = Get-Date if ($null -eq $TypesRequired -or $TypesRequired -contains [PSWinDocumentation.Office365Health]::All) { $TypesRequired = Get-Types -Types ([PSWinDocumentation.Office365Health]) } $Authorization = Connect-O365ServiceHealth -ApplicationID $ApplicationID -ApplicationKey $ApplicationKey -TenantDomain $TenantDomain if ($null -eq $Authorization) { return } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::Services)) { $Services = Get-Office365ServiceHealthServices -Authorization $Authorization -TenantDomain $TenantDomain } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @( [PSWinDocumentation.Office365Health]::CurrentStatus, [PSWinDocumentation.Office365Health]::CurrentStatusExtended )) { $CurrentStatus = Get-Office365ServiceHealthCurrentStatus -Authorization $Authorization -TenantDomain $TenantDomain -ToLocalTime:$ToLocalTime } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @( [PSWinDocumentation.Office365Health]::Incidents, [PSWinDocumentation.Office365Health]::IncidentsExtended [PSWinDocumentation.Office365Health]::IncidentsUpdates )) { $Issues = Get-Office365ServiceHealthIssues -Authorization $Authorization -ToLocalTime:$ToLocalTime } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @( [PSWinDocumentation.Office365Health]::MessageCenterInformation, [PSWinDocumentation.Office365Health]::MessageCenterInformationExtended )) { $Messages = Get-Office365ServiceHealthMessages -Authorization $Authorization } $Output = [ordered] @{} if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::Services)) { $Output.Services = $Services.Simple } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::CurrentStatus)) { $Output.CurrentStatus = $CurrentStatus.Simple } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::CurrentStatusExtended)) { $Output.CurrentStatusExtended = $CurrentStatus.Extended } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::MessageCenterInformation)) { $Output.MessageCenterInformation = $Messages.MessageCenterInformation | Sort-Object -Property LastUpdatedTime -Descending } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::MessageCenterInformationExtended)) { $Output.MessageCenterInformationExtended = $Messages.MessageCenterInformationExtended | Sort-Object -Property LastUpdatedTime -Descending } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::Incidents)) { $Output.Incidents = $Issues.Incidents | Sort-Object -Property LastUpdatedTime -Descending } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::IncidentsExtended)) { $Output.IncidentsExtended = $Issues.IncidentsExtended | Sort-Object -Property LastUpdatedTime -Descending } if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded @([PSWinDocumentation.Office365Health]::IncidentsUpdates)) { $Output.IncidentsUpdates = $Issues.IncidentsUpdates | Sort-Object -Property LastUpdatedTime -Descending } $EndTime = Stop-TimeLog -Time $StartTime -Option OneLiner Write-Verbose "Get-Office365Health - Time to process: $EndTime" return $Output } Add-Type -TypeDefinition @" using System; namespace PSWinDocumentation { [Flags] public enum Office365Health { All, Services, CurrentStatus, CurrentStatusExtended, MessageCenterInformation, MessageCenterInformationExtended, Incidents, IncidentsExtended, IncidentsUpdates } } "@ $ModuleFunctions = @{ } [Array] $FunctionsAll = 'Get-Office365Health' [Array] $AliasesAll = $AliasesToRemove = [System.Collections.Generic.List[string]]::new() $FunctionsToRemove = [System.Collections.Generic.List[string]]::new() foreach ($Module in $ModuleFunctions.Keys) { try { Import-Module -Name $Module -ErrorAction Stop } catch { foreach ($Function in $ModuleFunctions[$Module].Keys) { $FunctionsToRemove.Add($Function) $ModuleFunctions[$Module][$Function] | ForEach-Object { if ($_) { $AliasesToRemove.Add($_) } } } } } $FunctionsToLoad = foreach ($Function in $FunctionsAll) { if ($Function -notin $FunctionsToRemove) { $Function } } $AliasesToLoad = foreach ($Alias in $AliasesAll) { if ($Alias -notin $AliasesToRemove) { $Alias } } Export-ModuleMember -Function @($FunctionsToLoad) -Alias @($AliasesToLoad) # SIG # Begin signature block # MIItrwYJKoZIhvcNAQcCoIItoDCCLZwCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBFpkL/I8/2RXsq # lFamhxvH1VcsRH3nKKT598NrbDej4qCCJrIwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwDCCBKigAwIBAgIQDE1pckuU+jwqSj0p # B4A9WjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDkyMTAwMDAwMFoXDTMzMTEy # MTIzNTk1OVowRjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSQwIgYD # VQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDP7KUmOsap8mu7jcENmtuh6BSFdDMaJqzQHFUeHjZt # vJJVDGH0nQl3PRWWCC9rZKT9BoMW15GSOBwxApb7crGXOlWvM+xhiummKNuQY1y9 # iVPgOi2Mh0KuJqTku3h4uXoW4VbGwLpkU7sqFudQSLuIaQyIxvG+4C99O7HKU41A # gx7ny3JJKB5MgB6FVueF7fJhvKo6B332q27lZt3iXPUv7Y3UTZWEaOOAy2p50dIQ # kUYp6z4m8rSMzUy5Zsi7qlA4DeWMlF0ZWr/1e0BubxaompyVR4aFeT4MXmaMGgok # vpyq0py2909ueMQoP6McD1AGN7oI2TWmtR7aeFgdOej4TJEQln5N4d3CraV++C0b # H+wrRhijGfY59/XBT3EuiQMRoku7mL/6T+R7Nu8GRORV/zbq5Xwx5/PCUsTmFnta # fqUlc9vAapkhLWPlWfVNL5AfJ7fSqxTlOGaHUQhr+1NDOdBk+lbP4PQK5hRtZHi7 # mP2Uw3Mh8y/CLiDXgazT8QfU4b3ZXUtuMZQpi+ZBpGWUwFjl5S4pkKa3YWT62SBs # GFFguqaBDwklU/G/O+mrBw5qBzliGcnWhX8T2Y15z2LF7OF7ucxnEweawXjtxojI # sG4yeccLWYONxu71LHx7jstkifGxxLjnU15fVdJ9GSlZA076XepFcxyEftfO4tQ6 # dwIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYD # VR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI # AYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQW # BBRiit7QYfyPMRTtlwvNPSqUFN9SnDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2 # VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hB # MjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQBVqioa80bz # eFc3MPx140/WhSPx/PmVOZsl5vdyipjDd9Rk/BX7NsJJUSx4iGNVCUY5APxp1Mqb # KfujP8DJAJsTHbCYidx48s18hc1Tna9i4mFmoxQqRYdKmEIrUPwbtZ4IMAn65C3X # CYl5+QnmiM59G7hqopvBU2AJ6KO4ndetHxy47JhB8PYOgPvk/9+dEKfrALpfSo8a # OlK06r8JSRU1NlmaD1TSsht/fl4JrXZUinRtytIFZyt26/+YsiaVOBmIRBTlClmi # a+ciPkQh0j8cwJvtfEiy2JIMkU88ZpSvXQJT657inuTTH4YBZJwAwuladHUNPeF5 # iL8cAZfJGSOA1zZaX5YWsWMMxkZAO85dNdRZPkOaGK7DycvD+5sTX2q1x+DzBcNZ # 3ydiK95ByVO5/zQQZ/YmMph7/lxClIGUgp2sCovGSxVK05iQRWAzgOAj3vgDpPZF # R+XOuANCR+hBNnF3rf2i6Jd0Ti7aHh2MWsgemtXC8MYiqE+bvdgcmlHEL5r2X6cn # l7qWLoVXwGDneFZ/au/ClZpLEQLIgpzJGgV8unG1TnqZbPTontRamMifv427GFxD # 9dAq6OJi7ngE273R+1sKqHB+8JeEeOMIA11HLGOoJTiXAdI/Otrl5fbmm9x+LMz/ # F0xNAKLY1gEOuIvu5uByVYksJxlh9ncBjDCCB18wggVHoAMCAQICEAfCUnQoFKLW # q/4k6hfl3S4wDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoT # DkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENv # ZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMTAeFw0yMzA0MTYwMDAw # MDBaFw0yNjA3MDYyMzU5NTlaMGcxCzAJBgNVBAYTAlBMMRIwEAYDVQQHDAlNaWtv # xYLDs3cxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlzIEVWT1RFQzEhMB8GA1UE # AwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAlJoHlzELSGimkpCr2wLfBhWSdcsDh/EsMZU7rODHMq1plTq0 # QVUUAPAKRfRWnqG8JpGcb5MUExSxypvvJJ8KJhFLJXGvAqkjiNGMBC7+RME1RIdA # vw2nob8aOrZJjTxff0j9Sgt3NJdbzvjO73TVRikCEK4cauxBtInswWTgIrpDXRlV # 0WDi5+O1d6i+T8Bv6LtmpSf74nyA2nfNahW/kJFIdNiaNuEjI1nSg8rXazF4tNt+ # QjeEa1vvII30Sfnyio4DCJm7nHgrIvSL9Wuum1HPWpwHpjm0+JheVP8kAYALgKN/ # o1QfMIlHfO5FEDtMyQhfL6tmK1Ts/DiZjF/IICLBBFGdwmSg9IVXN3Zu3FkgMPPx # TcxjT5QGiMc11/ang9BIGgi0ZCLQN7d3kFviAF8kv/WZ56RVKA70BmyvkOP2z9Im # /fFy30KcVRkbtHAldDYO+wyJERfiMkdT3MFQKvjs1VN7ynqNub/657YlwpgsYluK # B2DtvHkkP3iAHJ4ovt7igzWayNeT+1cQ65FCHOhbYkrzocHNwM2PrxH4r1JBSkas # L0kq+Hwq65JO89kHu9mcJcNhA0VR8stH1FRjvUDLoehN0cJyS/eoqdGpXJoSgARq # CKkltOZ13QlG5F5oTwk0+Z2kA7mdVJAF22T0oSo2z8M3Vz9m/CPZ0PPVUoECAwEA # AaOCAgMwggH/MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1Ud # DgQWBBR68WolWbgyccRJNeWy6DLhSOdt9zAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0l # BAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0cDovL2NybDMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2 # U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFD # QTEuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEWG2h0dHA6 # Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBlAYIKwYBBQUHAQEEgYcwgYQwJAYIKwYB # BQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcwAoZQaHR0 # cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNp # Z25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcnQwCQYDVR0TBAIwADANBgkqhkiG # 9w0BAQsFAAOCAgEAtxHh11D4aXt9Stgy+Nx34eqpLwR8kdUZQ/ZVSJJXEQkedGR8 # 6FrOhAZUxcqIb5KXJVQrkXUFt97Uur7SjzrnKQw7+MLAPus5CWCPHx6Lluk6mtVu # O2Eq3OQDkoSHCffjaTWyjRood3aEpXIqNplCgl+SP2a8yQZEKSdJGIWv6VEk9gmx # Nya6CX9r0FhlIiPidy3YjzR5oTtZfs2kJEsb9HFQxEzH0BmSikVREmehYOtW9HY7 # 0EseddDHW8bSjI70t2bQMrap0B5NYqT/kYPjOZRR60pFJZ6Rmvn957kIcQ2+zfRP # IVFXr8QC06xYn4PM4bJVUR+fw3/wsZTClwu6Kd9PwMkLDkMR1tbjcd7RtQzIIs6c # AWrK8YesGu4mgPi6dO6tSPdni4a2G7cN8QtrzSBnTHTe53e+sjCI3WJwJ+69/MML # WidymA9EE5e+xAfLv+XArN0oWXQ3coOCuzaCZfIhB626raKABzjC4iaYi9ovWJ/J # EDAev0OkTDtyFDy7snAfaOgzYsEB3+ibeaFuz9PZOTccQRJpLMcDW5mbzUOuWZ93 # sVACqhvsd9RIM+SGeFP4z80WpRJRCKUtK4K1YPEfKRDoXfeZhM6eVhEShcl4Xupw # em0mB7/HJSwFdIjJLt9PK6X4zIkJKktyy831CeTh6rSikDTC8c/c9fOVArMxggZT # MIIGTwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j # LjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNB # NDA5NiBTSEEzODQgMjAyMSBDQTECEAfCUnQoFKLWq/4k6hfl3S4wDQYJYIZIAWUD # BAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq # hkiG9w0BCQQxIgQgzsZXMuti4HIyZMoeWc2o3xSmKBAMGOXmoQQsB8Ho4YAwDQYJ # KoZIhvcNAQEBBQAEggIAVOGDIwSq1ll8Nf5jrJr+/sLdjwY7/f6zclq7z3gcq6l2 # ClUaTfF2mrRvcES95uq/LSnqm2H+nyh15m1esc2UMrvpb6Rm/q2BXustu8rq6ezK # bybkc9Yb4wfjIvuaYiQmHUYCVIXckrlcq3LAORPzxn7i8UObuQcS98tmXn+PJL6F # iMjfHSDhkVY5Ga/ZLodMvGXE0C/6HCrfgwTk/NShSrMjpTvO3TN/Tyy4Z3Lh2who # 5kvgo6hqyVn6MIiadsGMEak2HijfU6x6gtR14pF1U/GDZ6Okxdv/6tWuB7VUJZG1 # +OGSSpLeKHK3tXMTbL0vLqEyALanNoJvB5dtTrDIygwCoaHxvL6k/O0g9iFMh9qE # rm7OUkc2f/d+UYp8UPRnNDNCEEo7CFbql/qMH3912N4pQ1X9DGkg+pz56A8QFOCF # ZuEzFtEbgSDrfF1+dKQPzzaQb17jg7WiDzZrltjLNl9JRTRTB0HGQ5PrTpg5WzRi # NbrCRBO4Ucih2LSmaU+5BoHDySFHXkYXUPhnzfJCGIZ7YC40jFbwPUaxE2QI+5Fb # fT4rJEqTbrdBArvA3GiuCNwNyVRqHGe2wFw90e0Fz6A7mEiOfIjFTnpszfoAcn8c # uml6e3NCmvdIejGlIoQgQo4/GJ6twnpeFGNhFcyna8qQnRz/mgkioc6sMDUTujKh # ggMgMIIDHAYJKoZIhvcNAQkGMYIDDTCCAwkCAQEwdzBjMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0 # ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBAhAMTWlyS5T6PCpK # PSkHgD1aMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEH # ATAcBgkqhkiG9w0BCQUxDxcNMjMwNzA2MDgxMzQ1WjAvBgkqhkiG9w0BCQQxIgQg # gXzagF/i/wh8zLujF3QoCwi+MBN0ji/3pbRLPN4VBqowDQYJKoZIhvcNAQEBBQAE # ggIAUEAO22DY8gh0uzCPx627UVUulnmNDKsFv0QVvn6Ef+bRtQ5yvqhcOu8ZYBh0 # Aw+et+S2pHK6RE10/B/1gmhQEuePl7EO7rVgEz+0daNR0Tmu0St9GJVwnuJm//0g # CHR9UF4p/6KGdC6UXHfpv/GEwhEGaFj2MkTXedjIUR8CqRZQaQ/i+QKmdbmG0TUR # cV3oPZJR7zRJFoBg8ogx7xeVbE/VUqM6mxPaaHKJrXHQTYlo0rsM4qkqoQm8vBjo # IxtR1yTXsYfisXeHWRIG0ODyIpSGiH4ORDiwPTf8H0lg0aFB6EtdUwa2yEoiN89l # Qk9KKGsAY2pw+ddXGNSuGQ+Wq44R3TVwHfQ1jBj9sgySHGULinmAQWKAzV6rWrzg # j4FKTsDMaSMorJ38id5eFGI7ssCdwzAQ2miLNtzCgorSVVR2rKUUYutRVriCGOYg # 3Jpb77Uwwzc2jEFQtAjLQz/E0tPGQW0HGXoP/RqY7kl02O08/pHGQz/3S48f1OlC # O2gOl5VGT32Iz1+N9RroL2uWsgJUjK13vl+Jbq9FQI6JFpFXPSHlo5/32xUmi29I # PdYxzIUivJ1ddOpbLDl04CDxeRSE/VfZxl94Wrxa/HT8PT82rOK6ESZyU9LZVxU4 # /aAVE1nO29FDSWwnlc+goisgEjgUDSasiEWhQ81+WKhyj+g= # SIG # End signature block |