AWS.Tools.Installer.psm1

$script:AWSToolsSignatureSubject = 'CN="Amazon.com, Inc.", O="Amazon.com, Inc.", L=Seattle, S=Washington, C=US'
$script:AWSToolsTempRepoName = 'AWSToolsTemp'
$script:CurrentMinAWSToolsInstallerVersion = '0.0.0.0'
$script:ExpectedModuleCompanyName = 'aws-dotnet-sdk-team'

function Get-CleanVersion {
    Param(
        [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory, Position = 0)]
        [AllowNull()]
        [System.Version]
        $Version
    )

    Process {
        if ($null -eq $Version) {
            $Version
        }
        else {
            $major = $Version.Major
            $minor = $Version.Minor
            $build = $Version.Build
            $revision = $Version.Revision
    
            #PowerShell modules version numbers can have missing fields, that would create problems with
            #matching and sorting versions. Replacing missing fields with 0s
            if ($major -lt 0) {
                $major = 0
            }
            if ($minor -lt 0) {
                $minor = 0
            }
            if ($build -lt 0) {
                $build = 0
            }
            if ($revision -lt 0) {
                $revision = 0
            }
    
            [System.Version]::new($major, $minor, $build, $revision)
        }
    }
}

function Get-AWSToolsModule {
    Param(
        [Parameter()]
        [Switch]
        $SkipIfInvalidSignature
    )

    Process {
        $awsToolsModules = Get-Module AWS.Tools.* -ListAvailable
        if ($awsToolsModules -and ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows)) {
            $awsToolsModules = $awsToolsModules | Where-Object {
                $signature = Get-AuthenticodeSignature $_.Path
                ($signature.Status -eq 'Valid' -or $SkipIfInvalidSignature) -and
                $signature.SignerCertificate.Subject -eq $script:AWSToolsSignatureSubject
            }
        }
        $awsToolsModules | Where-Object { $_.Name -ne 'AWS.Tools.Installer' }
    }
}

<#
.Synopsis
    Uninstalls all currently installed AWS.Tools modules.
 
.Description
    This cmdlet uses Uninstall-Module to uninstall all currently installed AWS.Tools
    modules.
 
.Notes
 
.Example
    Uninstall-AWSToolsModule -ExceptVersion 4.0.0.0
 
    This example uninstalls all versions of all AWS.Tools modules except for version 4.0.0.0.
#>

function Uninstall-AWSToolsModule {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        ## Specifies the minimum version of the modules to uninstall.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $MinimumVersion,

        ## Specifies exact version number of the module to uninstall.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $RequiredVersion,

        ## Specifies the maximum version of the modules to uninstall.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $MaximumVersion,

        ## Specifies that you want to uninstall all of the other available versions of AWS Tools except this one.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $ExceptVersion,

        ## Forces Uninstall-AWSToolsModule to run without asking for user confirmation
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $Force
    )

    Begin {
        $MinimumVersion = Get-CleanVersion $MinimumVersion
        $RequiredVersion = Get-CleanVersion $RequiredVersion
        $MaximumVersion = Get-CleanVersion $MaximumVersion
        $ExceptVersion = Get-CleanVersion $ExceptVersion

        Write-Verbose "[$($MyInvocation.MyCommand)] ConfirmPreference=$ConfirmPreference WhatIfPreference=$WhatIfPreference VerbosePreference=$VerbosePreference Force=$Force"
    }

    Process {
        $ErrorActionPreference = 'Stop'

        Write-Verbose "[$($MyInvocation.MyCommand)] Searching installed modules"
        $awsToolsModules = Get-AWSToolsModule
        
        if ($MinimumVersion) {
            $awsToolsModules = $awsToolsModules | Where-Object { $_.Version -ge $MinimumVersion }
        }
        if ($MaximumVersion) {
            $awsToolsModules = $awsToolsModules | Where-Object { $_.Version -le $MaximumVersion }
        }
        if ($RequiredVersion) {
            $awsToolsModules = $awsToolsModules | Where-Object { $_.Version -eq $RequiredVersion }
        }
        if ($ExceptVersion) {
            $awsToolsModules = $awsToolsModules | Where-Object { $_.Version -ne $ExceptVersion }
        }

        $versions = $awsToolsModules | Group-Object Version

        if ($awsToolsModules -and ($Force -or $WhatIfPreference -or $PSCmdlet.ShouldProcess("AWS Tools version $([System.String]::Join(', ', $versions.Name))"))) {
            $ConfirmPreference = 'None'

            $versions | ForEach-Object {
                Write-Host "Uninstalling AWS.Tools version $($_.Name)"
            
                $versionModules = $_.Group
            
                while ($versionModules) {
                    $dependencies = $versionModules.RequiredModules | Sort-Object -Unique
                    if ($dependencies) {
                        $removableModules = $versionModules | Where-Object { -not $dependencies.Name.Contains($_.Name) }
                    }
                    else {
                        $removableModules = $versionModules
                    }
            
                    if (-not $removableModules) {
                        Write-Error "Remaining modules for version $($_.Name) cannot be removed"
                        break
                    }
                    $removableModules | ForEach-Object {
                        if ($WhatIfPreference) {
                            Write-Host "What if: Uninstalling module $($_.Name)"
                        }
                        else {
                            Write-Host "Uninstalling module $($_.Name)"
                            #We need to use -Force to work around https://github.com/PowerShell/PowerShellGet/issues/542
                            $uninstallModuleParams = @{
                                Name            = $_.Name
                                RequiredVersion = $_.Version
                                Force           = $true
                                Confirm         = $false
                                ErrorAction     = 'Continue'
                            }
                            Uninstall-Module @uninstallModuleParams
                        }
                    }
            
                    $versionModules = $versionModules | Where-Object { -not $removableModules.Name.Contains($_.Name) }
                }
            }
        }
    }

    End {
        Write-Verbose "[$($MyInvocation.MyCommand)] End"
    }
}

function Get-AvailableModuleVersion {
    Param(
        ## Specifies names of the AWS.Tools modules to retrieve the version for.
        [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory)]
        [string[]]
        $Name,

        ## Specifies exact version number of the module to install.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $RequiredVersion,

        ## Specifies the maximum version of the modules to install.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $MaximumVersion,

        ## Specifies a proxy server for the request, rather than connecting directly to an internet resource.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Uri]
        $Proxy,

        ## Specifies a user account that has permission to use the proxy server specified by the Proxy parameter.
        [Parameter(ValueFromPipelineByPropertyName)]
        [PSCredential]
        $ProxyCredential
    )

    Begin {
        $RequiredVersion = Get-CleanVersion $RequiredVersion
        $MaximumVersion = Get-CleanVersion $MaximumVersion

        Write-Verbose "[$($MyInvocation.MyCommand)] ConfirmPreference=$ConfirmPreference WhatIfPreference=$WhatIfPreference VerbosePreference=$VerbosePreference Force=$Force"
    }

    Process {
        $proxyParams = @{ }
        if ($Proxy) {
            $proxyParams['Proxy'] = $Proxy
        }
        if ($ProxyCredential) {
            $proxyParams['ProxyCredential'] = $ProxyCredential
        }
        $findModuleParams = @{
            RequiredVersion = $versionToInstall
            MaximumVersion  = $MaximumVersion
            Repository      = 'PSGallery'
            ErrorAction     = 'Stop'
        }
        $savedModules = $Name | ForEach-Object { Find-Module -Name $_ @findModuleParams @proxyParams }
        
        $versionToInstall = $savedModules.Version | Sort-Object -Unique
            
        if ($versionToInstall.Count -gt 1) {
            Write-Verbose "[$($MyInvocation.MyCommand)] Found multiple modules versions: $([System.String]::Join(', ', $versionToInstall)).)"
            $versionToInstall = [System.Version[]]$versionToInstall | Measure-Object -Minimum | Select-Object -Expand Minimum
        
            $findModuleParams = @{
                RequiredVersion = $versionToInstall
                Repository      = 'PSGallery'
                ErrorAction     = 'Stop'
            }
            $savedModules = $Name | ForEach-Object { Find-Module -Name $_ $findModuleParams @proxyParams }
        }
        
        $versionToInstall
    }

    End {
        Write-Verbose "[$($MyInvocation.MyCommand)] End"
    }
}

function Save-AWSToolsModule {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    Param(
        ## Specifies name of the AWS.Tools module to save.
        [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory)]
        [string]
        $Name,

        ## Specifies directory the module should be saved to.
        [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
        [string]
        $TemporaryRepoDirectory,

        ## Specifies exact version number of the module to install.
        [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
        [System.Version]
        $RequiredVersion,

        ## Specifies which modules are already available either installed or in the temporary repository.
        [Parameter(ValueFromPipelineByPropertyName, Mandatory)]
        [AllowEmptyCollection()]
        [System.Collections.Generic.HashSet[string]]
        $SavedModules,

        ## Specifies a proxy server for the request, rather than connecting directly to an internet resource.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Uri]
        $Proxy,

        ## Specifies a user account that has permission to use the proxy server specified by the Proxy parameter.
        [Parameter(ValueFromPipelineByPropertyName)]
        [PSCredential]
        $ProxyCredential
    )

    Begin {
        $RequiredVersion = Get-CleanVersion $RequiredVersion

        Write-Verbose "[$($MyInvocation.MyCommand)] ConfirmPreference=$ConfirmPreference WhatIfPreference=$WhatIfPreference VerbosePreference=$VerbosePreference Force=$Force"
    }

    Process {
        $ErrorActionPreference = 'Stop'

        if ($SavedModules.Add($Name.ToLower())) {
            $proxyParams = @{ }
            if ($Proxy) {
                $proxyParams['Proxy'] = $Proxy
            }
            if ($ProxyCredential) {
                $proxyParams['ProxyCredential'] = $ProxyCredential
            }

            $findModuleParams = @{
                Name            = $Name
                RequiredVersion = $versionToInstall
                Repository      = 'PSGallery'
                ErrorAction     = 'Stop'
            }
            $moduleInfo = Find-Module @findModuleParams @proxyParams
            if ($moduleInfo.CompanyName -ne $script:ExpectedModuleCompanyName) {
                throw "Error validating CompanyName for $($moduleInfo.Name)"
            }

            $nupkgFilePath = Join-Path $TemporaryRepoDirectory "$($moduleInfo.Name).$($moduleInfo.Version).nupkg"

            Write-Verbose "[$($MyInvocation.MyCommand)] Downloading module $($moduleInfo.Name) to $TemporaryRepoDirectory"
            $webRequestParams = @{
                Uri     = "https://www.powershellgallery.com/api/v2/package/$($moduleInfo.Name)/$($moduleInfo.Version)"
                OutFile = $nupkgFilePath
            }
            if ($PSVersionTable.PSVersion.Major -le 5) {
                $webRequestParams['UseBasicParsing'] = $true
            }
            Invoke-WebRequest @webRequestParams @proxyParams

            Write-Verbose "[$($MyInvocation.MyCommand)] Validating module manifest"
            try {
                Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
                $zipArchive = [System.IO.Compression.ZipFile]::OpenRead($nupkgFilePath)
                $entry = $zipArchive.GetEntry("$($moduleInfo.Name).psd1")
                $entryStream = $entry.Open()
                $temporaryManifestFilePath = Join-Path ([System.IO.Path]::GetTempPath()) "$([System.IO.Path]::GetRandomFileName()).psd1"
                $manifestFileStream = [System.IO.File]::OpenWrite($temporaryManifestFilePath)
                $entryStream.CopyTo($manifestFileStream)
                $manifestFileStream.Close();

                if ($awsToolsModules -and ($PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows)) {
                    $manifestSignature = Get-AuthenticodeSignature $temporaryManifestFilePath
                    if ($manifestSignature.Status -eq 'Valid' -and $manifestSignature.SignerCertificate.Subject -eq $script:AWSToolsSignatureSubject) {
                        Write-Verbose "[$($MyInvocation.MyCommand)] Manifest signature correctly validated"
                    }
                    else {
                        Write-Host $temporaryManifestFilePath
                        Write-Host $manifestSignature.Status
                        Write-Host $manifestSignature.SignerCertificate.Subject
                        throw "Error validating manifest signature for $($moduleInfo.Name)"
                    }
                }
                else {
                    Write-Verbose "[$($MyInvocation.MyCommand)] Authenticode signature can only be vefied on Windows, skipping"
                }

                $manifestData = Import-PowerShellDataFile $temporaryManifestFilePath

                if ($manifestData.PrivateData.MinAWSToolsInstallerVersion) {
                    $minVersion = Get-CleanVersion $manifestData.PrivateData.MinAWSToolsInstallerVersion
                    if ($minVersion -gt $script:CurrentMinAWSToolsInstallerVersion) {
                        throw "$($moduleInfo.Name) version $RequiredVersion requires at least AWS.Tools.Installer version $minVersion. Run 'Update-Module AWS.Tools.Installer -Force'."
                    }
                }

                $saveAWSToolsModuleParams = @{
                    RequiredVersion        = $RequiredVersion
                    TemporaryRepoDirectory = $TemporaryRepoDirectory
                    SavedModules           = $SavedModules
                }
                $manifestData.RequiredModules | ForEach-Object {
                    Write-Verbose "[$($MyInvocation.MyCommand)] Found dependency $($_.ModuleName)"
                    Save-AWSToolsModule -Name $_.ModuleName @saveAWSToolsModuleParams @proxyParams | Out-Null
                }

                #Returning the property capitalized module name.
                $moduleInfo.Name
            }
            finally {
                if ($manifestFileStream) {
                    $manifestFileStream.Dispose()
                }
                if ($entryStream) {
                    $entryStream.Dispose()
                }
                if ($zipArchive) {
                    $zipArchive.Dispose()
                }
                if ($temporaryManifestFilePath) {
                    Remove-Item $temporaryManifestFilePath
                }
            }
        }
    }

    End {
        Write-Verbose "[$($MyInvocation.MyCommand)] End"
    }
}

<#
.Synopsis
    Install AWS.Tools modules.
 
.Description
    This cmdlet uses Instal-Module to install AWS.Tools modules.
    Unless -SkipUpdate is specified, this cmdlet also updates all other currently installed AWS.Tools modules to the version being installed.
 
.Notes
    This cmdlet uses the PSRepository named PSGallery as source.
    Use 'Get-PSRepository -Name PSGallery' for information on the PSRepository used by Update-AWSToolsModule.
    This cmdlet downloads all modules from https://www.powershellgallery.com/api/v2/package/ and considers it a trusted source.
 
.Example
    Install-AWSToolsModule EC2,S3 -RequiredVersion 4.0.0.0
 
    This example installs version 4.0.0.0 of AWS.Tools.EC2, AWS.Tools.S3 and their dependencies.
#>

function Install-AWSToolsModule {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        ## Specifies names of the AWS.Tools modules to install.
        ## The names can be listed either with or without the "AWS.Tools." prefix (i.e. "AWS.Tools.Common" or simply "Common").
        [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline, Mandatory, Position = 0)]
        [string[]]
        $Name,

        ## Specifies exact version number of the module to install.
        [Parameter(ValueFromPipelineByPropertyName, Position = 1)]
        [System.Version]
        $RequiredVersion,

        ## Specifies the maximum version of the modules to install.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $MaximumVersion,

        ## Specifies that, after a successful install, all other versions of the AWS Tools modules should be uninstalled.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $CleanUp,

        ## Install-AWSToolsModule by default also updates all currently installed AWS.Tools modules. -SkipUpdate disables the update.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $SkipUpdate,

        ## Specifies the installation scope of the module. The acceptable values for this parameter are AllUsers and CurrentUser.
        ## The AllUsers scope installs modules in a location that is accessible to all users of the computer:
        ## $env:ProgramFiles\PowerShell\Modules
        ## The CurrentUser installs modules in a location that is accessible only to the current user of the computer:
        ## $home\Documents\PowerShell\Modules
        ## When no Scope is defined, the default is CurrentUser.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]
        $Scope = 'CurrentUser',

        ## Overrides warning messages about installation conflicts about existing commands on a computer.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $AllowClobber,

        ## Specifies a proxy server for the request, rather than connecting directly to an internet resource.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Uri]
        $Proxy,

        ## Specifies a user account that has permission to use the proxy server specified by the Proxy parameter.
        [Parameter(ValueFromPipelineByPropertyName)]
        [PSCredential]
        $ProxyCredential,
        
        ## Forces an install of each specified module without a prompt to request confirmation
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $Force
    )

    Begin {
        $RequiredVersion = Get-CleanVersion $RequiredVersion
        $MaximumVersion = Get-CleanVersion $MaximumVersion

        Write-Verbose "[$($MyInvocation.MyCommand)] ConfirmPreference=$ConfirmPreference WhatIfPreference=$WhatIfPreference VerbosePreference=$VerbosePreference Force=$Force"
    }

    Process {
        $ErrorActionPreference = 'Stop'

        $Name = $Name | ForEach-Object {
            if ($_.Contains('.')) {
                $_
            }
            else {
                "AWS.Tools.$_"
            }
        }

        if (($Name -like 'AWS.Tools.*' | Sort-Object -Unique).Count -ne $Name.Count) {
            throw "The Name parameter must contain only AWS.Tools modules without duplicates."
        }

        if ($Name -eq 'AWS.Tools.Installer') {
            throw "AWS.Tools.Installer cannot be used to install AWS.Tools.Installer. Use Update-Module instead."
        }
        
        if ($RequiredVersion) {
            $versionToInstall = $RequiredVersion
        }
        else {
            $versionToInstall = Get-AvailableModuleVersion -Name $Name -MaximumVersion $MaximumVersion -Proxy $Proxy -ProxyCredential $ProxyCredential
            Write-Verbose "[$($MyInvocation.MyCommand)] Installing AWS Tools version $versionToInstall"
        }

        if (-not $SkipUpdate) {
            Write-Verbose "[$($MyInvocation.MyCommand)] Searching installed modules"
            $awsToolsModules = Get-AWSToolsModule -SkipIfInvalidSignature
            if ($awsToolsModules) {
                Write-Verbose "[$($MyInvocation.MyCommand)] Merging existing modules into the list of modules to install"
                $Name = ($Name + ($awsToolsModules | Select-Object -Expand Name)) | Sort-Object -Unique
            }
        }

        $Name = $Name | Where-Object { -not (Get-Module $_ -ListAvailable | Where-Object { $_.Version -eq $versionToInstall }) }

        if ($Name) {
            if ($Force -or $WhatIfPreference -or $PSCmdlet.ShouldProcess("AWS Tools version $versionToInstall")) {
                $ConfirmPreference = 'None'

                $temporaryRepoDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
                Write-Verbose "[$($MyInvocation.MyCommand)] Create folder for temporary repository $temporaryRepoDirectory"
                New-Item -ItemType Directory -Path $temporaryRepoDirectory -WhatIf:$false | Out-Null
                try {
                    if (-not $WhatIfPreference) {
                        Unregister-PSRepository -Name $script:AWSToolsTempRepoName -ErrorAction 'SilentlyContinue'
                        Write-Verbose "[$($MyInvocation.MyCommand)] Registering temporary repository $script:AWSToolsTempRepoName"
                        Register-PSRepository -Name $script:AWSToolsTempRepoName -SourceLocation $temporaryRepoDirectory -ErrorAction 'Stop'
                        Set-PSRepository -Name $script:AWSToolsTempRepoName -InstallationPolicy Trusted
                    }

                    $savedModules = New-Object System.Collections.Generic.HashSet[string]
                    $modulesToInstall = @()

                    Write-Verbose "[$($MyInvocation.MyCommand)] Downloading modules to temporary repository"
                    $saveAWSToolsModuleParams = @{
                        RequiredVersion        = $versionToInstall
                        TemporaryRepoDirectory = $temporaryRepoDirectory
                        SavedModules           = $savedModules
                        Proxy                  = $Proxy
                        ProxyCredential        = $ProxyCredential
                        Confirm                = $false
                        WhatIf                 = $false
                    }
                    $Name | ForEach-Object {
                        if (Get-Module $_ -ListAvailable | Where-Object { $_.Version -eq $versionToInstall } ) {
                            Write-Verbose "[$($MyInvocation.MyCommand)] Skipping installation of $_ because already installed"
                        }
                        else {
                            $modulesToInstall += Save-AWSToolsModule -Name $_ @saveAWSToolsModuleParams
                        }
                    }

                    Write-Verbose "[$($MyInvocation.MyCommand)] Installing modules"
                    $installModuleParams = @{
                        RequiredVersion = $versionToInstall
                        Scope           = $Scope
                        Repository      = $script:AWSToolsTempRepoName
                        AllowClobber    = $AllowClobber
                        Confirm         = $false
                        ErrorAction     = 'Stop'
                    }
                    $modulesToInstall | ForEach-Object {
                        if (-not $WhatIfPreference) {
                            Write-Host "Installing module $_ version $versionToInstall"
                            Install-Module -Name $_ @installModuleParams
                        }
                        else {
                            Write-Host "What if: Installing module $_ version $versionToInstall"
                        }
                    }
                    Write-Verbose "[$($MyInvocation.MyCommand)] Modules install complete"
                }
                finally {
                    if (-not $WhatIfPreference) {
                        Write-Verbose "[$($MyInvocation.MyCommand)] Unregistering temporary repository $script:AWSToolsTempRepoName"
                        Unregister-PSRepository -Name $script:AWSToolsTempRepoName -ErrorAction 'Continue'
                    }
                    Write-Verbose "[$($MyInvocation.MyCommand)] Delete repository folder $temporaryRepoDirectory"
                    Remove-Item -Path $temporaryRepoDirectory -Recurse -WhatIf:$false
                }
            }
        }
        else {
            Write-Verbose "[$($MyInvocation.MyCommand)] All modules are up to date"
        }

        if ($CleanUp) {
            Uninstall-AWSToolsModule -ExceptVersion $versionToInstall
        }
    }

    End {
        Write-Verbose "[$($MyInvocation.MyCommand)] End"
    }
}

<#
.Synopsis
    Updates all currently installed AWS.Tools modules.
 
.Description
    This cmdlet uses Instal-Module to update all AWS.Tools modules.
 
.Notes
    This cmdlet uses the PSRepository named PSGallery as source.
    Use 'Get-PSRepository -Name PSGallery' for information on the PSRepository used by Update-AWSToolsModule.
    This cmdlet downloads all modules from https://www.powershellgallery.com/api/v2/package/ and considers it a trusted source.
 
.Example
    Update-AWSToolsModule -CleanUp
 
    This example updates all installed AWS.Tools modules to the latest version available on the PSGallery and uninstalls all other versions.
#>

function Update-AWSToolsModule {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    Param(
        ## Specifies the exact version of the modules to update to.
        [Parameter(ValueFromPipelineByPropertyName, Position = 0)]
        [System.Version]
        $RequiredVersion,

        ## Specifies the maximum version of the modules to update to.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Version]
        $MaximumVersion,

        ## Specifies that, after a successful install, all other versions of the AWS Tools modules should be uninstalled.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $CleanUp,

        #Specifies the installation scope of the module. The acceptable values for this parameter are AllUsers and CurrentUser.
        #The AllUsers scope installs modules in a location that is accessible to all users of the computer:
        # $env:ProgramFiles\PowerShell\Modules
        #The CurrentUser installs modules in a location that is accessible only to the current user of the computer:
        # $home\Documents\PowerShell\Modules
        #When no Scope is defined, the default is CurrentUser.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]
        $Scope = 'CurrentUser',

        #Overrides warning messages about installation conflicts about existing commands on a computer.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $AllowClobber,

        #Specifies a proxy server for the request, rather than connecting directly to an internet resource.
        [Parameter(ValueFromPipelineByPropertyName)]
        [Uri]
        $Proxy,

        ## Specifies a user account that has permission to use the proxy server specified by the Proxy parameter.
        [Parameter(ValueFromPipelineByPropertyName)]
        [PSCredential]
        $ProxyCredential,
        
        ## Forces an update of each specified module without a prompt to request confirmation
        [Parameter(ValueFromPipelineByPropertyName)]
        [Switch]
        $Force
    )

    Begin {
        $RequiredVersion = Get-CleanVersion $RequiredVersion
        $MaximumVersion = Get-CleanVersion $MaximumVersion

        Write-Verbose "[$($MyInvocation.MyCommand)] ConfirmPreference=$ConfirmPreference WhatIfPreference=$WhatIfPreference VerbosePreference=$VerbosePreference Force=$Force"
    }

    Process {
        $ErrorActionPreference = 'Stop'

        Write-Verbose "[$($MyInvocation.MyCommand)] Searching installed modules"
        $awsToolsModules = Get-AWSToolsModule -SkipIfInvalidSignature
        $awsToolsModulesNames = $awsToolsModules | Select-Object -Expand Name | Sort-Object -Unique

        if ($awsToolsModulesNames) {
            Write-Verbose "[$($MyInvocation.MyCommand)] Found modules $([System.String]::Join(', ', $awsToolsModulesNames))"
            if ($RequiredVersion) {
                $versionToInstall = $RequiredVersion
            }
            else {
                $getAvailableModuleVersionParams = @{
                    Name            = $awsToolsModulesNames
                    MaximumVersion  = $MaximumVersion
                    Proxy           = $Proxy
                    ProxyCredential = $ProxyCredential
                }
                $versionToInstall = Get-AvailableModuleVersion @getAvailableModuleVersionParams
                Write-Host "Installing AWS Tools version $versionToInstall"
            }
    
            $installAWSToolsModuleParams = @{
                Name            = $awsToolsModulesNames
                RequiredVersion = $versionToInstall
                Scope           = $Scope
                AllowClobber    = $AllowClobber
                CleanUp         = $CleanUp
                Force           = $Force
                SkipUpdate      = $true
                Proxy           = $Proxy
                ProxyCredential = $ProxyCredential
            }
            Install-AWSToolsModule @installAWSToolsModuleParams
        }
    }

    End {
        Write-Verbose "[$($MyInvocation.MyCommand)] End"
    }
}

# SIG # Begin signature block
# MIIcUAYJKoZIhvcNAQcCoIIcQTCCHD0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBUSUxnJ7tNQ/VW
# ZsL7AppjJrk3R+e1Uy7lZzZlJvsXoKCCF0cwggS5MIIDoaADAgECAhArd4OFAE9M
# ppHAfzWQwHt/MA0GCSqGSIb3DQEBCwUAMIGEMQswCQYDVQQGEwJVUzEdMBsGA1UE
# ChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0
# IE5ldHdvcmsxNTAzBgNVBAMTLFN5bWFudGVjIENsYXNzIDMgU0hBMjU2IENvZGUg
# U2lnbmluZyBDQSAtIEcyMB4XDTE3MDcxMDAwMDAwMFoXDTIwMDcxMDIzNTk1OVow
# ajELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1Nl
# YXR0bGUxGTAXBgNVBAoMEEFtYXpvbi5jb20sIEluYy4xGTAXBgNVBAMMEEFtYXpv
# bi5jb20sIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC18TJW
# m2/aGpvb+TP2CZMg49X/5bjDAgPxWlAs7bqDyZnRYJork4rLmejpeJu+2BhRjZeK
# OirT4XveddBsdL1/TR+FKp8BXEsrm0wcR4yT6mNHJ9yCgC1YBNG91bZ75kRIT+46
# chbC7eNI5703wi8ejxe2KvvOERppBTaFStVJuAHab69dvFma8qE3s7wbqPdQ5eTI
# +Xm0bXp8cObS+vj+hf3N2pfDNWM8ITva3kbowGoCW0rKzpf7fBGtBOKnOCCSL0yC
# AOwLlFkslemVyrT1/HTDjOTKCro016HxOPddA4cefvr2ZhGlRZQQHg7wMdG7TpZX
# ueQ6LoS9UxlzCYHFAgMBAAGjggE+MIIBOjAJBgNVHRMEAjAAMA4GA1UdDwEB/wQE
# AwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzBhBgNVHSAEWjBYMFYGBmeBDAEEATBM
# MCMGCCsGAQUFBwIBFhdodHRwczovL2Quc3ltY2IuY29tL2NwczAlBggrBgEFBQcC
# AjAZDBdodHRwczovL2Quc3ltY2IuY29tL3JwYTAfBgNVHSMEGDAWgBTUwAYiSes5
# S92T4lyhuEd2CXIDWDArBgNVHR8EJDAiMCCgHqAchhpodHRwOi8vcmIuc3ltY2Iu
# Y29tL3JiLmNybDBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0dHA6Ly9y
# Yi5zeW1jZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly9yYi5zeW1jYi5jb20vcmIu
# Y3J0MA0GCSqGSIb3DQEBCwUAA4IBAQC+C4TcK44ph2QQK/1f65jOR23DtSBC3y9a
# bzRHdo4qxmcAW5ot69os7GJfzgVsA5lh1IT4+aMuGYA4GTcF6iTSOMgFSRwP8urI
# N2BprsWuMJsQ7+Flo3PBRN3dU6idOlFKOfuRxgIHRn47t2yRan6XTNhfiWl84DrD
# NjSTnk4c72Gzu0hiwQB9OTsf8CQP3Shb3ZzcAOmeUB01TFoJU34PfJpKlKQZeQIi
# W5WdPPr1G/0cAHgejDHtdNYcSqIWfoGeYgCxUg1IFpp1VmPlqb/de8QKONzPDK6/
# 5hulSGqGgpRmEkwGGJiQeOB51GxYZRCPq3hN3UJ6N0A+hYzj7yspMIIFRzCCBC+g
# AwIBAgIQfBs1NUrn23TnQV8RacprqDANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
# BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
# biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5j
# LiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBV
# bml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNDA3MjIw
# MDAwMDBaFw0yNDA3MjEyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEdMBsGA1UEChMU
# U3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5l
# dHdvcmsxNTAzBgNVBAMTLFN5bWFudGVjIENsYXNzIDMgU0hBMjU2IENvZGUgU2ln
# bmluZyBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA15VD
# 1NzfZ645+1KktiYxBHDpt45bKro3aTWVj7vAMOeG2HO73+vRdj+KVo7rLUvwVxhO
# sY2lM9MLdSPVankn3aPT9w6HZbXerRzx9TW0IlGvIqHBXUuQf8BZTqudeakC1x5J
# sTtNh/7CeKu/71KunK8I2TnlmlE+aV8wEE5xY2xY4fAgMxsPdL5byxLh24zEgJRy
# u/ZFmp7BJQv7oxye2KYJcHHswEdMj33D3hnOPu4Eco4X0//wsgUyGUzTsByf/qV4
# IEJwQbAmjG8AyDoAEUF6QbCnipEEoJl49He082Aq5mxQBLcUYP8NUfSoi4T+Idpc
# Xn31KXlPsER0b21y/wIDAQABo4IBeDCCAXQwLgYIKwYBBQUHAQEEIjAgMB4GCCsG
# AQUFBzABhhJodHRwOi8vcy5zeW1jZC5jb20wEgYDVR0TAQH/BAgwBgEB/wIBADBm
# BgNVHSAEXzBdMFsGC2CGSAGG+EUBBxcDMEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8v
# ZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwICMBkaF2h0dHBzOi8vZC5zeW1jYi5j
# b20vcnBhMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9zLnN5bWNiLmNvbS91bml2
# ZXJzYWwtcm9vdC5jcmwwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQD
# AgEGMCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTeW1hbnRlY1BLSS0xLTcyNDAd
# BgNVHQ4EFgQU1MAGIknrOUvdk+JcobhHdglyA1gwHwYDVR0jBBgwFoAUtnf6aUhH
# n1MS1cLqBzJ2B9GXBxkwDQYJKoZIhvcNAQELBQADggEBAH/ryqfqi3ZC6z6OIFQw
# 47e53PpIPhbHD0WVEM0nhqNm8wLtcfiqwlWXkXCD+VJ+Umk8yfHglEaAGLuh1KRW
# pvMdAJHVhvNIh+DLxDRoIF60y/kF7ZyvcFMnueg+flGgaXGL3FHtgDolMp9Er25D
# KNMhdbuX2IuLjP6pBEYEhfcVnEsRjcQsF/7Vbn+a4laS8ZazrS359N/aiZnOsjhE
# wPdHe8olufoqaDObUHLeqJ/UzSwLNL2LMHhA4I2OJxuQbxq+CBWBXesv4lHnUR7J
# eCnnHmW/OO8BSgEJJA4WxBR5wUE3NNA9kVKUneFo7wjw4mmcZ26QCxqTcdQmAsPA
# WiMwggZqMIIFUqADAgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUA
# MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT
# EHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQg
# Q0EtMTAeFw0xNDEwMjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYT
# AlVTMREwDwYDVQQKEwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0
# YW1wIFJlc3BvbmRlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNk
# Xfx8s+CCNeDg9sYq5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85v
# Zu7dy4XpX6X51Id0iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmej
# vmGfrvP9Enh1DqZbFP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFy
# GGi5uHzu5uc0LzF3gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjH
# yQYuRhDIjegEYNu8c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr
# 7GJCkawCwO+k8IkRj3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNV
# HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCC
# AbIwggGhBglghkgBhv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k
# aWdpY2VydC5jb20vQ1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUA
# cwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMA
# bwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYA
# IAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQA
# IAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUA
# bQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkA
# dAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAA
# aABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG
# /WwDFTAfBgNVHSMEGDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQU
# YVpNJLZJMp1KKnkag0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2Ny
# bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSG
# Mmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEu
# Y3JsMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln
# aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEA
# nSV+GzNNsiaBXJuGziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA
# +94GAYw3+puxnSR+/iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n
# 4hFBpr1i2fAnPTgdKG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OP
# Q6dxnSHdFMoVXZJB2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xI
# J2bIo4sKHOWV2q7ELlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g
# 9BQKOhvjjz3Kr2qNe9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhsw
# DQYJKoZIhvcNAQEFBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0
# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNl
# cnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAw
# MDAwMFowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG
# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJl
# ZCBJRCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnK
# wkKVpYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InE
# Ctqvy15r7a2wcTHrzzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH
# 6zY/2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77b
# lUjE7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnx
# TPKvmPv2zkBdXPao8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2Kk
# PzIUYJX9BwSiCQIDAQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0
# MDIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF
# BQcDCDCCAdIGA1UdIASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYB
# BQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9y
# eS5odG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYA
# IAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkA
# dAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAA
# RABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAA
# UgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAA
# dwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4A
# ZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkA
# bgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1Ud
# EwEB/wQIMAYBAf8CAQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRw
# Oi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1Ud
# HwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz
# c3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZ
# B+0e36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0G
# CSqGSIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/s
# dR90OPKyXGGinJXDUOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZ
# Vann4+erYs37iy2QwsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQl
# c+Qqq8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dF
# zdaD7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsY
# Gk38KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIEXzCCBFsCAQEwgZkw
# gYQxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEf
# MB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazE1MDMGA1UEAxMsU3ltYW50
# ZWMgQ2xhc3MgMyBTSEEyNTYgQ29kZSBTaWduaW5nIENBIC0gRzICECt3g4UAT0ym
# kcB/NZDAe38wDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA
# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgzAM4r9mRY3E90x9cXOXOuiPS
# yrX8WXHh5cvqS78eLgMwDQYJKoZIhvcNAQEBBQAEggEAlje1u5UNgXd28t19L+8q
# tb4Jo7TqRpziQfCZ6y0qXbQxcQLl14FlPqIwr0D8M/qdaxFIaiL9V3QNfWAmYvi+
# o6J7N7LboYax48ATo1tgmNcuFrfhw4zE4t8eEbXdr0obc0PNUP9YQ23IB+zTZIXW
# qKDkIdqQmLyOhtpYPKPUbuIvY5LYD/koe+qKx7WJha186gGz0KIWnWZrOJrJ2qVT
# GJKGkb6UO/fQl1BBpw2H/EIFbh2Up9Tx3MC21zBC85G5thq+t0xST/zyTOIheaG9
# NpozWXyw0gqlaveGATKgx5AsGSPqt6cqNDitvdQH8dnz8JmPlZuPEfrRUAtmzE9r
# C6GCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMTkxMTIxMDEwNzM5WjAjBgkqhkiG9w0BCQQxFgQUWJM6
# VRJjv3KCr6Jv+whdJC7SL8MwDQYJKoZIhvcNAQEBBQAEggEAJUC7XsJ6w/Agc33D
# iaa6gHJleWzIA9azkFBgJmZOqn/LhW15Ag/g+Mt57X8Gxfhp4kz5ER9gC20J+A/Q
# Tk2Z7hNZ8f4JJ6Ej9thBVzEwLmwS/iSPSRSUehMLnl9cw8wOkgHiZMeGR3zH9R33
# KU5drZM8YkVKim1EijAT8bO2MhV/Ioo3abHxY//PUHOkmEUs9HBSnKoVOPh3j0Bc
# o7OciG0w6bs6la79SC2lpoYc/Rj7TDMvpfzX4IRqsEmxg83lx+c6diBjDeu/zQ6z
# mG4w4VHFBaUE/fracXB7OS9FnDj7V1jXOEEK257j0P95awz1pBTZxc4WyNTYjljC
# Jsz5Tw==
# SIG # End signature block