AnyPackage.PSResourceGet.psm1

# Copyright (c) Thomas Nieto - All Rights Reserved
# You may use, distribute and modify this code under the
# terms of the MIT license.

using module AnyPackage
using module Microsoft.PowerShell.PSResourceGet

using namespace System.Collections.Generic
using namespace System.Threading
using namespace AnyPackage.Provider
using namespace AnyPackage.Feedback
using namespace Microsoft.PowerShell.PSResourceGet.UtilClasses

[PackageProvider('PSResourceGet')]
class PSResourceGetProvider : PackageProvider, IGetPackage, IFindPackage,
IInstallPackage, ISavePackage, IUninstallPackage,
IUpdatePackage, IPublishPackage, IGetSource, ISetSource, ICommandNotFound {
    [PackageProviderInfo] Initialize([PackageProviderInfo] $providerInfo) {
        return [PSResourceGetProviderInfo]::new($providerInfo)
    }

    [IEnumerable[CommandNotFoundFeedback]] FindPackage([CommandNotFoundContext] $context, [CancellationToken] $token) {
        $dict = [Dictionary[string, CommandNotFoundFeedback]]::new([StringComparer]::OrdinalIgnoreCase)
        $packages = $this.ProviderInfo.CommandCache[$context.Command]

        foreach ($package in $packages) {
            if (!$dict.ContainsKey($package.Name)) {
                $feedback = [CommandNotFoundFeedback]::new($package.Name, $this.ProviderInfo)
                $dict.Add($package.Name, $feedback)
            }
        }

        return $dict.Values
    }

    #region GetPackage
    [void] GetPackage([PackageRequest] $request) {
        $params = @{
            Name        = $request.Name
            ErrorAction = 'SilentlyContinue'
        }

        if ($request.Version) {
            $params['Version'] = $request.Version
        }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $params -IsBound

        Get-InstalledPSResource @params |
        Write-Package -Request $request
    }
    #endregion

    #region FindPackage
    [void] FindPackage([PackageRequest] $request) {
        $params = @{
            Name        = $request.Name
            Prerelease  = $request.Prerelease
            ErrorAction = 'SilentlyContinue'
        }

        if ($request.Version) {
            $params['Version'] = $request.Version
        }

        if ($request.Source) {
            $params['Repository'] = $request.Source
        }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $params -Exclude 'Latest' -IsBound

        $resources = Find-PSResource @params

        if ($request.DynamicParameters.Latest) {
            $resources = $resources | Get-Latest
        }

        $resources | Write-Package -Request $request
    }
    #endregion

    #region InstallPackage
    [void] InstallPackage([PackageRequest] $request) {
        $params = @{
            Name        = $request.Name
            Prerelease  = $request.Prerelease
            ErrorAction = 'SilentlyContinue'
        }

        if ($request.Version) {
            $params['Version'] = $request.Version
        }

        if ($request.Source) {
            $params['Repository'] = $request.Source
        }

        $installParams = @{ }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $installParams -IsBound

        Find-PSResource @params |
        Get-Latest |
        Install-PSResource @installParams -TrustRepository -PassThru |
        Write-Package -Request $request
    }
    #endregion

    #region SavePackage
    [void] SavePackage([PackageRequest] $request) {
        $params = @{
            Name        = $request.Name
            Prerelease  = $request.Prerelease
            ErrorAction = 'SilentlyContinue'
        }

        if ($request.Version) {
            $params['Version'] = $request.Version
        }

        if ($request.Source) {
            $params['Repository'] = $request.Source
        }

        $saveParams = @{ }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $saveParams -IsBound

        Find-PSResource @params |
        Get-Latest |
        Save-PSResource @saveParams -Path $request.Path -TrustRepository -PassThru |
        Write-Package -Request $request
    }
    #endregion

    #region UninstallPackage
    [void] UninstallPackage([PackageRequest] $request) {
        $params = @{
            Name        = $request.Name
            ErrorAction = 'SilentlyContinue'
        }

        if ($request.Version) {
            $params['Version'] = $request.Version
        }

        $uninstallParams = @{ }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $uninstallParams -IsBound

        # Issue to get PassThru parameter added
        # https://github.com/PowerShell/PSResourceGet/issues/667

        # Prerelease parameter causes it to silently fail
        # https://github.com/PowerShell/PSResourceGet/issues/842
        Get-InstalledPSResource @params |
        ForEach-Object {
            try {
                $_ | Uninstall-PSResource @uninstallParams -ErrorAction Stop
                $_ | Write-Package -Request $request
            }
            catch {
                $_
            }
        }
    }
    #endregion

    #region UpdatePackage
    [void] UpdatePackage([PackageRequest] $request) {
        $params = @{
            Prerelease  = $request.Prerelease
        }

        if ($request.Version) {
            $params['Version'] = $request.Version
        }

        if ($request.Source) {
            $params['Repository'] = $request.Source
        }

        $updateParams = @{ }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $updateParams -IsBound

        # Find-PSResource pipeline input
        # https://github.com/PowerShell/PSResourceGet/issues/666
        Get-InstalledPSResource -Name $request.Name -ErrorAction SilentlyContinue |
        Select-Object -ExpandProperty Name -Unique |
        Find-PSResource @params -ErrorAction SilentlyContinue |
        Select-Object -ExpandProperty Name -Unique |
        Update-PSResource @params @updateParams -TrustRepository -PassThru |
        Write-Package -Request $request
    }
    #endregion

    #region PublishPackage
    [void] PublishPackage([PackageRequest] $request) {
        $params = @{
            Path = $request.Path
        }

        if ($request.Source) {
            $params['Repository'] = $request.Source
        }

        $request.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $params -IsBound

        try {
            # PassThru parameter
            # https://github.com/PowerShell/PSResourceGet/issues/718
            Publish-PSResource @params -ErrorAction Stop

            $params.Remove('Path')
            $params['Name'] = Get-Item -Path $request.Path |
            Select-Object -ExpandProperty BaseName

            Find-PSResource @params |
            Get-Latest |
            Write-Package -Request $request
        }
        catch {
            throw $_
        }
    }
    #endregion

    #region Source
    [void] GetSource([SourceRequest] $sourceRequest) {
        Get-PSResourceRepository -Name $sourceRequest.Name |
        Write-Source -SourceRequest $sourceRequest
    }

    [void] SetSource([SourceRequest] $sourceRequest) {
        $params = @{
            PassThru = $true
        }

        if ($sourceRequest.Location) {
            $params.Uri = $sourceRequest.Location
        }

        if ($null -ne $sourceRequest.Trusted) {
            $params.Trusted = $sourceRequest.Trusted
        }

        $sourceRequest.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $params -IsBound

        Get-PSResourceRepository -Name $sourceRequest.Name |
        Set-PSResourceRepository @params |
        Write-Source -SourceRequest $sourceRequest
    }

    [void] RegisterSource([SourceRequest] $sourceRequest) {
        $params = @{
            Trusted  = $sourceRequest.Trusted
            PassThru = $true
        }

        if ($sourceRequest.DynamicParameters.PSGallery) {
            $params['PSGallery'] = $true
        }
        else {
            $params['Name'] = $sourceRequest.Name
            $params['Uri'] = $sourceRequest.Location
        }

        $sourceRequest.DynamicParameters |
        ConvertTo-Hashtable -Hashtable $params -Exclude 'PSGallery' -IsBound

        Register-PSResourceRepository @params |
        Write-Source -SourceRequest $sourceRequest
    }

    [void] UnregisterSource([SourceRequest] $sourceRequest) {
        Get-PSResourceRepository -Name $sourceRequest.Name |
        Unregister-PSResourceRepository -PassThru |
        Write-Source -SourceRequest $sourceRequest
    }
    #endregion

    [object] GetDynamicParameters([string] $commandName) {
        return $(switch ($commandName) {
            'Get-Package' { return [GetPackageDynamicParameters]::new() }
            'Find-Package' { return [FindPackageDynamicParameters]::new() }
            'Install-Package' { return [InstallPackageDynamicParameters]::new() }
            'Publish-Package' { return [PublishPackageDynamicParameters]::new() }
            'Save-Package' { return [SavePackageDynamicParameters]::new() }
            'Uninstall-Package' { return [UninstallPackageDynamicParameters]::new() }
            'Update-Package' { return [UpdatePackageDynamicParameters]::new() }
            'Set-PackageSource' { return [SetPackageSourceDynamicParameters]::new() }
            'Register-PackageSource' { return [RegisterPackageSourceDynamicParameters]::new() }
            default { return $null }
        })
    }
}

class PSResourceGetProviderInfo : PackageProviderInfo {
    [Dictionary[string, List[PSResourceInfo]]] $CommandCache = [Dictionary[string, List[PSResourceInfo]]]::new([StringComparer]::OrdinalIgnoreCase)
    
    PSResourceGetProviderInfo([PackageProviderInfo] $providerInfo) : base($providerInfo) {
        if ([Runspace]::DefaultRunspace.Name -eq $this.FullName) {
            $this.SetCommandCache()
        }
    }

    [void] SetCommandCache() {
        $packages = Find-PSResource -Name * -Type Module

        foreach ($package in $packages) {
            $commands = $package.Tags | Where-Object { $_ -like "PSCommand*" } | ForEach-Object { $_ -replace 'PSCommand_', '' }

            foreach ($command in $commands) {
                if ($this.CommandCache.ContainsKey($command)) {
                    $this.CommandCache[$command] += $package
                } else {
                    $list = [List[PSResourceInfo]]::new()
                    $list += $package
                    $this.CommandCache.Add($command, $package)
                }
            }
        }
    }
}

class GetPackageDynamicParameters {
    [Parameter()]
    [string] $Path

    [Parameter()]
    [ScopeType] $Scope
}

class FindPackageDynamicParameters {
    [Parameter()]
    [switch] $Credential

    [Parameter()]
    [string[]] $Tag

    [Parameter()]
    [ResourceType] $Type

    [Parameter()]
    [switch] $Latest

    [Parameter()]
    [switch] $IncludeDependencies
}

class PublishPackageDynamicParameters {
    [Parameter()]
    [string] $ApiKey

    [Parameter()]
    [switch] $Credential

    [Parameter()]
    [string] $DestinationPath

    [Parameter()]
    [switch] $SkipDependenciesCheck

    [Parameter()]
    [switch] $SkipModuleManifestValidate

    [Parameter()]
    [uri] $Proxy

    [Parameter()]
    [pscredential] $ProxyCredential
}

class InstallDynamicParameters {
    [Parameter()]
    [switch] $AuthenticodeCheck

    [Parameter()]
    [switch] $Credential

    [Parameter()]
    [switch] $SkipDependencyCheck

    [Parameter()]
    [string] $TemporaryPath
}

class InstallUpdateDynamicParameters : InstallDynamicParameters {
    [Parameter()]
    [switch] $AcceptLicense

    [Parameter()]
    [ScopeType] $Scope
}

class InstallPackageDynamicParameters : InstallUpdateDynamicParameters {
    [Parameter()]
    [switch] $Reinstall

    # Install-PSResource -NoClobber fails
    # https://github.com/PowerShell/PSResourceGet/issues/946
    # [Parameter()]
    # [switch] $NoClobber
}

class SavePackageDynamicParameters : InstallDynamicParameters {
    # Pipeline input fails with -AsNupkg
    # https://github.com/PowerShell/PSResourceGet/issues/948
    # [Parameter()]
    # [switch] $AsNupkg

    # Pipeline input fails with -IncludeXml
    # https://github.com/PowerShell/PSResourceGet/issues/949
    # [Parameter()]
    # [switch] $IncludeXml
}

class UninstallPackageDynamicParameters {
    [Parameter()]
    [switch] $SkipDependencyCheck

    [Parameter()]
    [ScopeType] $Scope
}

class UpdatePackageDynamicParameters : InstallUpdateDynamicParameters {
    [Parameter()]
    [switch] $Force
}

class SetPackageSourceDynamicParameters {
    [Parameter()]
    [int] $Priority

    [Parameter()]
    [PSCredentialInfo] $CredentialInfo
}

class RegisterPackageSourceDynamicParameters : SetPackageSourceDynamicParameters {
    [Parameter(ParameterSetName = 'PSGallery')]
    [switch] $PSGallery
}

[guid] $id = 'c9a39544-274b-4935-9cad-7423e8c47e6b'
[PackageProviderManager]::RegisterProvider($id, [PSResourceGetProvider], $MyInvocation.MyCommand.ScriptBlock.Module)

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    [PackageProviderManager]::UnregisterProvider($id)
}

function ConvertTo-Hashtable {
    [CmdletBinding()]
    [OutputType([hashtable])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    param (
        [Parameter(Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [object]
        $InputObject,

        [Parameter()]
        [hashtable]
        $Hashtable = @{ },

        [Parameter()]
        [string[]]
        $Exclude,

        [Parameter()]
        [switch]
        $IsBound
    )

    process {
        if ($null -eq $InputObject) {
            return
        }

        $properties = $InputObject |
            Get-Member -MemberType Properties |
            Where-Object Name -notin $Exclude |
            Select-Object -ExpandProperty Name

        foreach ($property in $properties) {
            if ($IsBound -and -not $InputObject.$property) {
                continue
            }

            $Hashtable[$property] = $InputObject.$property
        }

        $Hashtable
    }
}

function Get-Latest {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,
            ValueFromPipeline)]
        [PSResourceInfo]
        $Resource
    )

    begin {
        $resources = [List[PSResourceInfo]]::new()
    }

    process {
        $resources.Add($resource)
    }

    end {
        $resources |
        Group-Object -Property Name |
        ForEach-Object {
            # PSResourceGet returns the latest as the first object
            $_.Group | Select-Object -First 1
        }
    }
}

function Write-Source {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline)]
        [PSRepositoryInfo]
        $Source,

        [Parameter(Mandatory)]
        [SourceRequest]
        $SourceRequest
    )

    process {
        $sourceInfo = [PackageSourceInfo]::new($Source.Name,
                                           $Source.Uri,
                                           [bool]::Parse($Source.Trusted),
                                           @{ Priority = $Source.Priority
                                              CredentialInfo = $Source.CredentialInfo },
                                            $SourceRequest.ProviderInfo)
        $SourceRequest.WriteSource($sourceInfo)
    }
}

function Write-Package {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline)]
        [PSResourceInfo]
        $Resource,

        [Parameter(Mandatory)]
        [PackageRequest]
        $Request
    )

    begin {
        $sources = Get-PackageSource -Provider AnyPackage.PSResourceGet\PSResourceGet
    }

    process {
        $ht = ConvertTo-Hashtable $resource

        $deps = [List[PackageDependency]]::new()
        foreach ($dep in $resource.Dependencies) {
            $versionRange = [PackageVersionRange]::new($dep.VersionRange, $true)
            $dependency = [PackageDependency]::new($dep.Name, $versionRange)
            $deps.Add($dependency)
        }

        $source = $sources |
        Where-Object Name -eq $resource.Repository

        # Blank RepositorySourceLocation
        # https://github.com/PowerShell/PSResourceGet/issues/1052
        if (-not $source -and $resource.RepositorySourceLocation) {
            $source = [PackageSourceInfo]::new($resource.Repository, $resource.RepositorySourceLocation, $false, $Request.ProviderInfo)
        }

        if ($resource.Prerelease) {
            $version = "{0}-{1}" -f $resource.Version, $resource.Prerelease
        }
        else {
            $version = $resource.Version
        }

        $package = [PackageInfo]::new($resource.Name, $version, $source, $resource.Description, $deps, $ht, $Request.ProviderInfo)
        $Request.WritePackage($package)
    }
}

# SIG # Begin signature block
# MIIlQgYJKoZIhvcNAQcCoIIlMzCCJS8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDbka/I59e2UBfe
# UrOJkdGSHv0BEi1G+EFQKaxBwabmQ6CCHtcwggW2MIIEHqADAgECAhEApPLKEVUi
# zMqDeJHsqFBBojANBgkqhkiG9w0BAQwFADBUMQswCQYDVQQGEwJHQjEYMBYGA1UE
# ChMPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2Rl
# IFNpZ25pbmcgQ0EgUjM2MB4XDTIyMTIwNjAwMDAwMFoXDTI1MTIwNTIzNTk1OVow
# TDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkthbnNhczEVMBMGA1UECgwMVGhvbWFz
# IE5pZXRvMRUwEwYDVQQDDAxUaG9tYXMgTmlldG8wggGiMA0GCSqGSIb3DQEBAQUA
# A4IBjwAwggGKAoIBgQDKp+8DxaXlIDZh+gMApqRZ4hD4IDTdRkUEZZqSnUwxQnD5
# k8VpbDeqrEkmww2TKUeOWQbSRMT70T/8R8+ld2GrwzLDQ83k5mrGXIXSXCHRiLIZ
# UnLmVjDtf/4FXTEni+acu8dddbCS0GAAzDfnINFgVRQR9GdoaYLqoipRVpLVRBzb
# eR2gSCtzwmpuX1d4NR58NvfUkfzNxy0kaLfqamHl9ae0JHyGvyzrHgOUyt2ttA/F
# idNo8KudwGau/gfyko5egOAURpEqgJHrcaekTVio+GRQs2IFNHIvNnfF9Rm7kn5w
# PZuxWL4UaV5xGyYWLupny3Lpp1n+XlVg6UgYZX25BWwbdbfLvsDPHnlbPB2L0WgC
# CeG6ZOZjB2dmFaYHhwozRzcvX0FLoYXv1/Vo9QviUTm2QIqb/gaxt3xl/rtcCZOj
# ly518iHNR2f1ClS+dPiD7KO5r2owmUsaMZiPVeMnD8/dKT8c9jPhyRBntbX9V6ho
# RXD6J2mf5SKSql8pwC8CAwEAAaOCAYkwggGFMB8GA1UdIwQYMBaAFA8qyyCHKLjs
# b0iuK1SmKaoXpM0MMB0GA1UdDgQWBBQOV7tpbOOE2AnSg3DwNoU56VePWzAOBgNV
# HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDAzBK
# BgNVHSAEQzBBMDUGDCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczov
# L3NlY3RpZ28uY29tL0NQUzAIBgZngQwBBAEwSQYDVR0fBEIwQDA+oDygOoY4aHR0
# cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIz
# Ni5jcmwweQYIKwYBBQUHAQEEbTBrMEQGCCsGAQUFBzAChjhodHRwOi8vY3J0LnNl
# Y3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ0NBUjM2LmNydDAjBggr
# BgEFBQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJKoZIhvcNAQEMBQAD
# ggGBADWsCefpJbT4oKIh1SXIR4hnfGN/YqI6g3aFIntNgoiurd5uxLkow2WSpfaC
# iXjWjMubDpBvynVvaBsRfcrwT0WGBTwz4miuITPKKkIXYIVb5dCf33ghMJ4UD+zH
# P73ioUtDV545Yeb5WVLrPN8ZCC++u0kX+jck30w56LCvng6gDpPHU/KNroQxENjG
# pcycTf7Gq9tkcEVbGersD6R64NhI6r8uDH6l0s5NMep1x4yTs0MBPmlB6ZHK+88Y
# GDVdfTSYbLpQuvmLkEMHNaPOL0YyTjJJbeaHvGuTfqQb17HDLFJGd70CKrQumvcI
# CrJM9il3B+bNsSKjTLQtGbp5JdJ5paUahybiJKyZlDw5QPRrFEnwHiWaTI/9zfRc
# iZyX4kYnLkPpp8rlZFXBqfIzf0gRhgCjVVZoMwZHWqyZNL7gS4C6uvEn2t/3BqM7
# 548OuoPLH7W07orz8T7iP7aNYtJpAttZOhAbqB2EKoY3qcKClqKOMQGvc12/16mA
# KE7E+TCCBhQwggP8oAMCAQICEHojrtpTaZYPkcg+XPTH4z8wDQYJKoZIhvcNAQEM
# BQAwVzELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEuMCwG
# A1UEAxMlU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBSb290IFI0NjAeFw0y
# MTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5NTlaMFUxCzAJBgNVBAYTAkdCMRgwFgYD
# VQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28gUHVibGljIFRp
# bWUgU3RhbXBpbmcgQ0EgUjM2MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKC
# AYEAzZjYQ0GrboIr7PYzfiY05ImM0+8iEoBUPu8mr4wOgYPjoiIz5vzf7d5wu8GF
# K1JWN5hciN9rdqOhbdxLcSVwnOTJmUGfAMQm4eXOls3iQwfapEFWuOsYmBKXPNSp
# wZAFoLGl5y1EaGGc5LByM8wjcbSF52/Z42YaJRsPXY545E3QAPN2mxDh0OLozhiG
# gYT1xtjXVfEzYBVmfQaI5QL35cTTAjsJAp85R+KAsOfuL9Z7LFnjdcuPkZWjssME
# TFIueH69rxbFOUD64G+rUo7xFIdRAuDNvWBsv0iGDPGaR2nZlY24tz5fISYk1sPY
# 4gir99aXAGnoo0vX3Okew4MsiyBn5ZnUDMKzUcQrpVavGacrIkmDYu/bcOUR1mVB
# IZ0X7P4bKf38JF7Mp7tY3LFF/h7hvBS2tgTYXlD7TnIMPrxyXCfB5yQq3FFoXRXM
# 3/DvqQ4shoVWF/mwwz9xoRku05iphp22fTfjKRIVpm4gFT24JKspEpM8mFa9eTgK
# WWCvAgMBAAGjggFcMIIBWDAfBgNVHSMEGDAWgBT2d2rdP/0BE/8WoWyCAi/QCj0U
# JTAdBgNVHQ4EFgQUX1jtTDF6omFCjVKAurNhlxmiMpswDgYDVR0PAQH/BAQDAgGG
# MBIGA1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0g
# BAowCDAGBgRVHSAAMEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9jcmwuc2VjdGln
# by5jb20vU2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ1Jvb3RSNDYuY3JsMHwGCCsG
# AQUFBwEBBHAwbjBHBggrBgEFBQcwAoY7aHR0cDovL2NydC5zZWN0aWdvLmNvbS9T
# ZWN0aWdvUHVibGljVGltZVN0YW1waW5nUm9vdFI0Ni5wN2MwIwYIKwYBBQUHMAGG
# F2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqGSIb3DQEBDAUAA4ICAQAS13sg
# rQ41WAyegR0lWP1MLWd0r8diJiH2VVRpxqFGhnZbaF+IQ7JATGceTWOS+kgnMAzG
# YRzpm8jIcjlSQ8JtcqymKhgx1s6cFZBSfvfeoyigF8iCGlH+SVSo3HHr98NepjSF
# JTU5KSRKK+3nVSWYkSVQgJlgGh3MPcz9IWN4I/n1qfDGzqHCPWZ+/Mb5vVyhgaeq
# xLPbBIqv6cM74Nvyo1xNsllECJJrOvsrJQkajVz4xJwZ8blAdX5umzwFfk7K/0K3
# fpjgiXpqNOpXaJ+KSRW0HdE0FSDC7+ZKJJSJx78mn+rwEyT+A3z7Ss0gT5CpTrcm
# hUwIw9jbvnYuYRKxFVWjKklW3z83epDVzoWJttxFpujdrNmRwh1YZVIB2guAAjEQ
# oF42H0BA7WBCueHVMDyV1e4nM9K4As7PVSNvQ8LI1WRaTuGSFUd9y8F8jw22BZC6
# mJoB40d7SlZIYfaildlgpgbgtu6SDsek2L8qomG57Yp5qTqof0DwJ4Q4HsShvRl/
# 59T4IJBovRwmqWafH0cIPEX7cEttS5+tXrgRtMjjTOp6A9l0D6xcKZtxnLqiTH9K
# PCy6xZEi0UDcMTww5Fl4VvoGbMG2oonuX3f1tsoHLaO/Fwkj3xVr3lDkmeUqiveb
# QTvGkx5hGuJaSVQ+x60xJ/Y29RBr8Tm9XJ59AjCCBhowggQCoAMCAQICEGIdbQxS
# AZ47kHkVIIkhHAowDQYJKoZIhvcNAQEMBQAwVjELMAkGA1UEBhMCR0IxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEtMCsGA1UEAxMkU2VjdGlnbyBQdWJsaWMgQ29k
# ZSBTaWduaW5nIFJvb3QgUjQ2MB4XDTIxMDMyMjAwMDAwMFoXDTM2MDMyMTIzNTk1
# OVowVDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDErMCkG
# A1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNjCCAaIwDQYJ
# KoZIhvcNAQEBBQADggGPADCCAYoCggGBAJsrnVP6NT+OYAZDasDP9X/2yFNTGMjO
# 02x+/FgHlRd5ZTMLER4ARkZsQ3hAyAKwktlQqFZOGP/I+rLSJJmFeRno+DYDY1UO
# AWKA4xjMHY4qF2p9YZWhhbeFpPb09JNqFiTCYy/Rv/zedt4QJuIxeFI61tqb7/fo
# XT1/LW2wHyN79FXSYiTxcv+18Irpw+5gcTbXnDOsrSHVJYdPE9s+5iRF2Q/TlnCZ
# GZOcA7n9qudjzeN43OE/TpKF2dGq1mVXn37zK/4oiETkgsyqA5lgAQ0c1f1IkOb6
# rGnhWqkHcxX+HnfKXjVodTmmV52L2UIFsf0l4iQ0UgKJUc2RGarhOnG3B++OxR53
# LPys3J9AnL9o6zlviz5pzsgfrQH4lrtNUz4Qq/Va5MbBwuahTcWk4UxuY+PynPjg
# w9nV/35gRAhC3L81B3/bIaBb659+Vxn9kT2jUztrkmep/aLb+4xJbKZHyvahAEx2
# XKHafkeKtjiMqcUf/2BG935A591GsllvWwIDAQABo4IBZDCCAWAwHwYDVR0jBBgw
# FoAUMuuSmv81lkgvKEBCcCA2kVwXheYwHQYDVR0OBBYEFA8qyyCHKLjsb0iuK1Sm
# KaoXpM0MMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYEVR0gADAIBgZngQwBBAEwSwYD
# VR0fBEQwQjBAoD6gPIY6aHR0cDovL2NybC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVi
# bGljQ29kZVNpZ25pbmdSb290UjQ2LmNybDB7BggrBgEFBQcBAQRvMG0wRgYIKwYB
# BQUHMAKGOmh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVT
# aWduaW5nUm9vdFI0Ni5wN2MwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3Rp
# Z28uY29tMA0GCSqGSIb3DQEBDAUAA4ICAQAG/4Lhd2M2bnuhFSCbE/8E/ph1RGHD
# VpVx0ZE/haHrQECxyNbgcv2FymQ5PPmNS6Dah66dtgCjBsULYAor5wxxcgEPRl05
# pZOzI3IEGwwsepp+8iGsLKaVpL3z5CmgELIqmk/Q5zFgR1TSGmxqoEEhk60FqONz
# Dn7D8p4W89h8sX+V1imaUb693TGqWp3T32IKGfIgy9jkd7GM7YCa2xulWfQ6E1xZ
# tYNEX/ewGnp9ZeHPsNwwviJMBZL4xVd40uPWUnOJUoSiugaz0yWLODRtQxs5qU6E
# 58KKmfHwJotl5WZ7nIQuDT0mWjwEx7zSM7fs9Tx6N+Q/3+49qTtUvAQsrEAxwmzO
# TJ6Jp6uWmHCgrHW4dHM3ITpvG5Ipy62KyqYovk5O6cC+040Si15KJpuQ9VJnbPvq
# YqfMB9nEKX/d2rd1Q3DiuDexMKCCQdJGpOqUsxLuCOuFOoGbO7Uv3RjUpY39jkkp
# 0a+yls6tN85fJe+Y8voTnbPU1knpy24wUFBkfenBa+pRFHwCBB1QtS+vGNRhsceP
# 3kSPNrrfN2sRzFYsNfrFaWz8YOdU254qNZQfd9O/VjxZ2Gjr3xgANHtM3HxfzPYF
# 6/pKK8EE4dj66qKKtm2DTL1KFCg/OYJyfrdLJq1q2/HXntgr2GVw+ZWhrWgMTn8v
# 1SjZsLlrgIfZHDCCBl0wggTFoAMCAQICEDpSaiyEzlXmHWX8zBLY6YkwDQYJKoZI
# hvcNAQEMBQAwVTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRl
# ZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBDQSBSMzYw
# HhcNMjQwMTE1MDAwMDAwWhcNMzUwNDE0MjM1OTU5WjBuMQswCQYDVQQGEwJHQjET
# MBEGA1UECBMKTWFuY2hlc3RlcjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTAw
# LgYDVQQDEydTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFNpZ25lciBSMzUw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCN0Wf0wUibvf04STpNYYGb
# w9jcRaVhBDaNBp7jmJaA9dQZW5ighrXGNMYjK7Dey5RIHMqLIbT9z9if753mYboj
# JrKWO4ZP0N5dBT2TwZZaPb8E+hqaDZ8Vy2c+x1NiEwbEzTrPX4W3QFq/zJvDDbWK
# L99qLL42GJQzX3n5wWo60KklfFn+Wb22mOZWYSqkCVGl8aYuE12SqIS4MVO4PUax
# XeO+4+48YpQlNqbc/ndTgszRQLF4MjxDPjRDD1M9qvpLTZcTGVzxfViyIToRNxPP
# 6DUiZDU6oXARrGwyP9aglPXwYbkqI2dLuf9fiIzBugCDciOly8TPDgBkJmjAfILN
# iGcVEzg+40xUdhxNcaC+6r0juPiR7bzXHh7v/3RnlZuT3ZGstxLfmE7fRMAFwbHd
# Dz5gtHLqjSTXDiNF58IxPtvmZPG2rlc+Yq+2B8+5pY+QZn+1vEifI0MDtiA6BxxQ
# uOnj4PnqDaK7NEKwtD1pzoA3jJFuoJiwbatwhDkg1PIjYnMDbDW+wAc9FtRN6pUs
# O405jaBgigoFZCw9hWjLNqgFVTo7lMb5rVjJ9aSBVVL2dcqzyFW2LdWk5Xdp65oe
# eOALod7YIIMv1pbqC15R7QCYLxcK1bCl4/HpBbdE5mjy9JR70BHuYx27n4XNOZbw
# rXcG3wZf9gEUk7stbPAoBQIDAQABo4IBjjCCAYowHwYDVR0jBBgwFoAUX1jtTDF6
# omFCjVKAurNhlxmiMpswHQYDVR0OBBYEFGjvpDJJabZSOB3qQzks9BRqngyFMA4G
# A1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMEoGA1UdIARDMEEwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYBBQUHAgEWF2h0
# dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAEEAjBKBgNVHR8EQzBBMD+gPaA7
# hjlodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3RhbXBp
# bmdDQVIzNi5jcmwwegYIKwYBBQUHAQEEbjBsMEUGCCsGAQUFBzAChjlodHRwOi8v
# Y3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3RhbXBpbmdDQVIzNi5j
# cnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqGSIb3
# DQEBDAUAA4IBgQCw3C7J+k82TIov9slP1e8YTx+fDsa//hJ62Y6SMr2E89rv82y/
# n8we5W6z5pfBEWozlW7nWp+sdPCdUTFw/YQcqvshH6b9Rvs9qZp5Z+V7nHwPTH8y
# zKwgKzTTG1I1XEXLAK9fHnmXpaDeVeI8K6Lw3iznWZdLQe3zl+Rejdq5l2jU7iUf
# MkthfhFmi+VVYPkR/BXpV7Ub1QyyWebqkjSHJHRmv3lBYbQyk08/S7TlIeOr9iQ+
# UN57fJg4QI0yqdn6PyiehS1nSgLwKRs46T8A6hXiSn/pCXaASnds0LsM5OVoKYfb
# gOOlWCvKfwUySWoSgrhncihSBXxH2pAuDV2vr8GOCEaePZc0Dy6O1rYnKjGmqm/I
# RNkJghSMizr1iIOPN+23futBXAhmx8Ji/4NTmyH9K0UvXHiuA2Pa3wZxxR9r9XeI
# UVb2V8glZay+2ULlc445CzCvVSZV01ZB6bgvCuUuBx079gCcepjnZDCcEuIC5Se4
# F6yFaZ8RvmiJ4hgwggaCMIIEaqADAgECAhA2wrC9fBs656Oz3TbLyXVoMA0GCSqG
# SIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEU
# MBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0
# d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhv
# cml0eTAeFw0yMTAzMjIwMDAwMDBaFw0zODAxMTgyMzU5NTlaMFcxCzAJBgNVBAYT
# AkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28g
# UHVibGljIFRpbWUgU3RhbXBpbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCIndi5RWedHd3ouSaBmlRUwHxJBZvMWhUP2ZQQRLRBQIF3
# FJmp1OR2LMgIU14g0JIlL6VXWKmdbmKGRDILRxEtZdQnOh2qmcxGzjqemIk8et8s
# E6J+N+Gl1cnZocew8eCAawKLu4TRrCoqCAT8uRjDeypoGJrruH/drCio28aqIVEn
# 45NZiZQI7YYBex48eL78lQ0BrHeSmqy1uXe9xN04aG0pKG9ki+PC6VEfzutu6Q3I
# cZZfm00r9YAEp/4aeiLhyaKxLuhKKaAdQjRaf/h6U13jQEV1JnUTCm511n5avv4N
# +jSVwd+Wb8UMOs4netapq5Q/yGyiQOgjsP/JRUj0MAT9YrcmXcLgsrAimfWY3MzK
# m1HCxcquinTqbs1Q0d2VMMQyi9cAgMYC9jKc+3mW62/yVl4jnDcw6ULJsBkOkrcP
# LUwqj7poS0T2+2JMzPP+jZ1h90/QpZnBkhdtixMiWDVgh60KmLmzXiqJc6lGwqoU
# qpq/1HVHm+Pc2B6+wCy/GwCcjw5rmzajLbmqGygEgaj/OLoanEWP6Y52Hflef3XL
# vYnhEY4kSirMQhtberRvaI+5YsD3XVxHGBjlIli5u+NrLedIxsE88WzKXqZjj9Zi
# 5ybJL2WjeXuOTbswB7XjkZbErg7ebeAQUQiS/uRGZ58NHs57ZPUfECcgJC+v2wID
# AQABo4IBFjCCARIwHwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYD
# VR0OBBYEFPZ3at0//QET/xahbIICL9AKPRQlMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYE
# VR0gADBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20v
# VVNFUlRydXN0UlNBQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwNQYIKwYBBQUH
# AQEEKTAnMCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0G
# CSqGSIb3DQEBDAUAA4ICAQAOvmVB7WhEuOWhxdQRh+S3OyWM637ayBeR7djxQ8Si
# hTnLf2sABFoB0DFR6JfWS0snf6WDG2gtCGflwVvcYXZJJlFfym1Doi+4PfDP8s0c
# qlDmdfyGOwMtGGzJ4iImyaz3IBae91g50QyrVbrUoT0mUGQHbRcF57olpfHhQESt
# z5i6hJvVLFV/ueQ21SM99zG4W2tB1ExGL98idX8ChsTwbD/zIExAopoe3l6JrzJt
# Pxj8V9rocAnLP2C8Q5wXVVZcbw4x4ztXLsGzqZIiRh5i111TW7HV1AtsQa6vXy63
# 3vCAbAOIaKcLAo/IU7sClyZUk62XD0VUnHD+YvVNvIGezjM6CRpcWed/ODiptK+e
# vDKPU2K6synimYBaNH49v9Ih24+eYXNtI38byt5kIvh+8aW88WThRpv8lUJKaPn3
# 7+YHYafob9Rg7LyTrSYpyZoBmwRWSE4W6iPjB7wJjJpH29308ZkpKKdpkiS9WNsf
# /eeUtvRrtIEiSJHN899L1P4l6zKVsdrUu1FX1T/ubSrsxrYJD+3f3aKg6yxdbugo
# t06YwGXXiy5UUGZvOu3lXlxA+fC13dQ5OlL2gIb5lmF6Ii8+CQOYDwXM+yd9dbmo
# cQsHjcRPsccUd5E9FiswEqORvz8g3s+jR3SFCgXhN4wz7NgAnOgpCdUo4uDyllU9
# PzGCBcEwggW9AgEBMGkwVDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28g
# TGltaXRlZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENB
# IFIzNgIRAKTyyhFVIszKg3iR7KhQQaIwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYB
# BAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAc
# BgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgZIZT
# UJEmd4BPafewUkWq3pSv0lV4INfU3LKro5X1sq0wDQYJKoZIhvcNAQEBBQAEggGA
# FE3AtfxmB7ODZKglFG4VjmXzb5oywAf75WRPYMZR7hkeE6Spi+b4l1AkFT0T1OoE
# /VCL65Yp+P9WqtI65w+GgDe/R7u6b9zh+TepHKRF+TjdZKFNQcPj53nOXduQCABu
# cKMoIX1SkrPLDRPGfU/d/PLbiJ4GMmoc3xJpSaWS8XAaUiIF3LhQWctB7Q8iIqbQ
# blPYtOBwbuPgCNJDL7aZWbo7BfbJWtdE6rehT0BjCMrUA3CCsSYEd6iydnL5nYiS
# aS5jw1bCCOnFZTDo/9BrFOC85syjM8xOJg5xFpf+sbH+UBCHJBaaMkwADxbenf8L
# 6yQ9tYRlfL6JxPvgBFbolaB1xe4hUkE9BQNSrJlrWGI2VJhq59gLJikGIpToQPdj
# XK2ZfDjRRh24SwhoL5AfsnsZzcYkNJKLbOXRYgKcy4xjsJ8wtzMaH8toTaHQJLK2
# Ins5jHJM3b/jXyeE/nsJBXD2qJy/N3ZKSMtu3XCokhFqyC2I/DUmKAasBs5hOJhx
# oYIDIjCCAx4GCSqGSIb3DQEJBjGCAw8wggMLAgEBMGkwVTELMAkGA1UEBhMCR0Ix
# GDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJs
# aWMgVGltZSBTdGFtcGluZyBDQSBSMzYCEDpSaiyEzlXmHWX8zBLY6YkwDQYJYIZI
# AWUDBAICBQCgeTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNDA4MTUxNjM1MjRaMD8GCSqGSIb3DQEJBDEyBDBDmK21Bx8fQNXnkFTl
# WJL3lERbHnLTM1yYUUs/bl6MzbA5DdlGhTX2UDtfo+Azo1cwDQYJKoZIhvcNAQEB
# BQAEggIAFoww1H8wEf+0+R+wJeWzCEgNaabM60NGXGhKCGrkUSjHDPwDSHd3hRnF
# 5cSOLYl9oLuQGsLBzG0753iN3qJVDo5FK9NNs2GJX85Y3M+UjYO/C2K6I3ybgGon
# Ue3YCSD6bsnUaO5HDPKUDyIPpqeMintk3u7hzLBRqR12lWa28GqkNDLcXmKl/Jl/
# opKmIxkFhn2FMK3fcgx6LBBggcewTYf48UFfyR3ONSCsQh3CzxKvuRI5/xu1pzSq
# CvHtbO43fduGry4XSjOTSXoIochzKpQCUmKj+Db3HW07Y7RbxLj7RnCkeMTI4DHN
# dGUOy681Ya+I+EI6RIld1c1RicTmJ/r3+pKpaiBmGOtDQk6vyrE077Kr0knEg3de
# 7Gr6HUErDfCiztnI8NdmOOat442pP1sFA1wTq07CNM9uNowawdVjZIOBke99E5me
# h9Cv7YpSUbXJ3QPZbvgxbHYdC7ZekWTPc8W/dOrO7AtuxMHSdWynmp1s3hoelsBZ
# DzbYvKkb0wmK+LRXwffN3JFZF6WE4B4QxWgD3/sfV7DAjKWn4wAVq90WIepTaIPv
# tsZxRdpe1Z8n/sno3+wqWPv8KQXFTaTTB4m3FjMfaTxuZjusXrCpmbZTRISbf/D0
# PSew7CwnztVfqiLhEWY6i+t+zsZgLPCCPfJdRDi+PmSyOumwX0M=
# SIG # End signature block