AnyPackageDsc.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 namespace AnyPackage.Provider

enum Ensure {
    Absent
    Present
}

class APReason {
    [string] $Code

    [string] $Phrase
}

[DscResource()]
class Package {
    [DscProperty(Key)]
    [string] $Name

    [DscProperty(Key)]
    [string] $Version

    [DscProperty(Key)]
    [string] $Provider

    [DscProperty()]
    [bool] $Prerelease

    [DscProperty()]
    [string] $Source

    [DscProperty()]
    [hashtable] $AdditionalParameters = @{ }

    [DscProperty()]
    [bool] $Latest

    [DscProperty()]
    [Ensure] $Ensure = [Ensure]::Present

    [DscProperty(NotConfigurable)]
    [APReason[]] $Reasons

    static [Package[]] Export() {
        Get-PackageProvider -ListAvailable |
        Select-Object -ExpandProperty ModuleName -Unique |
        Import-Module

        $packages = @()
        foreach ($installedPackage in (Get-Package -ErrorAction Stop)) {
            $package = [Package]::new()
            $package.Name = $installedPackage.Name
            $package.Provider = $installedPackage.Provider.FullName

            if ($installedPackage.Version) {
                $package.Version = $installedPackage.Version
                $package.Prerelease = $installedPackage.Version.IsPrerelease
            } else {
                $package.Version = '*'
            }

            $package.Source = $installedPackage.Source
            $packages += $package
        }

        return $packages
    }

    [Package] Get() {
        $currentState = [Package]@{
            Name     = $this.Name
            Provider = $this.Provider
        }

        $params = @{
            Name        = $this.Name
            Provider    = $this.Provider
            ErrorAction = 'Stop'
        }

        if ($this.Version -ne '*') {
            $params['Version'] = $this.Version
        }

        if (-not $this.Provider.Contains('\')) {
            throw "Not a valid Provider full name: '$($this.Provider)'"
        }

        try {
            Import-Module ($this.Provider).Split('\')[0] -ErrorAction Stop

            $package = Get-Package @params |
                Sort-Object -Property Version -Descending |
                Select-Object -First 1

            $currentState.Ensure = [Ensure]::Present
            $currentState.Source = $package.Source
            $currentState.Version = $package.Version
            $currentState.Prerelease = $package.Version.IsPrerelease
        } catch [PackageNotFoundException] {
            $currentState.Ensure = [Ensure]::Absent
        }

        if ($this.Ensure -ne $currentState.Ensure) {
            $currentState.Reasons += [APReason]@{
                Code   = 'Package:Package:Ensure'
                Phrase = "Package '$($this.Name)' should be '$($this.Ensure)' but was '$($currentState.Ensure)'."
            }
        }

        if ($this.Source -and $this.Source -ne $currentState.Source) {
            $currentState.Reasons += [APReason]@{
                Code   = 'Package:Package:Source'
                Phrase = "Source should be '$($this.Source)' but was '$($currentState.Source)'."
            }
        }

        if (-not $this.Prerelease -and $this.Prerelease -ne $currentState.Prerelease) {
            $currentState.Reasons += [APReason]@{
                Code   = 'Package:Package:Prerelease'
                Phrase = 'Prerelease versions not allowed.'
            }
        }

        if ($this.Latest) {
            if ($this.Source) { $params['Source'] = $this.Source }
            $params['Prerelease'] = $this.Prerelease

            $latestPackage = Find-Package @params |
                Sort-Object -Property Version -Descending |
                Select-Object -First 1

            if ([PackageVersion]$currentState.Version -lt $latestPackage.Version) {
                $currentState.Reasons += [APReason]@{
                    Code   = 'Package:Package:Latest'
                    Phrase = "Version should be '$($latestPackage.Version)' but was '$($currentState.Version)'."
                }
            }
        }

        return $currentState
    }

    [void] Set() {
        if ($this.Test()) { return }

        $params = @{
            Name        = $this.Name
            Provider    = $this.Provider
            ErrorAction = 'Stop'
        }

        if ($this.Version -ne '*') {
            $params['Version'] = $this.Version
        }

        $currentState = $this.Get()

        if ($this.Ensure -eq [Ensure]::Present) {
            if ($this.Source) { $params['Source'] = $this.Source }

            if ($this.Latest) {
                $params['Version'] = Find-Package @params |
                    Sort-Object -Property Version -Descending |
                    Select-Object -ExpandProperty Version -First 1
            }

            # Can't splat using property
            $additionalParams = $this.AdditionalParameters

            if ($currentState.Ensure -eq [Ensure]::Present -and
                (Get-PackageProvider -Name $this.Provider).Operations.HasFlag([PackageProviderOperations]::Update)) {
                Update-Package @params @additionalParams
            } else {
                Install-Package @params @additionalParams
            }

        } elseif ($currentState.Ensure -eq [Ensure]::Present) {
            Uninstall-Package @params
        }
    }

    [bool] Test() {
        return $this.Get().Reasons.Count -eq 0
    }
}

[DscResource()]
class Source {
    [DscProperty(Key)]
    [string] $Name

    [DscProperty(Key)]
    [string] $Provider

    [DscProperty()]
    [string] $Location

    [DscProperty()]
    [bool] $Trusted

    [DscProperty()]
    [hashtable] $AdditionalParameters = @{ }

    [DscProperty()]
    [Ensure] $Ensure = [Ensure]::Present

    [DscProperty(NotConfigurable)]
    [APReason[]] $Reasons

    static [Source[]] Export() {
        Get-PackageProvider -ListAvailable |
        Select-Object -ExpandProperty ModuleName -Unique |
        Import-Module

        $sources = @()
        foreach ($installedSource in (Get-PackageSource -ErrorAction Stop)) {
            $source = [Source]::new()
            $source.Name = $installedSource.Name
            $source.Provider = $installedSource.Provider.FullName
            $source.Location = $installedSource.Location
            $source.Trusted = $installedSource.Trusted
            $sources += $source
        }

        return $sources
    }

    [Source] Get() {
        $currentState = [Source]@{
            Name     = $this.Name
            Provider = $this.Provider
        }

        $params = @{
            Name        = $this.Name
            Provider    = $this.Provider
            ErrorAction = 'Stop'
        }

        if (-not $this.Provider.Contains('\')) {
            throw "Not a valid Provider full name: '$($this.Provider)'"
        }

        try {
            Import-Module ($this.Provider).Split('\')[0] -ErrorAction Stop

            $source = Get-PackageSource @params

            $currentState.Ensure = [Ensure]::Present
            $currentState.Location = $source.Location
            $currentState.Trusted = $source.Trusted
        } catch [PackageSourceNotFoundException] {
            $currentState.Ensure = [Ensure]::Absent
        }

        if ($this.Ensure -ne $currentState.Ensure) {
            $currentState.Reasons += [APReason]@{
                Code   = 'Source:Source:Ensure'
                Phrase = "Package source '$($this.Name)' should be '$($this.Ensure)' but was '$($currentState.Ensure)'."
            }
        }

        if ($this.Location -and $this.Location -ne $currentState.Location) {
            $currentState.Reasons += [APReason]@{
                Code   = 'Source:Source:Location'
                Phrase = "Location should be '$($this.Location)' but was '$($currentState.Location)'."
            }
        }

        if ($this.Trusted -ne $currentState.Trusted) {
            $currentState.Reasons += [APReason]@{
                Code   = 'Source:Source:Trusted'
                Phrase = "Trusted should be '$($this.Trusted)' but was '$($currentState.Trusted)'."
            }
        }

        return $currentState
    }

    [void] Set() {
        if ($this.Test()) { return }

        $params = @{
            Name        = $this.Name
            Provider    = $this.Provider
            ErrorAction = 'Stop'
        }

        $currentState = $this.Get()

        if ($this.Ensure -eq [Ensure]::Present) {
            $params['Location'] = $this.Location
            $params['Trusted'] = $this.Trusted

            # Can't splat using property
            $additionalParams = $this.AdditionalParameters

            if ($currentState.Ensure -eq [Ensure]::Present) {
                Set-PackageSource @params @additionalParams
            } else {
                Register-PackageSource @params @additionalParams
            }

        } elseif ($currentState.Ensure -eq [Ensure]::Present) {
            Unregister-PackageSource @params
        }
    }

    [bool] Test() {
        return $this.Get().Reasons.Count -eq 0
    }
}

# SIG # Begin signature block
# MIIlSAYJKoZIhvcNAQcCoIIlOTCCJTUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAe0shp3YOi1CS5
# +Ri4LpQxS/OKHGST1BTv+/VqdooHVKCCHtwwggW2MIIEHqADAgECAhEApPLKEVUi
# 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
# 1SjZsLlrgIfZHDCCBmIwggTKoAMCAQICEQCkKTtuHt3XpzQIh616TrckMA0GCSqG
# SIb3DQEBDAUAMFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0
# ZWQxLDAqBgNVBAMTI1NlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjM2
# MB4XDTI1MDMyNzAwMDAwMFoXDTM2MDMyMTIzNTk1OVowcjELMAkGA1UEBhMCR0Ix
# FzAVBgNVBAgTDldlc3QgWW9ya3NoaXJlMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0
# ZWQxMDAuBgNVBAMTJ1NlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBpbmcgU2lnbmVy
# IFIzNjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANOElfRupFN48j0Q
# S3gSBzzclIFTZ2Gsn7BjsmBF659/kpA2Ey7NXK3MP6JdrMBNU8wdmkf+SSIyjX++
# UAYWtg3Y/uDRDyg8RxHeHRJ+0U1jHEyH5uPdk1ttiPC3x/gOxIc9P7Gn3OgW7DQc
# 4x07exZ4DX4XyaGDq5LoEmk/BdCM1IelVMKB3WA6YpZ/XYdJ9JueOXeQObSQ/doh
# QCGyh0FhmwkDWKZaqQBWrBwZ++zqlt+z/QYTgEnZo6dyIo2IhXXANFkCHutL8765
# NBxvolXMFWY8/reTnFxk3MajgM5NX6wzWdWsPJxYRhLxtJLSUJJ5yWRNw+NBqH1e
# zvFs4GgJ2ZqFJ+Dwqbx9+rw+F2gBdgo4j7CVomP49sS7CbqsdybbiOGpB9DJhs5Q
# VMpYV73TVV3IwLiBHBECrTgUfZVOMF0KSEq2zk/LsfvehswavE3W4aBXJmGjgWSp
# cDz+6TqeTM8f1DIcgQPdz0IYgnT3yFTgiDbFGOFNt6eCidxdR6j9x+kpcN5RwApy
# 4pRhE10YOV/xafBvKpRuWPjOPWRBlKdm53kS2aMh08spx7xSEqXn4QQldCnUWRz3
# Lki+TgBlpwYwJUbR77DAayNwAANE7taBrz2v+MnnogMrvvct0iwvfIA1W8kp155L
# o44SIfqGmrbJP6Mn+Udr3MR2oWozAgMBAAGjggGOMIIBijAfBgNVHSMEGDAWgBRf
# WO1MMXqiYUKNUoC6s2GXGaIymzAdBgNVHQ4EFgQUiGGMoSo3ZIEoYKGbMdCM/SwC
# zk8wDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYI
# KwYBBQUHAwgwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIBAwgwJTAjBggrBgEFBQcC
# ARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQQCMEoGA1UdHwRDMEEw
# P6A9oDuGOWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY1RpbWVT
# dGFtcGluZ0NBUjM2LmNybDB6BggrBgEFBQcBAQRuMGwwRQYIKwYBBQUHMAKGOWh0
# dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ0NB
# UjM2LmNydDAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5jb20wDQYJ
# KoZIhvcNAQEMBQADggGBAAKBPqSGclEh+WWpLj1SiuHlm8xLE0SThI2yLuq+75s1
# 1y6SceBchpnKpxWaGtXc8dya1Aq3RuW//y3wMThsvT4fSba2AoSWlR67rA4fTYGM
# Ihgzocsids0ct/pHaocLVJSwnTYxY2pE0hPoZAvRebctbsTqENmZHyOVjOFlwN2R
# 3DRweFeNs4uyZN5LRJ5EnVYlcTOq3bl1tI5poru9WaQRWQ4eynXp7Pj0Fz4DKr86
# HYECRJMWiDjeV0QqAcQMFsIjJtrYTw7mU81qf4FBc4u4swphLeKRNyn9DDrd3HIM
# J+CpdhSHEGleeZ5I79YDg3B3A/fmVY2GaMik1Vm+FajEMv4/EN2mmHf4zkOuhYZN
# zVm4NrWJeY4UAriLBOeVYODdA1GxFr1ycbcUEGlUecc4RCPgYySs4d00NNuicR4a
# 9n7idJlevAJbha/arIYMEuUqTeRRbWkhJwMKmb9yEvppRudKyu1t6l21sIuIZqcp
# VH8oLWCxHS0LpDRF9Y4jijCCBoIwggRqoAMCAQICEDbCsL18Gzrno7PdNsvJdWgw
# DQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVy
# c2V5MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVT
# VCBOZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24g
# QXV0aG9yaXR5MB4XDTIxMDMyMjAwMDAwMFoXDTM4MDExODIzNTk1OVowVzELMAkG
# A1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEuMCwGA1UEAxMlU2Vj
# dGlnbyBQdWJsaWMgVGltZSBTdGFtcGluZyBSb290IFI0NjCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAIid2LlFZ50d3ei5JoGaVFTAfEkFm8xaFQ/ZlBBE
# tEFAgXcUmanU5HYsyAhTXiDQkiUvpVdYqZ1uYoZEMgtHES1l1Cc6HaqZzEbOOp6Y
# iTx63ywTon434aXVydmhx7Dx4IBrAou7hNGsKioIBPy5GMN7KmgYmuu4f92sKKjb
# xqohUSfjk1mJlAjthgF7Hjx4vvyVDQGsd5KarLW5d73E3ThobSkob2SL48LpUR/O
# 627pDchxll+bTSv1gASn/hp6IuHJorEu6EopoB1CNFp/+HpTXeNARXUmdRMKbnXW
# flq+/g36NJXB35ZvxQw6zid61qmrlD/IbKJA6COw/8lFSPQwBP1ityZdwuCysCKZ
# 9ZjczMqbUcLFyq6KdOpuzVDR3ZUwxDKL1wCAxgL2Mpz7eZbrb/JWXiOcNzDpQsmw
# GQ6Stw8tTCqPumhLRPb7YkzM8/6NnWH3T9ClmcGSF22LEyJYNWCHrQqYubNeKolz
# qUbCqhSqmr/UdUeb49zYHr7ALL8bAJyPDmubNqMtuaobKASBqP84uhqcRY/pjnYd
# +V5/dcu9ieERjiRKKsxCG1t6tG9oj7liwPddXEcYGOUiWLm742st50jGwTzxbMpe
# pmOP1mLnJskvZaN5e45NuzAHteORlsSuDt5t4BBRCJL+5EZnnw0ezntk9R8QJyAk
# L6/bAgMBAAGjggEWMIIBEjAfBgNVHSMEGDAWgBRTeb9aqitKz1SA4dibwJ3ysgNm
# yzAdBgNVHQ4EFgQU9ndq3T/9ARP/FqFsggIv0Ao9FCUwDgYDVR0PAQH/BAQDAgGG
# MA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAow
# CDAGBgRVHSAAMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0
# LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDA1Bggr
# BgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5j
# b20wDQYJKoZIhvcNAQEMBQADggIBAA6+ZUHtaES45aHF1BGH5Lc7JYzrftrIF5Ht
# 2PFDxKKFOct/awAEWgHQMVHol9ZLSyd/pYMbaC0IZ+XBW9xhdkkmUV/KbUOiL7g9
# 8M/yzRyqUOZ1/IY7Ay0YbMniIibJrPcgFp73WDnRDKtVutShPSZQZAdtFwXnuiWl
# 8eFARK3PmLqEm9UsVX+55DbVIz33Mbhba0HUTEYv3yJ1fwKGxPBsP/MgTECimh7e
# XomvMm0/GPxX2uhwCcs/YLxDnBdVVlxvDjHjO1cuwbOpkiJGHmLXXVNbsdXUC2xB
# rq9fLrfe8IBsA4hopwsCj8hTuwKXJlSTrZcPRVSccP5i9U28gZ7OMzoJGlxZ5384
# OKm0r568Mo9TYrqzKeKZgFo0fj2/0iHbj55hc20jfxvK3mQi+H7xpbzxZOFGm/yV
# Qkpo+ffv5gdhp+hv1GDsvJOtJinJmgGbBFZIThbqI+MHvAmMmkfb3fTxmSkop2mS
# JL1Y2x/955S29Gu0gSJIkc3z30vU/iXrMpWx2tS7UVfVP+5tKuzGtgkP7d/doqDr
# LF1u6Ci3TpjAZdeLLlRQZm867eVeXED58LXd1Dk6UvaAhvmWYXoiLz4JA5gPBcz7
# J311uahxCweNxE+xxxR3kT0WKzASo5G/PyDez6NHdIUKBeE3jDPs2ACc6CkJ1Sji
# 4PKWVT0/MYIFwjCCBb4CAQEwaTBUMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2Vj
# dGlnbyBMaW1pdGVkMSswKQYDVQQDEyJTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25p
# bmcgQ0EgUjM2AhEApPLKEVUizMqDeJHsqFBBojANBglghkgBZQMEAgEFAKCBhDAY
# BgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3
# AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEi
# BCBvO68N0NApt6+h0+lhMJ0W/6TZcKD7mbGa788xYrhmkzANBgkqhkiG9w0BAQEF
# AASCAYCz8QNTW2Ti2qRDIS0fJoEebn3p39IBtIs4glL41aSR04d9u0b7ontk5TlD
# 6n+KYkIph0JIYPPMSSCa1kgraEpODwYkLweErD2GRt2HsOg1VKnVAC+07OyYDu8e
# CfTclETvFVOn4ZXuoQKXjvqsMqfYHWfTVAt8pJgtfUXg/jx3FudKh4B6KOvDh4C3
# 8dMCPbUQuC0ktEivCxXG/rnBR/PrBxJsTiwHNgrGK4oykCjNofLr0w3jJY5NuIXw
# uYUiiQjTc4BThW3KAv/aU4LdEbotPd+YdZT4DiYVRa0mqUCNZQWLExFwm1kAfaro
# 5+Xsdq1azwTSPFLKbXovlyon9dNqxl+SjL27NZgk6YURJ0bMoRIEuyJOkAV7n+P3
# qu0kHT608X+1mmZFBvXH/c2R+5DuJZvrVcHLPbp+Wt6TYMGfjAuExLOMork58U7n
# +tygsvOkjlaPLKTR+osOFiWl1CvQ/l4YHOuo/uYBBMMrlFoRMBILTE1agJSOxTNa
# +UMn4KGhggMjMIIDHwYJKoZIhvcNAQkGMYIDEDCCAwwCAQEwajBVMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSwwKgYDVQQDEyNTZWN0aWdv
# IFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIzNgIRAKQpO24e3denNAiHrXpOtyQw
# DQYJYIZIAWUDBAICBQCgeTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqG
# SIb3DQEJBTEPFw0yNTA1MTAwNDU1MTVaMD8GCSqGSIb3DQEJBDEyBDDfAJAgho79
# PsVtnDrbfTxA49qgZmL7i7tWumjFaKYjWg4l3eQ9U79TRll1wXkluCwwDQYJKoZI
# hvcNAQEBBQAEggIAw7f/zsRhzU7aqKcR0OtN8SKnB+4KMqihJHwVuzB+1rfor9tN
# 7UzRnjNADMozNmul8bb2HzY+CzKlgSN8E09e4xnUvUVgFKOQHDFJGkJ+BepRQq7+
# oRwp9u/qMbRJXEoG2kuWPvoNG9rX9xkf2LKaDrDrlfnK/Luf3JI7KeGSdwq9UcND
# F4RAs02BemfxVtgg5jucUS0vKJ6QKkgG9LhkXiYlIajFmF9L2C+KjRWA0YHTkVsi
# jFvULjluY3Cj6HVtU/PyXQbqZ+k+UKWSU8dw4tWvSi2UOcmpapIMEporSQemORPB
# 8HxOravN3lN+Nh2Q4w5JbVhFWqaJI/cMSnmR/YzPF/R0dfM8PxSMfnXPatCk7lsQ
# DBWYhvocV4Z1+dBPw4gRYtOv2eO57DWQqkaKkqC/mUpa8oIZ3y9oqHZZTAJxg+xe
# zbhH7tveedowC+ghN3kQT4oT3fT7KCATDnZDmZ6GOu8WM8ehjaMhvNSj+TD20X9L
# 957Jag7FSNlRw8senw0+rGNJRwIV23eeWb7Yzx5ODttAqC1tRA5irD2g7CdsspeN
# 9gHAu8bsE34QOupT4Q/HvCgTmS+0e7zIpKSHWfYt1tkR0Fq8qGAfgl9jvvcrxNuf
# 8rkodqiBH96DR9v0wE0ToUp7pa1HR8t4EdnUGm9KRHxb5hXLZAr1IvrA2jI=
# SIG # End signature block