Az.Tools.Installer.psm1

# ----------------------------------------------------------------------------------
#
# Copyright Microsoft Corporation
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.internal
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------------
using namespace System
using namespace System.Collections.Generic
using namespace System.IO
using namespace System.IO.Compression
using namespace System.Linq
using namespace System.Management.Automation
using namespace System.Net

using namespace System.Net.Http
using namespace System.Threading
using namespace System.Threading.Tasks

Microsoft.PowerShell.Core\Set-StrictMode -Version 3

$script:AzTempRepoName = 'AzTempRepo'
$script:FixProgressBarId = 1
$script:ParallelDownloaderClassCode = @"
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
 
public class ParallelDownloader
{
    private readonly HttpClientHandler httpClientHandler = new HttpClientHandler();
    private readonly HttpClient client = null;
    private readonly string urlRepository = null;
    private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
 
    private readonly IList<Task> tasks;
    private readonly IList<string> modules;
 
    private string lastModuleName = null;
    private string lastModuleVersion = null;
 
    public string LastModuleName
    {
        get
        {
            return lastModuleName;
        }
    }
 
    public string LastModuleVersion
    {
        get
        {
            return lastModuleVersion;
        }
    }
 
    public ParallelDownloader(string url)
    {
        client = new HttpClient(httpClientHandler);
        this.urlRepository = url;
        tasks = new List<Task>();
        modules = new List<string>();
    }
 
    public ParallelDownloader()
    {
        client = new HttpClient(httpClientHandler);
        tasks = new List<Task>();
        modules = new List<string>();
    }
 
    private async Task DownloadToFile(string uri, string filePath)
    {
        using (var httpResponseMessage = await client.GetAsync(uri, CancellationTokenSource.Token))
        using (var stream = await httpResponseMessage.EnsureSuccessStatusCode().Content.ReadAsStreamAsync())
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await stream.CopyToAsync(fileStream, 81920, CancellationTokenSource.Token);
        }
    }
 
    private void Copy(string src, string dest)
    {
        File.Copy(src, dest);
    }
 
    bool ParseFile(string fileName, out string moduleName, out Version moduleVersion, out Boolean preview)
    {
        try
        {
            Regex pattern = new Regex(@"(?<moduleName>[a-zA-Z.]+)\.(?<moduleVersion>[0-9.]+(\-preview)?)\.nupkg");
            Match matches = pattern.Match(fileName);
            if (matches.Groups["moduleName"].Success && matches.Groups["moduleVersion"].Success)
            {
                moduleName = matches.Groups["moduleName"].Value;
                var versionString = matches.Groups["moduleVersion"].Value;
                if (versionString.Contains('-'))
                {
                    var parts = versionString.Split('-');
                    moduleVersion = Version.Parse(parts[0]);
                    preview = string.Compare(parts[1], "preview", true) == 0;
                }
                else
                {
                    moduleVersion = Version.Parse(versionString);
                    preview = false;
                }
                return true;
            }
        }
        catch
        {
        }
        moduleName = null;
        moduleVersion = null;
        preview = false;
        return false;
    }
 
    public string Download(string sourceUri, string targetPath)
    {
        try
        {
            Uri uri = new Uri(sourceUri);
            var fileName = Path.GetFileName(uri.AbsoluteUri);
            string module = null;
            Version version = null;
            Boolean preview = false;
            if (!ParseFile(fileName, out module, out version, out preview))
            {
                throw new ArgumentException(string.Format("{0} is not a valid Az module nuget package name for installation.", fileName));
            }
            var nupkgFile = preview ? "{0}.{1}-preview.nupkg": "{0}.{1}.nupkg";
            nupkgFile = Path.Combine(targetPath, String.Format(nupkgFile, module, version));
            if (uri.IsFile)
            {
                Copy(uri.AbsolutePath, nupkgFile);
            }
            else if(String.Compare(uri.Scheme, "http", true) == 0 || String.Compare(uri.Scheme, "https", true) == 0)
            {
                Task task = DownloadToFile(uri.AbsoluteUri, nupkgFile);
                tasks.Add(task);
                modules.Add(module);
            }
            else
            {
                throw new ArgumentException(string.Format("{0} scheme is not supported.", sourceUri));
            }
            lastModuleName = module;
            lastModuleVersion = string.Format(preview ? "{0}-preview" : "{0}", version);
            return nupkgFile;
        }
        catch (UriFormatException)
        {
            throw new ArgumentException(string.Format("{0} is not a valid uri.", sourceUri));
        }
    }
 
    public string Download(string module, Version version, string path, Boolean preview = false)
    {
        var nupkgFile = preview ? "{0}.{1}-preview.nupkg" : "{0}.{1}.nupkg";
        nupkgFile = Path.Combine(path, String.Format(nupkgFile, module, version));
        Task task = DownloadToFile(String.Format("{0}/package/{1}/{2}", urlRepository, module, version), nupkgFile);
        tasks.Add(task);
        modules.Add(module);
        lastModuleName = module;
        lastModuleVersion = string.Format(preview ? "{0}-preview" : "{0}", version);
        return nupkgFile;
    }
 
    public void WaitForAllTasks()
    {
        while (tasks.Count() > 0)
        {
            int taskIndex = Task.WaitAny(tasks.ToArray());
            var task = tasks[taskIndex];
            tasks.Remove(task);
            modules.Remove(modules[taskIndex]);
            if (!task.IsCompleted)
            {
                throw new Exception(String.Format("Error downloading {0} {1}", modules[taskIndex], task.Exception));
            }
        }
    }
 
    public void Dispose()
    {
        if (tasks.Count() > 0)
        {
            CancellationTokenSource.Cancel();
            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch
            {
 
            }
        }
 
        if (client != null)
        {
            client.Dispose();
        }
 
        if (httpClientHandler != null)
        {
            httpClientHandler.Dispose();
        }
    }
}
"@


Add-Type -AssemblyName System.Net.Http -ErrorAction Stop
Add-Type $script:ParallelDownloaderClassCode -ReferencedAssemblies System.Net.Http,System.Threading.Tasks,System.Linq,System.Collections,System.Runtime.Extensions,System.Text.RegularExpressions,System.IO.FileSystem

$getModule = Get-Module -Name "PowerShellGet"
if ($null -ne $getModule -and $getModule.Version -lt [System.Version]"2.1.3") {
    Write-Error "This module requires PowerShellGet version 2.1.3. An earlier version of PowerShellGet is imported in the current PowerShell session. Please open a new session before importing this module." -ErrorAction Stop
}
elseif ($null -eq $getModule -or $getModule.Version -ge [System.Version]"3.0") {
    try {
        Import-Module PowerShellGet -MinimumVersion 2.1.3 -MaximumVersion 3.0 -Scope Global -Force -ErrorAction Stop
    }
    catch {
        Write-Error "This module requires PowerShellGet version no earlier than 2.1.3 and no later than 3.0. Please install the required PowerShellGet firstly."
    }
}

function Get-AllAzModule {
    param (
        [Parameter()]
        [Switch]
        ${PrereleaseOnly}
    )

    process {
        $allmodules = Microsoft.PowerShell.Core\Get-Module -ListAvailable -Name Az*, Az `
         | Where-Object {$_.Name -match "Az(\.[a-zA-Z0-9]+)?$"} `
         | Where-Object {
            !$PrereleaseOnly -or ($_.PrivateData -and $_.PrivateData.ContainsKey('PSData') -and $_.PrivateData.PSData.ContainsKey('PreRelease') -and $_.PrivateData.PSData.Prerelease -eq 'preview') -or ($_.Version -lt [Version] "1.0")
        }
        $allmodules
    }
}

function Normalize-ModuleName {
    param (
        [Parameter()]
        [string[]]
        ${Name}
    )

    process {
        $normalName = $Name | ForEach-Object {
            if ($_) {
                if ($_ -notlike "Az.*") {
                    "Az.$_"
                }
                else {
                    $_
                }
            }
        } | Sort-Object -Unique

        if ($normalName -and $normalName -notmatch "Az(\.[a-zA-Z0-9.]+)?$") {
            $normalName = $null
            Throw "The Name parameter must only contain Az modules."
        }

        if ($normalName -eq 'Az.Az') {
            $normalName = $normalName | Where-Object { $_ -ne 'Az.Az'}
            Write-Warning "Az.Tools.Installer cannot be used to install Az. Will discard Az in the Name parameter."
        }

        $normalName
    }
}

function Get-AzModuleFromRemote {
    [OutputType([PSCustomObject[]])]
    param (
        [Parameter()]
        [string[]]
        ${Name},

        [Parameter()]
        [string]
        ${Repository},

        [Parameter()]
        [Switch]
        ${AllowPrerelease},

        [Parameter()]
        [Version]
        ${RequiredVersion},

        [Parameter()]
        [Switch]
        ${UseExactAccountVersion},

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Invoker}
    )

    process {
        $azModule = "Az"
        if ($AllowPrerelease) {
            if ($RequiredVersion -and $RequiredVersion -lt [Version] "6.0") {
                Throw "[$Invoker] Prerelease version cannot be lower than 6.0. Please install GA modules only or specify Az version above 6.0."
            }
            else {
                $azModule = "AzPreview"
            }
        }
        $findModuleParams = @{
            Name =  $azModule
            RequiredVersion = $RequiredVersion
            ErrorAction = 'Stop'
        }
        if ($Repository) {
            $findModuleParams.Add('Repository', $Repository);
        }

        $modules = [Array] (PowerShellGet\Find-Module @findModuleParams)
        if ($modules.Count -gt 1) {
            Throw "[$Invoker] You have multiple modules matched 'Az' in the registered reposistory $($modules.Repository). Please specify a single -Repository."
        }

        $accountVersion = 0
        if (!$UseExactAccountVersion) {
            $findModuleParams = @{
                Name = 'Az.Accounts'
                Repository = $Repository
            }
            $module = PowerShellGet\Find-Module @findModuleParams
            $accountVersion = [Version] $module.Version
        }

        $modulesWithVersion = @()
        $containValidModule = if ($Name) {$Name -Contains 'Az.Accounts'} else {$false}
        $module = $null
        foreach($module in $modules.Dependencies) {
            if ($module.Name -eq 'Az.Accounts') {
                if ($UseExactAccountVersion) {
                    $version = $accountVersion
                    if ($module.Keys -Contains 'MinimumVersion') {
                        $version = $module.MinimumVersion
                    }
                    elseif ($module.Keys -Contains 'RequiredVersion') {
                        $version = $module.RequiredVersion
                    }
                    $modulesWithVersion += [PSCustomObject]@{Name = $module.Name; Version = $version}
                }
                else {
                    $modulesWithVersion += [PSCustomObject]@{Name = $module.Name; Version = $accountVersion}
                }
            }
            elseif (!$Name -or $Name -Contains $module.Name)
            {
                if ($module.RequiredVersion) {
                    $modulesWithVersion += [PSCustomObject]@{Name = $module.Name; Version = $module.RequiredVersion}
                    $containValidModule = $true
                }
            }
        }

        if (!$containValidModule) {
            $modulesWithVersion = $modulesWithVersion | Where-Object {$_.Name -ne "Az.Accounts"}
        }
        $count = if ($modulesWithVersion) {$modulesWithVersion.Count} else {0}
        Write-Debug "[$Invoker] $count module(s) are found."
        $modulesWithVersion
    }
}

class ModuleInfo
{
    [string] $Name = $null
    [Version[]] $Version = @()
}

function Remove-AzureRM {
    process {
        try {
            $azureModuleNames = (Get-InstalledModule -Name Azure* -ErrorAction Stop).Name | Where-Object {$_ -match "Azure(\.[a-zA-Z0-9]+)?" -or $_ -match "AzureRM(\.[a-zA-Z0-9]+)?"}
            foreach($moduleName in $azureModuleNames) {
                PowerShellGet\Uninstall-Module -Name $moduleName -AllVersion -AllowPrerelease -ErrorAction Continue
            }
        }
        catch {
            Write-Warning $_
        }
    }
}

function Get-RepositoryUrl {
    param (
        [Parameter()]
        [string]
        ${Repository}
    )

    process {
        $url = (Get-PSRepository -Name $repository).SourceLocation
        $url
    }
}

$exportedFunctions = @( Get-ChildItem -Path $PSScriptRoot/exports/*.ps1 -Recurse -ErrorAction SilentlyContinue )
$internalFunctions = @( Get-ChildItem -Path $PSScriptRoot/internal/*.ps1 -ErrorAction SilentlyContinue )

$allFunctions = $internalFunctions + $exportedFunctions
foreach($function in $allFunctions) {
    try {
        . $function.Fullname
    }
    catch {
        Write-Error "Failed to import function $($function.fullname): $_"
    }
}

Export-ModuleMember -Function $exportedFunctions.Basename

$commandsWithRepositoryParameter = @(
    "Install-AzModule",
    "Update-AzModule"
)

Add-RepositoryArgumentCompleter -Cmdlets $commandsWithRepositoryParameter -ParameterName "Repository"
# SIG # Begin signature block
# MIIjhgYJKoZIhvcNAQcCoIIjdzCCI3MCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAO35Vstyt7bXP5
# XBFiPulTWoewPWI1LSwQOu2YlEHdcaCCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWzCCFVcCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgnoNvIDbN
# U14Xek0n59eIgcIOcYj/QbLzbYiH+BfzfFUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBqhMqMUbn/SrbOsbt4w3orGaaHU89611Q2cMAaxlxc
# TA85OC1kGXKFCIMg0RVZSHQGK0vCCYlsJmtw9oxsAH0rwV4bUQGxefEXdwP0TUFH
# SZIdxm1SitneSLOSBqs/pw9gSKpzG6GUl8CFi00viVzvcXzmFOkdUmBsUQEWuP7c
# CqpKGiSdMbaswWs7yhjNs5R0TbOyYdpLcqtgKi0PcRI+ddR7P1ruyalVMWUs6/rD
# WSzsmeD/KexmUI8svl76ox/W6ILf1az/Vb9ldDMnEs7jpr4ecnGCZrn0jvIT5qAX
# rj1hahEapqlMFBn0AZnPH89mtIFmRyPScXDkVDLB6BDkoYIS5TCCEuEGCisGAQQB
# gjcDAwExghLRMIISzQYJKoZIhvcNAQcCoIISvjCCEroCAQMxDzANBglghkgBZQME
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIGncDWqVwu4Ty01xiMCCosM3835wi1slfPyWy/ne
# PvmEAgZhgBLSF64YEzIwMjExMTE1MTMxNjM5LjExNVowBIACAfSggdCkgc0wgcox
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjdCRjEtRTNFQS1CODA4MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIOPDCCBPEwggPZoAMCAQICEzMAAAFRw1DnWWyqxqcAAAAAAVEw
# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
# MjAxMTEyMTgyNjA0WhcNMjIwMjExMTgyNjA0WjCByjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046N0JGMS1FM0VBLUI4
# MDgxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf0ofvqoSuO+84iSNZsem0yRgOOYb4
# kSbOC7Kv9XGNmBn+KDwyTjuOpIk/lHEf+wPKqFi7uM9I7zqyJmHy7sMFf0vwj4AH
# 7x88+8Pi6gsoPbYGmgWXgHwXDkrtK6Ju9vEY3tp0vX/Nb6xZeVW+kOEQ8goMgK8R
# 02MZMuGS19+2N5+D2W6YExQEnYbj+Dhp3R0O9E2YqIxldd78uXhCD+g9LNcJQRih
# JKprkP7kxGKZV7n9hMuPSNWvyIXjlXSFPtUfw4k7hgiZydmGroPDUb7DoAJEZ48W
# Y5apby0RnXdIyY6q4mtOTDLLzPI21W20kBft2IUttHRK8yVsllYrQod3AgMBAAGj
# ggEbMIIBFzAdBgNVHQ4EFgQUxXf/42hQYpM0aDo4zITp83VE6m0wHwYDVR0jBBgw
# FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB
# XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx
# MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN
# BgkqhkiG9w0BAQsFAAOCAQEAK/31wBWDmfHRKqO8t9DOa6AyPlwn00TrR25IfUun
# EdiKb0uzdR+Jh3u3Qm/ITD+tFMQodvOdXosUuVf76UckwYrNmce1N7Y4jpkcWc2I
# WG2DJa5gMmubspDKQ2LUbUtu5WJ70x6Gagr6EGJmeetx9lKcFKiSu87ZARYcLXGd
# nnAzzZQSOmsVg6RyFT7pFygKOOYgUZ+BLM2PUwht/iVwnkWhXUyDoXAXjkKKM5cd
# VevOSKwxn2m4OkWOMRXpMBjog2AySEt6/8BWjDSwXwx9DO0kiUVh0USRnk0X8jLO
# gLZhv2LDhsIp0Gt0PcCzqa+gZI2MILqU53PoR6skrc2EWDCCBnEwggRZoAMCAQIC
# CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp
# ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx
# NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF
# ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD
# DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx
# z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1
# rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc
# sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB
# 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF
# bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
# EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB
# kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe
# MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA
# LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx
# vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS
# inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1
# L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO
# M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4
# pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45
# V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x
# 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe
# gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn
# QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp
# 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT
# X4/edIhJEqGCAs4wggI3AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP
# cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3QkYxLUUzRUEtQjgw
# ODElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG
# BSsOAwIaAxUAoKKvc/E/pEILJUwlIBWgxXrXI16ggYMwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOU8XTcwIhgPMjAy
# MTExMTUxMjE0NDdaGA8yMDIxMTExNjEyMTQ0N1owdzA9BgorBgEEAYRZCgQBMS8w
# LTAKAgUA5TxdNwIBADAKAgEAAgIHBwIB/zAHAgEAAgISDzAKAgUA5T2utwIBADA2
# BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIB
# AAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAOCsXz7/pQmT+mtOzY4Gjlc3cnBfQUye
# YqTz8DXIS+VTJb41Mnt/YLmOyXabGgOGgyJnMUd5PQMmuSFIdiE2pgf4vwAuIwiy
# 67G2gu8dIszpKsY1kZDPf8gP7BzMP9kwQFcHHetse67hiyrJ83EUgi+Z3X4BzjUQ
# ZqSxtnOxFdVPMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAFRw1DnWWyqxqcAAAAAAVEwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgubg2IoiA
# Az99DrHJRNzJVxzEBMWdzjrnBzzEBwSZOawwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCAuzVyZiPjWwVkHAKYW+/1Jw/m265SHGy/+3QH1cXrlQTCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABUcNQ51lsqsanAAAA
# AAFRMCIEIHQOBfOPE0ETQcVtgmV823W1tGatQ1l2FGJIvH55FejMMA0GCSqGSIb3
# DQEBCwUABIIBAIkgi0+fHMwJHFxfhd8CalxFLSeq6rb/JC3Xa+1UtOGj5rCXrM5g
# pFs/LL6QwW+wkjBKBFSx+myVxoko5a+haFqOrtNWc5MTWIhI3n9kwdgnHYLKRHwq
# 9vpxOUsLaUYgvxb+UbC3COSgcd0nCzDl4Gwx7o5y5JwzJAg+fKqkopvFaFh7Tz9b
# J07ex7Nv4fHZppkDc+VBMd9MF25UxNxgX9GDSZIz5sy8UUDCi9ccMXTyC4eL89OZ
# hhMXM/UvJdlC04Skj1WCFaG938HJOivaiETkoRRMaQRei2Nfz/nySEzHzS7Ot8H1
# jub/2upTbZFlclc9IBtUUxayY9RwkCWh6BQ=
# SIG # End signature block