modules/AzStack.Core/AzStack.Core.psm1
<################################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # ################################################################> Import-LocalizedData -BindingVariable 'msg' -BaseDirectory "$PSScriptRoot\locale" -UICulture (Get-Culture) Import-Module $PSScriptRoot\..\AzStack.Utilities\AzStack.Utilities.psm1 -WarningAction SilentlyContinue <# ┌──────────────────────────────────────────────────────────────────────────┐ │ Async command dispatch and receive functionality. │ └──────────────────────────────────────────────────────────────────────────┘ #> $script:diagnosticChecks = 0 # counts how many jobs we have currently going on. function Register-DiagnosticCheck() { Trace-Output -Level:Verbose -Message ("[CORE] Registering Diagnostic check: " + $script:diagnosticChecks + " / " + ($script:diagnosticChecks + 1)) $script:diagnosticChecks = $script:diagnosticChecks + 1 } function Unregister-DiagnosticCheck() { Trace-Output -Level:Verbose -Message ("[CORE] Unregistering Diagnostic check: " + $script:diagnosticChecks + " / " + ($script:diagnosticChecks - 1)) $script:diagnosticChecks -= 1 } function Reconcile-DiagnosticCheck() { param( [array] $jobs, [array] $buffer = @() # !!! DO NOT USE - THIS IS RECURSIVE ONLY !!! ) <# .SYNOPSIS Receive a diagnostic check. .DESCRIPTION Receive a diagnostic check. Will wait for the diagnostic job to finish (if it is not yet done) and surface the data gathered by the script. .PARAMETER jobs array of jobs you got from Start-DiagnosticCheck .PARAMETER buffer !!! DO NOT USE - THIS IS RECURSIVE ONLY !!! .INPUTS None. You can't pipe objects to Add-Extension. .OUTPUTS System.Hashtable. Use: $_.name for the node name of the given dataset. Use: $_.output to access the dataset. #> Trace-Output -Level:Verbose -Message "[CORE] [RECONCILE-DIAG CHECK] ENTER" if($diagnosticChecks -le 0) { Trace-Output -Level:Verbose -Message "[CORE] [RECONCILE-DIAG CHECK] RETURN BUFFER" Trace-Output -Level:Information -Message $msg.CollectionSucess return $buffer } if($jobs.count -ge 1) { # we loop through the array with job id's foreach($jobId in $jobs) { try { if($null -ne $jobId) { $job = Get-Job -Id $jobId if($job.JobStateInfo.state -ne 'Running') { Trace-Output -Level:Verbose -Message "[CORE] [RECONCILE-DIAG CHECK] FOUND JOB IN COMPLETED STATE" # we should receive the job $data = Receive-Job -Job $job $buffer += $data # remove job from array Unregister-DiagnosticCheck $index = $jobs.IndexOf($jobId) $jobs.SetValue($null, $index) } else { # call recursive. Trace-Output -Level:Verbose -Message "[CORE] [RECONCILE-DIAG CHECK] FOUND JOB IN IN-COMPLETE STATE. START SLEEP" Start-Sleep -Milliseconds 500 Trace-Output -Level:Verbose -Message "[CORE] [RECONCILE-DIAG CHECK] RETURN RETRY" } } } catch {} } } else { # invalid state return -1 } Trace-Output -Level:Verbose -Message "[CORE] [RECONCILE-DIAG CHECK] EXIT" return Reconcile-DiagnosticCheck -jobs $jobs -buffer $buffer } function Start-DiagnosticCheck() { param( [scriptblock] $DiagCheck, [array] $ComputerNames, [array] $ArgumentList ) <# .SYNOPSIS Starts an Diagnostic check. .DESCRIPTION Starts an async data collection. It will run the powershell script defined on all computer objects passed into it. .PARAMETER DiagCheck script you want to run on each computer .PARAMETER ComputerNames Array of computer objects .PARAMETER ArgumentList Any arguments you require inside your script. NOTE: We are not allowing "using" .INPUTS None. You can't pipe objects to Add-Extension. .OUTPUTS System.PsJob. Use this job to later recive data. #> $script:diagnosticChecks = 0 Trace-Output -Level:Information -Message "We are preparing to collect diagnostic information from your environment" $ids = @() foreach($ComputerName in $ComputerNames) { $diagnosticJob = Invoke-Command -ScriptBlock { param( $check, $arguments ) $result = [ScriptBlock]::Create($check).Invoke($arguments) return @{ "name"=$env:COMPUTERNAME "output"=$result } } -ArgumentList @($DiagCheck,$ArgumentList) -ComputerName $ComputerName -AsJob Trace-Output -Level:Verbose -Message ("[CORE] [START-DIAGNOSTICCHECK] JOB ID: " + $diagnosticJob.id) Register-DiagnosticCheck $ids += $diagnosticJob.id } Trace-Output -Level:Verbose -Message ("[CORE] [START-DIAGNOSTICCHECK] Returning: " + $ids) Trace-Output -Level:Information -Message $msg.CollectionStarted return $ids } <# ┌──────────────────────────────────────────────────────────────────────────┐ │ Diagnostic check basiscs. that will include runtime creation etc. │ └──────────────────────────────────────────────────────────────────────────┘ #> function Register-CommandRuntime() { $guid = [System.Guid]::NewGuid() Trace-Output -Level:Verbose -Message ("[CORE] [Register-CommandRuntime] Issuing new GUID for runtime: " + $guid) return $guid } function Unregister-CommandRuntime() { param( [string]$runtime ) Trace-Output -Level:Verbose -Message ("[CORE] [Unregister-CommandRuntime] Unregistering runtime for GUID: " + $runtime) return $true } function Collect-SupportData() { param ( [string] $runtime, # Guid of runtime [array] $ClusterCommands, # Array of commands to run on the cluster [array] $NodeCommands, # Array of commands to run on the nodes [array] $NodeEvents, # Array of events to collect on the nodes [array] $NodeRegistry, # Array of registry keys to collect on the nodes [array] $NodeFolders, # Array of folders to collect on the nodes [array] $ComputerName, # Array [string] $customName ) <# .SYNOPSIS Starts an async data collection .DESCRIPTION Starts an async data collection. It will collect local data from the nodes and also collect data from a cluster level. .PARAMETER runtime runtime GUID. .PARAMETER ClusterCommands Array of commands to be run on the cluster context. .PARAMETER NodeCommands Array for commands to be run on each node. .PARAMETER NodeEvents Array of eventlogs to be collected on each node. .PARAMETER NodeFolders Array of filepaths to be collected recursive on each node. .PARAMETER ComputerName Array of computer objects to run the collection on .PARAMETER customName String of a custom prefix. Should be used for defining what this log collection is for. .INPUTS None. You can't pipe objects to Add-Extension. .OUTPUTS None. There will be a ZIP file generated inside the command runtime temp folder. #> # START INIT $prefix = "[UTITLITY] [Collect-SupportData] " Trace-Output -Level:Information -Message $msg.LocalContainerCreation $storageContainer = ("{0}\temp\" -f (Get-WorkingDirectory)) if((Test-path $storageContainer) -ne $true) { New-Item -Type Directory -Path $storageContainer | Out-Null } # END INIT # START CLUSTER COMMANDS Trace-Output -Level:Information -Message $msg.GatherClusterData foreach($command in $ClusterCommands) { try { $output = Invoke-Expression -Command $command } catch { $output = $_ } finally { # export text $output | Out-File -FilePath ($storageContainer + "\" + $command + ".txt") -Width 9999 -Encoding ascii -Confirm:$false $output | Export-Clixml -Path ($storageContainer + "\" + $command + ".xml") -confirm:$false } } Trace-Output -Level:Information -Message $msg.ColectionComplete # END CLUSTER COMMANDS # START NODE COMMANDS $job = Start-DiagnosticCheck -ComputerNames $ComputerName -DiagCheck { param( $arguments ) # Write-Debug ("runtime " + $arguments[0]) # Write-Debug ("cmdlets " + $arguments[1]) # Write-Debug ("events" + $arguments[2]) # Write-Debug ("registry " + $arguments[3]) # Write-Debug ("folders " + $arguments[4]) # Write-Debug ("computername " + $arguments[5]) # Write-Debug ("storagecontainer " + $arguments[6]) # assign variables to more human readable formats. $runtime = $arguments[0] $nodeCommands = $arguments[1] $nodeEvents = $arguments[2] $nodeRegistry = $arguments[3] $nodeFolders = $arguments[4] $remoteComputerName = $arguments[5] $storageContainer = $arguments[6] function Export-Commands() { param ( $nodeCommands, $storageContainer ) foreach($command in $nodeCommands) { # when we run a command that outputs a file, we should be able to capture it in our data collection. if($command.IndexOf("STORAGE_DEST") -ne -1) { $command = $command.replace("STORAGE_DEST",$storageContainer) } try { $output = Invoke-Expression -Command $command } catch { $output = $_ } finally { # export text switch ($command) { "Get-ChildItem env:*" { $commandName = "Get-EnvironmentVariables" } Default { $commandName = $command.split(" ")[0] # Ensure that we only select the first part of the cmdlet i.e. Get-SomeCommand | fl* -> Get-SomeCommand } } $output | Out-File -FilePath ($storageContainer + "\" + $commandName + ".txt") -Width 9999 -Encoding ascii -Confirm:$false $output | Export-Clixml -Path ($storageContainer + "\" + $commandName + ".xml") -confirm:$false } } return $output } function Export-Events() { param ( $nodeEvents = @(), $storageContainer, $evtxexport = $false ) #$nodeEvents += @("System","Application") #commented out as if we want to expose this funtion for manual usage system and applog could take too much time foreach($eventLog in $nodeEvents) { try { $output = Get-WinEvent -LogName $eventLog -Oldest -ErrorAction SilentlyContinue -WarningAction SilentlyContinue } catch { $output = $_ } finally { $eventlogName = $eventLog.replace("/","_").replace("\","_") # Ensure that the name will be valid i.e. Microsoft-AzureStack-HCI/Admin -> Microsoft-AzureStack-HCI_Admin $output | Select-Object TimeCreated, Id, LevelDisplayName,Message | Export-Csv -Path ($storageContainer + "\" + $eventlogName + ".csv") if ($evtxexport -ne $false) { (Get-WmiObject -Class Win32_NTEventlogFile | Where-Object LogfileName -EQ $eventlog).BackupEventlog($storagecontainer+"\"+$eventlogName+".evtx") } } } return $null #$output, also takes too much time } function Export-Registry() { param ( $nodeRegistry, $storageContainer ) foreach($registry in $nodeRegistry) { $fileName = $registry.replace("\","_").replace(":","") try { $output = Get-ChildItem -Path $registry -ErrorAction SilentlyContinue } catch { $output = $_ } finally { $output | Out-File -FilePath ($storageContainer + "\" + $fileName + ".txt") } } return $output } function Export-Folders() { param ( $nodeFolders, $storageContainer ) foreach($folder in $nodeFolders) { $storename = Split-Path -Path $folder -Leaf if($true -ne (Test-Path -Path ($storageContainer + "\" + $storename))) { New-Item -ItemType Directory -Path $storageContainer -Name $storename } if(Test-Path -Path $folder) { Copy-Item -Path $folder -Destination $storageContainer -Recurse -force } else { $msg = "THIS FOLDER DOES NOT EXISTS" $msg | Out-File ($storageContainer + "\" + $storename + "\" + $msg + ".txt") } } } # wrap all copy functions inside a try/catch to capture all unhandled exceptions. try { $storageContainer += ("\" + $env:COMPUTERNAME) # if storage container is not present, create it. if((Test-path $storageContainer) -ne $true) { New-Item -Type Directory -Path $storageContainer } Export-Commands -nodeCommands $nodeCommands -storageContainer $storageContainer Export-Events -nodeEvents $nodeEvents -storageContainer $storageContainer Export-Registry -nodeRegistry $nodeRegistry -storageContainer $storageContainer Export-Folders -nodeFolders $nodeFolders -storageContainer $storageContainer } catch { } finally { # compress data and cleanup raw data. Compress-Archive -Path $storageContainer -DestinationPath ($storageContainer + ".zip") Remove-Item -Path $storageContainer -Recurse -Force } } -ArgumentList @($runtime, $NodeCommands, $NodeEvents, $NodeRegistry, $NodeFolders, ($env:COMPUTERNAME), $storageContainer) Trace-Output -Level:Information -Message $msg.CollectionCompress $output = Reconcile-DiagnosticCheck -jobs $job # END NODE COMMANDS # START COPY Trace-Output -Level:Information -Message $msg.CollectionCopy foreach ($computer in $ComputerName) { $computerObj = [PSCustomObject]@{ Name = $null } if($computer.GetType().Name -eq 'String') { $computerObj.Name = $computer } else { $computerObj.Name = $computer.name } Trace-Output -Level:Verbose -Message ("ComputerName: " + $computerObj.name) # only copy the item to the node when we are not the "destination" node. if($computerObj.name -ne $env:COMPUTERNAME) { $remotePath = ($storageContainer + "\" + $computerObj.name + ".zip") Trace-Output -Level:Verbose -Message ($computerObj.name + " is not " + $env:COMPUTERNAME + ". Starting copy") $fromSession = New-PSSession -ComputerName $computer Trace-Output -Level:Verbose -Message ("Copy from: " + $remotePath + " To: " + $storageContainer) try { Copy-Item -Path $remotePath -Destination $storageContainer -FromSession $fromSession Trace-Output -Level:Information -Message ($msg.InfoCopyDoneNode -f $computerObj.name) } catch { Trace-Output -Level:Warning -Message ($msg.WarnCopyFailed -f $computerObj.name, $env:COMPUTERNAME, $remotePath , $storageContainer) } finally { Remove-PSSession -Session $fromSession } } } Trace-Output -Level:Information -Message $msg.CollectionCopyEnd # END COPY # START COMPRESSION $date = Get-Date -Format "HH-mm_dd-MM-yyyy" $rootFolder = Split-Path -Path $storageContainer $collection = $rootFolder + "\log-collection-" + $customName + $date + ".zip" Compress-Archive -Path ($storageContainer + "\*") -DestinationPath $collection if(Test-Path -Path $collection) { Trace-Output -Level:Information -Message ($msg.ArchiveCreated -f $collection, $storageContainer) Remove-Item -Path $storageContainer -Recurse -Force } else { Trace-Output -Level:Warning -Message ($msg.ArchiveFailCreate -f $storageContainer) } Trace-Output -Level:Success -Message ($msg.CollectionDataEnd -f $collection) } function Confirm-OperatingSystemVerison() { param ( [int] $build ) $versionInformation = [System.Environment]::OSVersion.Version $isCompatible = $false if($versionInformation.Build -eq $build) { $isCompatible = $true } return $isCompatible } # SIG # Begin signature block # MIIoKwYJKoZIhvcNAQcCoIIoHDCCKBgCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBUQeE8rpH06Qkw # BnY8TN6Qo4f4arEp3trtnhaxHVlcH6CCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGgswghoHAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIImOHDOtFMhYayvkos5LEdgu # svhWoQhAkbtIH7Y6qWpAMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAG2KYGen4hFgBDCt7ApHD3vADW31ddBbDYjnPCn1w/jlv/vfQw+esmSdI # 4KN7ePX8r5M/oBNZDEt1rdKPczBwcjtGI+sED/0+99KtebDXcocM1FiyKYXHWt+Y # jZA73eFXJu5Y06tTQrPTrM0Ksm38fvKCtJqai8fWE2aEc8pAqLRjkTuDyosvP7sF # RJFV3fdmBbAQSAye3kLWTR7Igka41hVY90ZiYarKZHnyWnXYh/FZg8norZ2Pje4r # LC40chLlWO5hm+RuEJdmZdf/mxCLYl4H6tbOlnBMXiw3CFa5I411lViQeTchP2rs # +XYCyB5p/KcqGejlU0MIndravThtqqGCF5UwgheRBgorBgEEAYI3AwMBMYIXgTCC # F30GCSqGSIb3DQEHAqCCF24wghdqAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCArsRIJJgen7IsniYj/A/KchqR+RJLI3lFSoTJoFPxgqAIGZzX/uDnG # GBMyMDI0MTExNTEwMTQ0Ny42OTVaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHrMIIHIDCCBQigAwIBAgITMwAAAevgGGy1tu847QABAAAB6zANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MzRaFw0yNTAzMDUxODQ1MzRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDBFWgh2lbgV3eJp01oqiaFBuYbNc7hSKmktvJ15NrB # /DBboUow8WPOTPxbn7gcmIOGmwJkd+TyFx7KOnzrxnoB3huvv91fZuUugIsKTnAv # g2BU/nfN7Zzn9Kk1mpuJ27S6xUDH4odFiX51ICcKl6EG4cxKgcDAinihT8xroJWV # ATL7p8bbfnwsc1pihZmcvIuYGnb1TY9tnpdChWr9EARuCo3TiRGjM2Lp4piT2lD5 # hnd3VaGTepNqyakpkCGV0+cK8Vu/HkIZdvy+z5EL3ojTdFLL5vJ9IAogWf3XAu3d # 7SpFaaoeix0e1q55AD94ZwDP+izqLadsBR3tzjq2RfrCNL+Tmi/jalRto/J6bh4f # PhHETnDC78T1yfXUQdGtmJ/utI/ANxi7HV8gAPzid9TYjMPbYqG8y5xz+gI/SFyj # +aKtHHWmKzEXPttXzAcexJ1EH7wbuiVk3sErPK9MLg1Xb6hM5HIWA0jEAZhKEyd5 # hH2XMibzakbp2s2EJQWasQc4DMaF1EsQ1CzgClDYIYG6rUhudfI7k8L9KKCEufRb # K5ldRYNAqddr/ySJfuZv3PS3+vtD6X6q1H4UOmjDKdjoW3qs7JRMZmH9fkFkMzb6 # YSzr6eX1LoYm3PrO1Jea43SYzlB3Tz84OvuVSV7NcidVtNqiZeWWpVjfavR+Jj/J # OQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFHSeBazWVcxu4qT9O5jT2B+qAerhMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCDdN8voPd8C+VWZP3+W87c/QbdbWK0sOt9 # Z4kEOWng7Kmh+WD2LnPJTJKIEaxniOct9wMgJ8yQywR8WHgDOvbwqdqsLUaM4Nre # rtI6FI9rhjheaKxNNnBZzHZLDwlkL9vCEDe9Rc0dGSVd5Bg3CWknV3uvVau14F55 # ESTWIBNaQS9Cpo2Opz3cRgAYVfaLFGbArNcRvSWvSUbeI2IDqRxC4xBbRiNQ+1qH # XDCPn0hGsXfL+ynDZncCfszNrlgZT24XghvTzYMHcXioLVYo/2Hkyow6dI7uULJb # KxLX8wHhsiwriXIDCnjLVsG0E5bR82QgcseEhxbU2d1RVHcQtkUE7W9zxZqZ6/jP # maojZgXQO33XjxOHYYVa/BXcIuu8SMzPjjAAbujwTawpazLBv997LRB0ZObNckJY # yQQpETSflN36jW+z7R/nGyJqRZ3HtZ1lXW1f6zECAeP+9dy6nmcCrVcOqbQHX7Zr # 8WPcghHJAADlm5ExPh5xi1tNRk+i6F2a9SpTeQnZXP50w+JoTxISQq7vBij2nitA # sSLaVeMqoPi+NXlTUNZ2NdtbFr6Iir9ZK9ufaz3FxfvDZo365vLOozmQOe/Z+pu4 # vY5zPmtNiVIcQnFy7JZOiZVDI5bIdwQRai2quHKJ6ltUdsi3HjNnieuE72fT4eWh # xtmnN5HYCDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # 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/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNO # MIICNgIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCA # Bol1u1wwwYgUtUowMnqYvbul3qCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6uEm2TAiGA8yMDI0MTExNTAxNDgw # OVoYDzIwMjQxMTE2MDE0ODA5WjB1MDsGCisGAQQBhFkKBAExLTArMAoCBQDq4SbZ # AgEAMAgCAQACAwFyxDAHAgEAAgITBTAKAgUA6uJ4WQIBADA2BgorBgEEAYRZCgQC # MSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqG # SIb3DQEBCwUAA4IBAQAJ9GcoxJ7WPoi9fhNLpa2vGohnrq9bzzhFlg2+Brzqcstf # xMTBxG5XLporWuC3j8VuMQq1w24yCowcEpVqAj/koPArv2ZMWovBt45fWWJet3qB # aRmUiOzKOK8ON8jdat9wJMtHqTZtRjjzvgkAfmvFCjt1B4c8he+dmoHs++Klr7T0 # sUOIOllo4WbcSo2WWQLKrsuVEq5MkbdlSwmQeQ5RjXcGsMFCkrZo5kHKH1bjmZ78 # 5fD2lGyrFXQ8+1wuXSYn7UcWKGkYPyIlCIcDRNF9PXopnlAxm65wu/ognR2Tg2kf # rm2ct5GC+gi1n6N9B587B02GKY/d8sw5fCVlXJnYMYIEDTCCBAkCAQEwgZMwfDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v # bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHr4BhstbbvOO0AAQAAAesw # DQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAv # BgkqhkiG9w0BCQQxIgQguKkIPXvXyUEDvql59JwSaSQWr6rawR+4TgJ1eWtafq4w # gfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDOt2u+X2kD4X/EgQ07ZNg0lICG # 3Ys17M++odSXYSws+DCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy # MDEwAhMzAAAB6+AYbLW27zjtAAEAAAHrMCIEILkV/fGoAF+xC8CJW28xsW8ToHAp # 0N6LYJXJffFW8+edMA0GCSqGSIb3DQEBCwUABIICAEinMvS1C/J7BwyPw+/xPWYs # l4z6dzSoGATI/zswRO8iwrfuoO5wkVTvsNz+fzbYL0DAiEdIFp0Sl6qUYL45/7aj # FVKFLFE2fPA3OCJmvcaSgY0r3UodCP6m6wMW+YuoIxbZMo2GAkPmd74iGy633t/H # PVfYoikfIX6cIn8smFITTd4+vbKSVopnBGIoQd2cZL9hqPTqftKx47va0hPuW0mc # n3ccIH04Xcjf0Vt93rgJrfDrhulPuaMa3KoHxJQrKrrlpGOq+nGGX23oPxbgRUXQ # LQTrcDAJSOhH1x9E9f6QjbFhP7frOgjouvADiEvAqydaJV1kWFXRoABZMzpq4Cus # 3Ye0IEHnqngWeLyjHCADs10gYWlbAnlHXm9pMwvFVyDEuuj/Dg9w1B/xJngx9E+3 # 4I/kk3Nr4F8tHk/9Kai0Gzn/L6AkndqMC3x34Nn25WcsBSjPij1LtJJBcB9O+9j6 # tbrwKbx/0/8LcopMPhZJubJfA3kQ0iPBvyhFxQVhOW+ajJOcc3+ed5QqLzDuRcmp # etX1KEY9xtCw9YF2QkdZP6LGLhR+0V0hq8APKd7Ulw7qYxZH20S79wX3gEXKE7QP # TSqw/nfMdZCv66keRRjP9pmVMguYUl+7YrMcmAvNPMY2pm59I6gCr65ha9ncJdPp # E8Lqo2P/Nq6xMYcAOd87 # SIG # End signature block |