functions/Save-DbaCommunitySoftware.ps1

function Save-DbaCommunitySoftware {
    <#
    .SYNOPSIS
        Download and extract software from Github to update the local cached version of that software.
 
    .DESCRIPTION
        Download and extract software from Github to update the local cached version of that software.
        This command is run from inside of Install-Dba* and Update-Dba* commands to update the local cache if needed.
 
        In case you don't have internet access on the target computer, you can download the zip files from the following URLs
        at another computer, transfer them to the target computer or place them on a network share and then use -LocalFile
        to update the local cache:
        * MaintenanceSolution: https://github.com/olahallengren/sql-server-maintenance-solution
        * FirstResponderKit: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/releases
        * DarlingData: https://github.com/erikdarlingdata/DarlingData
        * SQLWATCH: https://github.com/marcingminski/sqlwatch/releases
        * WhoIsActive: https://github.com/amachanic/sp_whoisactive/releases
        * DbaMultiTool: https://github.com/LowlyDBA/dba-multitool/releases
 
    .PARAMETER Software
        Name of the software to download.
        Options include:
        * MaintenanceSolution: SQL Server Maintenance Solution created by Ola Hallengren (https://ola.hallengren.com)
        * FirstResponderKit: First Responder Kit created by Brent Ozar (http://FirstResponderKit.org)
        * DarlingData: Erik Darling's stored procedures (https://www.erikdarlingdata.com)
        * SQLWATCH: SQL Server Monitoring Solution created by Marcin Gminski (https://sqlwatch.io/)
        * WhoIsActive: Adam Machanic's comprehensive activity monitoring stored procedure sp_WhoIsActive (https://github.com/amachanic/sp_whoisactive)
        * DbaMultiTool: John McCall's T-SQL scripts for the long haul: optimizing storage, on-the-fly documentation, and general administrative needs (https://dba-multitool.org)
 
    .PARAMETER Branch
        Specifies the branch. Defaults to master or main. Can only be used if Software is used.
 
    .PARAMETER LocalFile
        Specifies the path to a local file to install from instead of downloading from Github.
 
    .PARAMETER Url
        Specifies the URL to download from. Is not needed if Software is used.
 
    .PARAMETER LocalDirectory
        Specifies the local directory to extract the downloaded file to. Is not needed if Software is used.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER EnableException
        By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message.
        This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting.
        Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch.
 
    .NOTES
        Tags: Community
        Author: Andreas Jordan, @JordanOrdix
 
        Website: https://dbatools.io
        Copyright: (c) 2021 by dbatools, licensed under MIT
        License: MIT https://opensource.org/licenses/MIT
 
    .LINK
         https://dbatools.io/Save-DbaCommunitySoftware
 
    .EXAMPLE
        PS C:\> Save-DbaCommunitySoftware -Software MaintenanceSolution
 
        Updates the local cache of Ola Hallengren's Solution objects.
 
    .EXAMPLE
        PS C:\> Save-DbaCommunitySoftware -Software FirstResponderKit -LocalFile \\fileserver\Software\SQL-Server-First-Responder-Kit-20211106.zip
 
        Updates the local cache of the First Responder Kit based on the given file.
 
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param (
        [ValidateSet('MaintenanceSolution', 'FirstResponderKit', 'DarlingData', 'SQLWATCH', 'WhoIsActive', 'DbaMultiTool')]
        [string]$Software,
        [string]$Branch,
        [string]$LocalFile,
        [string]$Url,
        [string]$LocalDirectory,
        [switch]$EnableException
    )

    process {
        $dbatoolsData = Get-DbatoolsConfigValue -FullName "Path.DbatoolsData"

        # Set Branch, Url and LocalDirectory for known Software
        if ($Software -eq 'MaintenanceSolution') {
            if (-not $Branch) {
                $Branch = 'master'
            }
            if (-not $Url) {
                $Url = "https://github.com/olahallengren/sql-server-maintenance-solution/archive/$Branch.zip"
            }
            if (-not $LocalDirectory) {
                $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "sql-server-maintenance-solution-$Branch"
            }
        } elseif ($Software -eq 'FirstResponderKit') {
            if (-not $Branch) {
                $Branch = 'main'
            }
            if (-not $Url) {
                $Url = "https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/archive/$Branch.zip"
            }
            if (-not $LocalDirectory) {
                $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "SQL-Server-First-Responder-Kit-$Branch"
            }
        } elseif ($Software -eq 'DarlingData') {
            if (-not $Branch) {
                $Branch = 'main'
            }
            if (-not $Url) {
                $Url = "https://github.com/erikdarlingdata/DarlingData/archive/$Branch.zip"
            }
            if (-not $LocalDirectory) {
                $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "DarlingData-$Branch"
            }
        } elseif ($Software -eq 'SQLWATCH') {
            if ($Branch -in 'prerelease', 'pre-release') {
                $preRelease = $true
            } else {
                $preRelease = $false
            }
            if (-not $Url -and -not $LocalFile) {
                $releasesUrl = "https://api.github.com/repos/marcingminski/sqlwatch/releases"
                try {
                    try {
                        $releasesJson = Invoke-TlsWebRequest -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
                    } catch {
                        # Try with default proxy and usersettings
                        (New-Object System.Net.WebClient).Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
                        $releasesJson = Invoke-TlsWebRequest -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
                    }
                } catch {
                    Stop-Function -Message "Unable to get release information from $releasesUrl." -ErrorRecord $_
                    return
                }
                $latestRelease = ($releasesJson | ConvertFrom-Json) | Where-Object prerelease -eq $preRelease | Select-Object -First 1
                if ($null -eq $latestRelease) {
                    Stop-Function -Message "No release found." -ErrorRecord $_
                    return
                }
                $Url = $latestRelease.assets[0].browser_download_url
            }
            if (-not $LocalDirectory) {
                if ($preRelease) {
                    $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "SQLWATCH-prerelease"
                } else {
                    $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "SQLWATCH"
                }
            }
        } elseif ($Software -eq 'WhoIsActive') {
            # We currently ignore -Branch as there is only one branch and there are no pre-releases.
            if (-not $Url -and -not $LocalFile) {
                $releasesUrl = "https://api.github.com/repos/amachanic/sp_whoisactive/releases"
                try {
                    try {
                        $releasesJson = Invoke-TlsWebRequest -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
                    } catch {
                        # Try with default proxy and usersettings
                        (New-Object System.Net.WebClient).Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
                        $releasesJson = Invoke-TlsWebRequest -Uri $releasesUrl -UseBasicParsing -ErrorAction Stop
                    }
                } catch {
                    Stop-Function -Message "Unable to get release information from $releasesUrl." -ErrorRecord $_
                    return
                }
                $latestRelease = ($releasesJson | ConvertFrom-Json) | Select-Object -First 1
                if ($null -eq $latestRelease) {
                    Stop-Function -Message "No release found." -ErrorRecord $_
                    return
                }
                $Url = $latestRelease.zipball_url
            }
            if (-not $LocalDirectory) {
                $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "WhoIsActive"
            }
        } elseif ($Software -eq 'DbaMultiTool') {
            if (-not $Branch) {
                $Branch = 'master'
            }
            if (-not $Url) {
                $Url = "https://github.com/LowlyDBA/dba-multitool/archive/$Branch.zip"
            }
            if (-not $LocalDirectory) {
                $LocalDirectory = Join-Path -Path $dbatoolsData -ChildPath "dba-multitool-$Branch"
            }
        }

        # Test if we now have source and destination for the following actions.
        if (-not ($Url -or $LocalFile)) {
            Stop-Function -Message 'Url or LocalFile is missing.'
            return
        }
        if (-not $LocalDirectory) {
            Stop-Function -Message 'LocalDirectory is missing.'
            return
        }

        # First part is download and extract and we use the temp directory for that and clean up afterwards.
        # So we use a file and a folder with a random name to reduce potential conflicts,
        # but name them with dbatools to be able to recognize them.
        $temp = [System.IO.Path]::GetTempPath()
        $random = Get-Random
        $zipFile = Join-DbaPath -Path $temp -Child "dbatools_software_download_$random.zip"
        $zipFolder = Join-DbaPath -Path $temp -Child "dbatools_software_download_$random"

        if ($Software -eq 'WhoIsActive' -and $LocalFile.EndsWith('.sql')) {
            # For WhoIsActive, we allow to pass in the sp_WhoIsActive.sql file or any other sql file with the source code.
            # We create the zip folder with a subfolder named WhoIsActive and copy the LocalFile there as sp_WhoIsActive.sql.
            $appFolder = Join-DbaPath -Path $zipFolder -Child 'WhoIsActive'
            $appFile = Join-DbaPath -Path $appFolder -Child 'sp_WhoIsActive.sql'
            $null = New-Item -Path $zipFolder -ItemType Directory
            $null = New-Item -Path $appFolder -ItemType Directory
            Copy-Item -Path $LocalFile -Destination $appFile
        } elseif ($LocalFile) {
            # No download, so we just extract the given file if it exists and is a zip file.
            if (-not (Test-Path $LocalFile)) {
                Stop-Function -Message "$LocalFile doesn't exist"
                return
            }
            if (-not ($LocalFile.EndsWith('.zip'))) {
                Stop-Function -Message "$LocalFile has to be a zip file"
                return
            }
            if ($PSCmdlet.ShouldProcess($LocalFile, "Extracting archive to $zipFolder path")) {
                try {
                    Unblock-File $LocalFile -ErrorAction SilentlyContinue
                    Expand-Archive -LiteralPath $LocalFile -DestinationPath $zipFolder -Force -ErrorAction Stop
                } catch {
                    Stop-Function -Message "Unable to extract $LocalFile to $zipFolder." -ErrorRecord $_
                    return
                }
            }
        } else {
            # Download and extract.
            if ($PSCmdlet.ShouldProcess($Url, "Downloading to $zipFile")) {
                try {
                    try {
                        Invoke-TlsWebRequest -Uri $Url -OutFile $zipFile -UseBasicParsing -ErrorAction Stop
                    } catch {
                        # Try with default proxy and usersettings
                        (New-Object System.Net.WebClient).Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
                        Invoke-TlsWebRequest -Uri $Url -OutFile $zipFile -UseBasicParsing -ErrorAction Stop
                    }
                } catch {
                    Stop-Function -Message "Unable to download $Url to $zipFile." -ErrorRecord $_
                    return
                }
            }
            if ($PSCmdlet.ShouldProcess($zipFile, "Extracting archive to $zipFolder path")) {
                try {
                    Unblock-File $zipFile -ErrorAction SilentlyContinue
                    Expand-Archive -Path $zipFile -DestinationPath $zipFolder -Force -ErrorAction Stop
                } catch {
                    Stop-Function -Message "Unable to extract $zipFile to $zipFolder." -ErrorRecord $_
                    Remove-Item -Path $zipFile -ErrorAction SilentlyContinue
                    return
                }
            }
        }

        # As a safety net, we test whether the archive contained exactly the desired destination directory.
        # But inside of zip files that are downloaded by the user via a webbrowser and not the api,
        # the directory name is the name of the zip file. So we have to test for that as well.
        if ($PSCmdlet.ShouldProcess($zipFolder, "Testing for correct content")) {
            $localDirectoryBase = Split-Path -Path $LocalDirectory
            $localDirectoryName = Split-Path -Path $LocalDirectory -Leaf
            $sourceDirectory = Get-ChildItem -Path $zipFolder -Directory
            $sourceDirectoryName = $sourceDirectory.Name
            if ($Software -eq 'SQLWATCH') {
                # As this software is downloaded as a release, the directory has a different name.
                # Rename the directory from like 'SQLWATCH 4.3.0.23725 20210721131116' to 'SQLWATCH' to be able to handle this like the other software.
                if ($sourceDirectoryName -like 'SQLWATCH*') {
                    # Write a file with version info, to be able to check if version is outdated
                    Set-Content -Path "$($sourceDirectory.FullName)\version.txt" -Value $sourceDirectoryName
                    Rename-Item -Path $sourceDirectory.FullName -NewName 'SQLWATCH'
                    $sourceDirectory = Get-ChildItem -Path $zipFolder -Directory
                    $sourceDirectoryName = $sourceDirectory.Name
                }
            } elseif ($Software -eq 'WhoIsActive') {
                # As this software is downloaded as a release, the directory has a different name.
                # Rename the directory from like 'amachanic-sp_whoisactive-459d2bc' to 'WhoIsActive' to be able to handle this like the other software.
                if ($sourceDirectoryName -like '*sp_whoisactive-*') {
                    Rename-Item -Path $sourceDirectory.FullName -NewName 'WhoIsActive'
                    $sourceDirectory = Get-ChildItem -Path $zipFolder -Directory
                    $sourceDirectoryName = $sourceDirectory.Name
                }
            } elseif ($Software -eq 'FirstResponderKit') {
                # As this software is downloadable as a release, the directory might have a different name.
                # Rename the directory from like 'SQL-Server-First-Responder-Kit-20211106' to 'SQL-Server-First-Responder-Kit-main' to be able to handle this like the other software.
                if ($sourceDirectoryName -like 'SQL-Server-First-Responder-Kit-20*') {
                    Rename-Item -Path $sourceDirectory.FullName -NewName 'SQL-Server-First-Responder-Kit-main'
                    $sourceDirectory = Get-ChildItem -Path $zipFolder -Directory
                    $sourceDirectoryName = $sourceDirectory.Name
                }
            } elseif ($Software -eq 'DbaMultiTool') {
                # As this software is downloadable as a release, the directory might have a different name.
                # Rename the directory from like 'dba-multitool-1.7.5' to 'dba-multitool-master' to be able to handle this like the other software.
                if ($sourceDirectoryName -like 'dba-multitool-[0-9]*') {
                    Rename-Item -Path $sourceDirectory.FullName -NewName 'dba-multitool-master'
                    $sourceDirectory = Get-ChildItem -Path $zipFolder -Directory
                    $sourceDirectoryName = $sourceDirectory.Name
                }
            }
            if ((Get-ChildItem -Path $zipFolder).Count -gt 1 -or $sourceDirectoryName -ne $localDirectoryName) {
                Stop-Function -Message "The archive does not contain the desired directory $localDirectoryName but $sourceDirectoryName."
                Remove-Item -Path $zipFile -ErrorAction SilentlyContinue
                Remove-Item -Path $zipFolder -Recurse -ErrorAction SilentlyContinue
                return
            }
        }

        # Replace the target directory by the extracted directory.
        if ($PSCmdlet.ShouldProcess($zipFolder, "Copying content to $LocalDirectory")) {
            try {
                if (Test-Path -Path $LocalDirectory) {
                    Remove-Item -Path $LocalDirectory -Recurse -ErrorAction Stop
                }
            } catch {
                Stop-Function -Message "Unable to remove the old target directory $LocalDirectory." -ErrorRecord $_
                Remove-Item -Path $zipFile -ErrorAction SilentlyContinue
                Remove-Item -Path $zipFolder -Recurse -ErrorAction SilentlyContinue
                return
            }
            try {
                Copy-Item -Path $sourceDirectory.FullName -Destination $localDirectoryBase -Recurse -ErrorAction Stop
            } catch {
                Stop-Function -Message "Unable to copy the directory $sourceDirectory to the target directory $localDirectoryBase." -ErrorRecord $_
                Remove-Item -Path $zipFile -ErrorAction SilentlyContinue
                Remove-Item -Path $zipFolder -Recurse -ErrorAction SilentlyContinue
                return
            }
        }

        if ($PSCmdlet.ShouldProcess($zipFile, "Removing temporary file")) {
            Remove-Item -Path $zipFile -ErrorAction SilentlyContinue
        }
        if ($PSCmdlet.ShouldProcess($zipFolder, "Removing temporary folder")) {
            Remove-Item -Path $zipFolder -Recurse -ErrorAction SilentlyContinue
        }
    }
}

# SIG # Begin signature block
# MIIZewYJKoZIhvcNAQcCoIIZbDCCGWgCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUOQHSs1T/LKVsRXqyC0Kp2Bfj
# VPWgghSJMIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0B
# AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz
# c3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEw
# NjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQ
# tSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4
# bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOK
# fF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlK
# XAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYer
# vnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0
# MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG
# AQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0
# dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLk
# YaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0f
# BGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl
# ZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz
# c3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6
# Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NB
# LmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5nRv1CclF0CiNH
# o6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4
# eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2h
# F3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1
# FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6X
# t/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aKLzCCBRowggQC
# oAMCAQICEAMFu4YhsKFjX7/erhIE520wDQYJKoZIhvcNAQELBQAwcjELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg
# U2lnbmluZyBDQTAeFw0yMDA1MTIwMDAwMDBaFw0yMzA2MDgxMjAwMDBaMFcxCzAJ
# BgNVBAYTAlVTMREwDwYDVQQIEwhWaXJnaW5pYTEPMA0GA1UEBxMGVmllbm5hMREw
# DwYDVQQKEwhkYmF0b29sczERMA8GA1UEAxMIZGJhdG9vbHMwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQC8v2N7q+O/vggBtpjmteofFo140k73JXQ5sOD6
# QLzjgija+scoYPxTmFSImnqtjfZFWmucAWsDiMVVro/6yGjsXmJJUA7oD5BlMdAK
# fuiq4558YBOjjc0Bp3NbY5ZGujdCmsw9lqHRAVil6P1ZpAv3D/TyVVq6AjDsJY+x
# rRL9iMc8YpD5tiAj+SsRSuT5qwPuW83ByRHqkaJ5YDJ/R82ZKh69AFNXoJ3xCJR+
# P7+pa8tbdSgRf25w4ZfYPy9InEvsnIRVZMeDjjuGvqr0/Mar73UI79z0NYW80yN/
# 7VzlrvV8RnniHWY2ib9ehZligp5aEqdV2/XFVPV4SKaJs8R9AgMBAAGjggHFMIIB
# wTAfBgNVHSMEGDAWgBRaxLl7KgqjpepxA8Bg+S32ZXUOWDAdBgNVHQ4EFgQU8MCg
# +7YDgENO+wnX3d96scvjniIwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsG
# AQUFBwMDMHcGA1UdHwRwMG4wNaAzoDGGL2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv
# bS9zaGEyLWFzc3VyZWQtY3MtZzEuY3JsMDWgM6Axhi9odHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDBMBgNVHSAERTBDMDcGCWCG
# SAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20v
# Q1BTMAgGBmeBDAEEATCBhAYIKwYBBQUHAQEEeDB2MCQGCCsGAQUFBzABhhhodHRw
# Oi8vb2NzcC5kaWdpY2VydC5jb20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9jYWNlcnRz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURDb2RlU2lnbmluZ0NB
# LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCPzflwlQwf1jak
# EqymPOc0nBxiY7F4FwcmL7IrTLhub6Pjg4ZYfiC79Akz5aNlqO+TJ0kqglkfnOsc
# jfKQzzDwcZthLVZl83igzCLnWMo8Zk/D2d4ZLY9esFwqPNvuuVDrHvgh7H6DJ/zP
# Vm5EOK0sljT0UQ6HQEwtouH5S8nrqCGZ8jKM/+DeJlm+rCAGGf7TV85uqsAn5JqD
# En/bXE1AlyG1Q5YiXFGS5Sf0qS4Nisw7vRrZ6Qc4NwBty4cAYjzDPDixorWI8+FV
# OUWKMdL7tV8i393/XykwsccCstBCp7VnSZN+4vgzjEJQql5uQfysjcW9rrb/qixp
# csPTKYRHMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkqhkiG9w0B
# AQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAwWjByMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQg
# Q29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
# +NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6kkPApfmJ
# 1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQjZhJUM1B0
# sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5wMWYzcT6s
# cKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp6moKq4Tz
# rGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH5DiLanMg
# 0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgwBgEB/wIB
# ADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYIKwYBBQUH
# AQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYI
# KwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz
# c3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaG
# NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD
# QS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0
# dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYE
# FFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6en
# IZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2qB1dHC06
# GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4QpO4/cY5j
# DhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEpKBo6cSgC
# PC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/DmZAwlCEIy
# sjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9CBoYs4Gb
# T8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHvMIIFMTCC
# BBmgAwIBAgIQCqEl1tYyG35B5AXaNpfCFTANBgkqhkiG9w0BAQsFADBlMQswCQYD
# VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln
# aWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
# HhcNMTYwMTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAwWjByMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5n
# IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvdAy7kvNj3/dqbqC
# mcU5VChXtiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI5Je/YyGQmL8TvFfTw+F+CNZq
# FAA49y4eO+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+wKL1oODeIj8O/36V+/OjuiI+
# GKwR5PCZA207hXwJ0+5dyJoLVOOoCXFr4M8iEA91z3FyTgqt30A6XLdR4aF5FMZN
# JCMwXbzsPGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmEUeaC50ZQ/ZQqLKfkdT66mA+E
# f58xFNat1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9olMqT4UdxB08r8/arBD13ays
# 6Vb/kwIDAQABo4IBzjCCAcowHQYDVR0OBBYEFPS24SAd/imu0uRhpbKiJbLIFzVu
# MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHkGCCsG
# AQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t
# MEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl
# cnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8v
# Y3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqg
# OKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS
# b290Q0EuY3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIB
# FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAsGCWCGSAGG/WwHATANBgkq
# hkiG9w0BAQsFAAOCAQEAcZUS6VGHVmnN793afKpjerN4zwY3QITvS4S/ys8DAv3F
# p8MOIEIsr3fzKx8MIVoqtwU0HWqumfgnoma/Capg33akOpMP+LLR2HwZYuhegiUe
# xLoceywh4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQGF+JOGFNYkYkh2OMkVIsrymJ
# 5Xgf1gsUpYDXEkdws3XVk4WTfraSZ/tTYYmo9WuWwPRYaQ18yAGxuSh1t5ljhSKM
# Ycp5lH5Z/IwP42+1ASa2bKXuh1Eh5Fhgm7oMLSttosR+u8QlK0cCCHxJrhO24XxC
# QijGGFbPQTS2Zl22dHv1VjMiLyI2skuiSpXY9aaOUjGCBFwwggRYAgEBMIGGMHIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJ
# RCBDb2RlIFNpZ25pbmcgQ0ECEAMFu4YhsKFjX7/erhIE520wCQYFKw4DAhoFAKB4
# MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB
# gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkE
# MRYEFM0xQIDnllCmSCqVeRefocQr2H0bMA0GCSqGSIb3DQEBAQUABIIBALq1Olff
# fYkSTOArJLBik4Au2kW+1MCw93oIYjsUIzFiKhGJ4gOCHmNEqtk61kMKJLGMvDHu
# /+RxtxHv3DCYOZDLF6g9D/JWRRbxs5Ke0wlNoS4RHHyhhseB0m16U/w6MhLj0nZX
# 0F+5f7ZotwqRQxVtXHwmMmbQNEKn4pa95MGlMJbXXdaEsi4o1Q0V5fAi84uS8QxG
# JRxHZybjdEpK/lPKQOgqflFflgWDT2vSq+E5D/rQcDtYDG6g9bKet8iOzV5yD4jp
# D/+VmqOTj+lwgL9t5PkkSv0XDEnRJCIBA5CBZ7x5JSFjT8H0/ZytssOvbzXcptj+
# kWPrWxNXEAZVL/yhggIwMIICLAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjEL
# MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
# LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElE
# IFRpbWVzdGFtcGluZyBDQQIQDUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEF
# AKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIy
# MDIyMTExNDUzNlowLwYJKoZIhvcNAQkEMSIEID6gcjto1K4M70BkPb66eM2WpgVZ
# V0jfNkbY/aUgSKydMA0GCSqGSIb3DQEBAQUABIIBAFd9cfH6A1oTQi0auFx+51s5
# QXVNeUPOexy69clVLgOAlaWI9INQTx4MTLmy/zdVcuweThh2ataxR7BegV+/qmAF
# eKxTgcZWgsBuFUJGg2SrLMtABVJUkR/PvfW/72/qz6gyLqprhegxq1KiY2pAAFTu
# Ttp0aUXD3wzlPnTNd3rbU82GvZsLLk1OZkN0Ek+OQHFCuRgRZ82SMknVI7GpZqmO
# c/M5WjOAnLRedZVbwkXPD/OLf+7GKM+0u92zpK2zttTP6jvMbzfe/uFBBsSF61Cj
# ePHmS1w0lP57HjbG8ZlLJ0CPOGAgxkOmSGyhJRe/lpAi2Wp9QBmAPdxTkFTkGjc=
# SIG # End signature block