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