Obs/bin/ObsAgent/lib/Artifacts/StampTools.psm1
<#############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> <# .SYNOPSIS Evaluates the result of Test-AzureStack .DESCRIPTION Runs Test-AzureStack on the provided endpoint using the current session's credentials .PARAMETER EndpointName The name of the JEA endpoint to access .PARAMETER ComputerName The name of the computer on which to invoke the endpoint .OUTPUTS $True if Test-AzureStack was successful, $False otherwise #> function Get-TestAzureStackResult{ Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $EndpointName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $ComputerName ) $testAzureStackResult = $false Import-Module $PSScriptRoot\SessionTools.psm1 try { $jeaSession = Get-JEASession -EndpointName $EndpointName -ComputerName $ComputerName # Run Test Azure Stack to generate MAS logs Write-Information "Invoking Test-AzureStack on $ComputerName using session $($jeaSession.InstanceId)" $testAzureStackResult = Invoke-Command -Session $jeaSession -ScriptBlock { Test-AzureStack -Ignore AzsStampBMCSummary,AzsHostingInfraFWSummary,AzsGpuConfigurationSummary } Write-Information "Test-AzureStack completed" } catch { Write-Error "Test-AzureStack failed with $_" -Verbose } finally { if($jeaSession -ne $null) { Write-Information "Removing JEA session $($jeaSession.InstanceId)" Remove-PSSession $jeaSession } } return $testAzureStackResult } <# .SYNOPSIS Executes Get-AzureStackLog .DESCRIPTION Runs Get-AzureStackLog on the provided endpoint using the current session's credentials .PARAMETER EndpointName The name of the JEA endpoint to access .PARAMETER ComputerName The name of the computer on which to invoke the endpoint .PARAMETER ErcsNames The comma seperated names of ERCS computer .PARAMETER OutputPath The output path for the logs .PARAMETER FromDate The starting window from which to start collecting logs .PARAMETER ToDate The ending time window for collecting logs .PARAMETER $BlobServiceSasUri Blob Service URI for Azure Storage .PARAMETER $OutputSharePath Local File Share path .PARAMETER $OutputSharePathUserName Local File Share path user name .PARAMETER $OutputSharePathPassword Local File Share path User Password .PARAMETER FilterRoles The roles to filter on .PARAMETER FilterNodes The nodes to filter on .PARAMETER FilterLogTypes The log types to collect .PARAMETER FilterResourceProviders The resource providers to filter on .PARAMETER AlertBased Whether the log collection is alert based .OUTPUTS A result object containing the output path of the logs #> function Get-AzureStackLogResult { Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $EndpointName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $ComputerName, [Parameter(Mandatory=$false)] [string] $ErcsNames, [Parameter(Mandatory=$false)] [nullable[DateTime]] $FromDate, [Parameter(Mandatory=$false)] [nullable[DateTime]] $ToDate, [Parameter(Mandatory=$false)] [string] $OutputPath, [Parameter(Mandatory=$false)] [string] $BlobServiceSasUri, [Parameter(Mandatory=$false)] [string] $OutputSharePath, [Parameter(Mandatory=$false)] [string] $OutputSharePathUserName, [Parameter(Mandatory=$false)] [string] $OutputSharePathPassword, [Parameter(Mandatory=$false)] [string[]] $FilterRoles, [Parameter(Mandatory=$false)] [string[]] $FilterNodes, [Parameter(Mandatory=$false)] [string[]] $FilterLogTypes, [Parameter(Mandatory=$false)] [string[]] $FilterResourceProviders, [Parameter(Mandatory=$false)] [bool] $AlertBased ) $getAzureStackLogResult = $false; $uploadErrors = @() $uploadSizeInMb = 0 $uploadNumberOfFiles = 0 $uploadDirectory = $null Import-Module $PSScriptRoot\SessionTools.psm1 $customerConfigPath = "$env:SystemDrive\ProgramData\Microsoft\AzureStack\config.xml" try { # Default value for log collection window start, if not specified, should be 4 hours ago if ($FromDate -eq $null) { $FromDate = (Get-Date).AddHours(-4) Write-Information "FromDate parameter not specified. Setting to default value $FromDate" } # Default value for log collection window end, if not specified, should be now if ($ToDate -eq $null) { $ToDate = Get-Date Write-Information "ToDate parameter not specified. Setting to default value $ToDate" } $getAzureStackLogParameters = @{ FromDate = $FromDate ToDate = $ToDate FilterByRole = $FilterRoles FilterByNode = $FilterNodes FilterByLogType = $FilterLogTypes FilterByResourceProvider = $FilterResourceProviders AlertBased = $AlertBased } # Provide the customer configuration file to Get-AzureStackLog if it exists Write-Information "Checking if config.xml exists: $customerConfigPath" if (Test-Path $customerConfigPath) { Write-Information "$customerConfigPath exists. Adding to Get-AzureStackLog parameters" $getAzureStackLogParameters.Add("CustomerConfigurationFilePath", $customerConfigPath) } else { # Considering this a non-terminating error Write-Error "$customerConfigPath does not exist. Continuing without a configuration file" } if (![string]::IsNullOrEmpty($BlobServiceSasUri)) { Write-Information "Using Blob Service Uri for upload $BlobServiceSasUri" $getAzureStackLogParameters.Add("OutputSasUri", $BlobServiceSasUri) $jeaSession = Get-JEASession -EndpointName $EndpointName -ComputerName $ComputerName } elseif (![string]::IsNullOrEmpty($OutputSharePath)) { Write-Information "Using Share Path for upload $OutputSharePath" $getAzureStackLogParameters.Add("OutputSharePath", $OutputSharePath) Write-Information "Adding share creds" $password = ConvertTo-SecureString $OutputSharePathPassword -AsPlainText -Force $cred = New-Object -TypeName System.Management.Automation.PSCredential ($OutputSharePathUserName, $password) $getAzureStackLogParameters.Add("OutputShareCredential", $cred) Write-Information "Checking share access locally first" $checkStatus = Test-SharePathAccess -SharePath $OutputSharePath -ShareCreds $cred Write-Information "Checking share access locally returned: $checkStatus" if(!$checkStatus) { Write-Information "Getting PEP session from ERCS: $($ErcsNames)" $jeaSession = Get-EcePepSession -ErcsComputerNames $ErcsNames } else { Write-Information "Getting PEP session from SRNG: $ComputerName" $jeaSession = Get-JEASession -EndpointName $EndpointName -ComputerName $ComputerName } } else { Write-Information "Using Autonomous Log Archival Location for upload $OutputPath" $jeaSession = Get-JEASession -EndpointName $EndpointName -ComputerName $ComputerName $getAzureStackLogParameters.Add("OutputPath", $OutputPath) } $getAzureStackLogCommand = { Get-AzureStackLog 6>&1 @using:getAzureStackLogParameters } Write-Information "Invoking Get-AzureStackLog using session $($jeaSession.InstanceId)" $azureStackLog = Invoke-Command -Session $jeaSession -ScriptBlock $getAzureStackLogCommand Write-Information "Completed Get-AzureStackLog" Write-Information "Collecting diagnostic data from Get-AzureStackLog" if($azureStackLog -ne $null) { $uploadDetails = Get-AzureStackLogDetails $azureStackLog $uploadErrors = $uploadDetails.UploadErrorDetails $uploadSizeInMb = $uploadDetails.UploadSize $uploadNumberOfFiles = $uploadDetails.UploadNumberOfFiles $uploadDirectory = $uploadDetails.UploadDirectory } else { Write-Error "Get-AzureStackLog failed to produce an output stream. Diagnostic data will not be collected." } } catch { Write-Error "Get-AzureStackLog failed on $ComputerName with $_" } finally { if($jeaSession -ne $null) { Write-Information "Removing JEA session $($jeaSession.InstanceId)" Remove-PSSession $jeaSession } } # If we have successfully uploaded files to the storage account consider this run a succes if (($uploadSizeInMb -gt 0) -and ($uploadNumberOfFiles -gt 0)) { Write-Information "Get-AzureStackLog successfully uploaded logs to $uploadDirectory" $getAzureStackLogResult = $true } # TODO: remove output path key return ConvertTo-Json @{ LogResult = $getAzureStackLogResult OutputPath = $uploadDirectory UploadDirectory = $uploadDirectory UploadSizeInMb = $uploadSizeInMb UploadNumberOfFiles = $uploadNumberOfFiles UploadErrors = $uploadErrors } } # Extracts a valid folder name from an InformationRecord function Find-FolderNameMatch { Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject] $LogDetail, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $LogDetailsLabel ) Write-Information "Finding match for $LogDetailsLabel" $folderMatch = "[a-zA-Z0-9](?:[a-zA-Z0-9_ -]*[a-zA-Z0-9]){1}$" $folderName = ($LogDetail | Select-String -pattern $folderMatch).Matches[0].Value if($folderName -eq $null) { Write-Error "Unable to find a valid folder name" } return $folderName } # Extracts the latest numerical match from an InformationRecord function Find-LatestNumericalMatch { Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject[]] $LogDetails, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $LogDetailsLabel ) $measureMatch = "\d+(\.\d+)?$" $maximum = 0 Write-Information "Finding the latest match for $LogDetailsLabel" $measureMatches = $LogDetails ` | Select-String -pattern $measureMatch ` | ForEach {$_.Matches } ` | ForEach {[decimal]$_.Value} #report the maximum in case multiple log messages exist $latestDetail = $measureMatches | Measure-Object -Maximum if ($latestDetail -ne $null) { $maximum = $latestDetail.Maximum } else { Write-Error "Unable to find a maximum for $LogDetailsLabel" } return $maximum } # Retrieves diagnostic data from a Get-AzureStackLog output stream function Get-AzureStackLogDetails{ Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSObject[]] $azureStackLog ) # Get-AzureStackLog output tags $sizeTag = "LogCollectionUploadFileSizeInMb" $numberOfFilesTag = "LogCollectionUploadNumberOfFiles" $errorDetailsTag = "LogCollectionUploadErrorDetails" $directoryTag = "LogCollectionUploadDirectory" # Data capture matches $errorDetailsMatch = "$errorDetailsTag.*$" $sizeMatch = "FileSizeInMb.*"; $numberOfFilesMatch = "NumberOfFiles.*" $directoryMatch = "$directoryTag.*" $logCollectionMatch = "($sizeTag|$numberOfFilesTag|$errorDetailsTag|$directoryTag).*" # Default values $errorDetails = @() $uploadSize = 0 $uploadNumberOfFiles = 0 $uploadDirectory = $null try { # Search the output stream for the output tags $uploadDetails = $azureStackLog | Where-Object { $_.MessageData -match $logCollectionMatch } if ($uploadDetails -ne $null) { # Find all upload error details if present $errorDetails += $uploadDetails ` | Select-String -pattern $errorDetailsMatch ` | ForEach { $_.Matches } ` | ForEach { $_.Value } # Find the latest record for upload size $uploadSizeMatches = $uploadDetails | Where-Object { $_.MessageData -match $sizeMatch } if ($uploadSizeMatches -ne $null) { $uploadSize = [int](Find-LatestNumericalMatch $uploadSizeMatches $sizeTag) Write-Information "${sizeTag}: $uploadSize" } else { Write-Error "$sizeTag details were not present" } # Find the latest record for the number of files uploaded $uploadNumberOfFilesMatches = $uploadDetails | Where-Object { $_.MessageData -match $numberOfFilesMatch } if ($uploadNumberOfFilesMatches -ne $null) { $uploadNumberOfFiles = Find-LatestNumericalMatch $uploadNumberOfFilesMatches $numberOfFilesTag Write-Information "${numberOfFilesTag}: $uploadNumberOfFiles" } else { Write-Error "$numberOfFilesTag details were not present" } # Find the upload directory $uploadDirectoryMatches = $uploadDetails | Where-Object { $_.MessageData -match $directoryMatch } if ($uploadDirectoryMatches -ne $null) { $uploadDirectory = Find-FolderNameMatch $uploadDirectoryMatches[0] $directoryTag Write-Information "${directoryTag}: $uploadDirectory" } else{ Write-Error "$uploadDirectoryTag details were not present" } } else { Write-Error "Log collection upload details were not present in the output provided." } } catch { Write-Error "An error occured while attempting to collect log collection upload details: $_" } return @{ UploadErrorDetails = $errorDetails UploadSize = $uploadSize UploadNumberOfFiles = $uploadNumberOfFiles UploadDirectory = $uploadDirectory } } # Checks if share is reachable from given node function Test-SharePathAccess { Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $SharePath, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [PSCredential] $ShareCreds ) Write-Information "Test-ShareAccessFromNode: Testing SharePath $SharePath access locally" $status = $true try { $drive = Get-PSDrive | Where-Object DisplayRoot -eq $SharePath if ($null -eq $drive) { Write-Information "Creating new PS drive: $SharePath." $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ShareCreds.Password) $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) $null = net use * $SharePath /user:$($ShareCreds.UserName) $password $drive = Get-PSDrive | Where-Object DisplayRoot -eq $SharePath if(!$drive) { throw "net use is not successful" } } Write-Information "Net Use did not return null. So considering success: $($netuseResult)" } catch { Write-Information "Failed to access the share. $_" $status = $false } finally { Write-Information "Test-ShareAccessFromNode : Cleanup mapped drive" if($drive) { $driveName = $drive.Root -replace "\\","" Write-Information "Test-ShareAccessFromNode : Cleaning mapped drive: $driveName" $null = net use $driveName /delete /yes Write-Information "Test-ShareAccessFromNode : Successfully delete mapped drive" } } $status } # Available commands Export-ModuleMember -Function Get-TestAzureStackResult Export-ModuleMember -Function Get-AzureStackLogResult # SIG # Begin signature block # MIIoKQYJKoZIhvcNAQcCoIIoGjCCKBYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDYaQv7Gh3Wj9OQ # byFBNOmQggQWNZy+m8z6kLS0crndTqCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGgkwghoFAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIE8JzdgMuK4CjUrE6OmRbjx1 # rgJDx4jk6VBAspXDkkj3MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAZv2k0/YVehRhcd2EbJCwI3bqyU3ZJ1gkdWtGW9onUs71Tnze682D1X54 # +wHrpOhKNu3O/CzA2mYLqtDDiobGFC6kNI2XvCcqauVNoku69Z7A9FiQ4ynv47gD # ewJiuwrst52NBRdnrSIJGyjr9FqyzdawLYKEQXbJ7b2ncUgJmg4byxnW4sHVA4BK # On45fKmfnlm249G/UfgM9TWpxGS2VvyFSDgRx949IEOQBMEsEj802VwYxEutj1c+ # /YBwCTCu0U0AsaFUmyDyEaH2yRCRiqA+jSQ3N1i31py8ASUaXSsAr5SAd+ZL40mR # 5oh8Z6P6vryPaqx5pWYlGNMmy6wPaKGCF5MwghePBgorBgEEAYI3AwMBMYIXfzCC # F3sGCSqGSIb3DQEHAqCCF2wwghdoAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCUVGkefQ8BUaIczKiAnvQSAgTAm2GcrQ2588mTEVbuMQIGZkYZ0Mux # GBMyMDI0MDUxNjE4NDQyNS45MjlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHpMIIHIDCCBQigAwIBAgITMwAAAehQsIDPK3KZTQABAAAB6DANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MjJaFw0yNTAzMDUxODQ1MjJaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDhQXdE0WzXG7wzeC9SGdH6eVwdGlF6YgpU7weOFBkp # W9yuEmJSDE1ADBx/0DTuRBaplSD8CR1QqyQmxRDD/CdvDyeZFAcZ6l2+nlMssmZy # C8TPt1GTWAUt3GXUU6g0F0tIrFNLgofCjOvm3G0j482VutKS4wZT6bNVnBVsChr2 # AjmVbGDN/6Qs/EqakL5cwpGel1te7UO13dUwaPjOy0Wi1qYNmR8i7T1luj2JdFdf # ZhMPyqyq/NDnZuONSbj8FM5xKBoar12ragC8/1CXaL1OMXBwGaRoJTYtksi9njuq # 4wDkcAwitCZ5BtQ2NqPZ0lLiQB7O10Bm9zpHWn9x1/HmdAn4koMWKUDwH5sd/zDu # 4vi887FWxm54kkWNvk8FeQ7ZZ0Q5gqGKW4g6revV2IdAxBobWdorqwvzqL70Wdsg # DU/P5c0L8vYIskUJZedCGHM2hHIsNRyw9EFoSolDM+yCedkz69787s8nIp55icLf # DoKw5hak5G6MWF6d71tcNzV9+v9RQKMa6Uwfyquredd5sqXWCXv++hek4A15WybI # c6ufT0ilazKYZvDvoaswgjP0SeLW7mvmcw0FELzF1/uWaXElLHOXIlieKF2i/YzQ # 6U50K9dbhnMaDcJSsG0hXLRTy/LQbsOD0hw7FuK0nmzotSx/5fo9g7fCzoFjk3tD # EwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFPo5W8o980kMfRVQba6T34HwelLaMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCWfcJm2rwXtPi74km6PKAkni9+BWotq+Qt # DGgeT5F3ro7PsIUNKRkUytuGqI8thL3Jcrb03x6DOppYJEA+pb6o2qPjFddO1TLq # vSXrYm+OgCLL+7+3FmRmfkRu8rHvprab0O19wDbukgO8I5Oi1RegMJl8t5k/UtE0 # Wb3zAlOHnCjLGSzP/Do3ptwhXokk02IvD7SZEBbPboGbtw4LCHsT2pFakpGOBh+I # SUMXBf835CuVNfddwxmyGvNSzyEyEk5h1Vh7tpwP7z7rJ+HsiP4sdqBjj6Avopuf # 4rxUAfrEbV6aj8twFs7WVHNiIgrHNna/55kyrAG9Yt19CPvkUwxYK0uZvPl2WC39 # nfc0jOTjivC7s/IUozE4tfy3JNkyQ1cNtvZftiX3j5Dt+eLOeuGDjvhJvYMIEkpk # V68XLNH7+ZBfYa+PmfRYaoFFHCJKEoRSZ3PbDJPBiEhZ9yuxMddoMMQ19Tkyftot # 6Ez0XhSmwjYBq39DvBFWhlyDGBhrU3GteDWiVd9YGSB2WnxuFMy5fbAK6o8PWz8Q # RMiptXHK3HDBr2wWWEcrrgcTuHZIJTqepNoYlx9VRFvj/vCXaAFcmkW1nk7VE+ow # aXr5RJjryDq9ubkyDq1mdrF/geaRALXcNZbfNXIkhXzXA6a8CiamcQW/DgmLJpiV # QNriZYCHIDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNM # MIICNAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkRDMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCM # JG4vg0juMOVn2BuKACUvP80FuqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6fCYTzAiGA8yMDI0MDUxNjE0MzU1 # OVoYDzIwMjQwNTE3MTQzNTU5WjBzMDkGCisGAQQBhFkKBAExKzApMAoCBQDp8JhP # AgEAMAYCAQACASAwBwIBAAICElcwCgIFAOnx6c8CAQAwNgYKKwYBBAGEWQoEAjEo # MCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG # 9w0BAQsFAAOCAQEAjk+W0YnLhKp0SORNHGi4JzUkO1Yu9ka8hF0KsneXGY/w7cVX # S7HieMN6zuxvjYkbafq4NuTYjnvv/HBW973d8eDf263+FvdNnLz8xu2aV5alXEyQ # JBQUDYlMpBkSVUJ+RQle+NnUbJGDdNv/MyVMWMnYw1UdkslSZz11w9uAyTuy116B # 0hAaV1nsg9IbBxI6e0aTaXzvupqytlF0OhEuIAPZtQsDUzP8i6y3G7IC9N29AZrD # 42CxAI7zgtKauOjrfyPDYo+D+KXip+I1MMbYGQeJ9J3ZU7xTJTJeVKTfIKuWb+sH # OrQADlVGd+SZqhcJ5LXJ/ubTEsyEFoPoODPYdzGCBA0wggQJAgEBMIGTMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB6FCwgM8rcplNAAEAAAHoMA0G # CWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJ # KoZIhvcNAQkEMSIEILoaCJFLl4k5K/HV+GHENfeuwgN6yc/9LBp0WgK4AF6SMIH6 # BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgKtLaxNUChCCCQdHn2k2qKB7TF8lP # YndTxbVJzwf46x0wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MAITMwAAAehQsIDPK3KZTQABAAAB6DAiBCCn0IYn/ySGCFfhzdyA0xzr4MGrVGRb # JrdweyhslNIENjANBgkqhkiG9w0BAQsFAASCAgC5rSbpxBQHCwQLlwiFlt35fWRs # jMJOucymXVVnklMlSSgOBLXq5XUVpDyfeQZEUMKOg+GTe69s4aeQDy7mXBhI6rLM # x/4zBjF9UO8yqzbAXiUadd+wDeVZmGnaHN5sVRc1I0JmzBUxOV1BaiI6NtuvxDfb # c2N29QrhD3hB86TDbeSnbw4UX2X7pzowg1t/IACEAu3WCMPked1asM2mHhAYAQ1M # Do6YT/A9fjPpw8ymW7hPCoe+SpXibFK2Ca9Sm5FqsX+IXoaVTE0DK8KwJ3St9SMz # MlO3Wns++eo5PN/f4URGo0UvXlF7Xg1OK8KKD07TawQM+6T30Md8c/lPzibfaEbN # 3n68F+1QoC4SqNby6WQtY8v6v1UIYD9S4Y8w5FnHdzp3bnlDQ+rLG9roF+UG89/5 # ZGzXNL8PrBre2PZEH13AReufNQSpkzrBniLjwoEKEgpf+LmFqHLbqcVtud7tABqk # Ocdzj3LKL9XOfRFK/0OLrqCSuTJSHAJBXxxUYW7/FNNg1zLipAXSuV056elDdryP # DE1d4RHYCeN85GgW3kp0ahHTBG4VtZhpU/wVwQ8PhL2Aq945dsJCda8tUJJw9iys # +Mdbrtu3UlJVcl3kbT4EnoiGnbTdqUc13/uoOa3+wqiY3Q8O8CKR6eKY1eJW+62I # PvbRhnAM+OkdOJ8YkQ== # SIG # End signature block |