AppvProvider.ps1

<#
     
    Copyright (c) Virtual Engine Limited. All rights reserved.
     
    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.
    See the License for the specific language governing permissions and
    limitations under the License.
     
#>


Import-LocalizedData -BindingVariable localized -FileName Resources.psd1;
$providerName = 'AppV';
$packageSourceRootName = 'AppvPackageSourceRoot';
$persistencePath = Join-Path -Path $env:LOCALAPPDATA -ChildPath ('PackageManagement\{0}' -f $providerName);
$registeredPackageSourcesPath = Join-Path -Path $persistencePath -ChildPath 'PackageSources.json';
$registeredPackageSources = New-Object -TypeName System.Collections.ArrayList -ArgumentList @();

function Resolve-PackageSource { 
    <#
        Returns all registered provider package sources. This is
        called by the Get-PackageSource cmdlet.
    #>

    param ()   
    Write-Debug ('In {0} Provider - ''Resolve-PackageSource''.' -f $providerName);
    Get-RegisteredPackageSources;

    # if that's null or empty, they're asking for the whole list.
    if (-not ($request.PackageSources)) {
        # if there is nothing passed in, just return all the known package sources
        foreach($registeredPackageSource in $registeredPackageSources) {
            Write-Debug ('In {0} Provider - ''Resolve-PackageSource'' - Returning package source ''{1}''.' -f $providerName, $registeredPackageSource.Name);
            Write-Output $registeredPackageSource;
        }
        return;
    }
    
    ##TODO: Support $request.Options['Location']
    # otherwise, they are requesing one or more sources back.
    foreach ($source in ($request.PackageSources)) {
        Write-Debug ('In {0} Provider - ''Resolve-PackageSource'' - Requesting package source ''{1}''.' -f $providerName, $source);
        $isFound = $false;
        # otherwise, for each item, check if we have a source by that name and return it.
        foreach ($packageSource in $registeredPackageSources) {
            Write-Debug ('In {0} Provider - ''Resolve-PackageSource'' - Checking package source ''{1}''.' -f $providerName, $packageSource.Name);
            if ($packageSource.Name -eq $source) {
                Write-Debug ('In {0} Provider - ''Resolve-PackageSource'' - Returning package source ''{1}''.' -f $providerName, $packageSource.Name);
                Write-Output $packageSource;
                $isFound = $true;
                break;
            }
        }
        if ($isFound) {
            continue;
        }
        # if it's not a valid source location send a warning back.
        Write-Warning ($localized.InvalidSourcePathWarning -f $source);
    } #end foreach source
    Write-Debug ('Done In {0} Provider - ''Resolve-PackageSource''.' -f $providerName);
} #end function Resolve-PackageSources

function Dump-RequestObjectOptions {
    param ()
    if ($request.Options) {
        foreach ($optionKey in $request.Options.Keys) {
            Write-Debug ('In {0} Provider - Dynamic Option ''{1}'' => ''{2}''.' -f $providerName, $optionKey, $request.Options[$optionKey]);
        }
    }
}

function Set-RegisteredPackageSources {
    <#
        Persists registered package sources to disk.
    #>

    param ()
    Write-Debug ('In {0} Provider - ''Set-RegisteredPackageSources''.' -f $providerName);
    Write-Debug ('Current Package Source Count: ''{0}''.' -f $RegisteredPackageSources.Count);
    if (-not (Test-Path -Path $registeredPackageSourcesPath -PathType Leaf)) {
        [Ref] $null = New-Item -Path $registeredPackageSourcesPath -ItemType File -Force;
    }
    ## Cannot pipe $registeredPackageSources into ConvertTo-Json if the object is empty!
    ConvertTo-Json -InputObject $registeredPackageSources | Set-Content -Path $registeredPackageSourcesPath -Force;
} #end function Set-RegisteredPackageSources

function Get-RegisteredPackageSources {
    <#
        Loads registered package sources from disk.
    #>

    param ()
    $script:registeredPackageSources = New-Object -TypeName System.Collections.ArrayList -ArgumentList @();
    if (Test-Path -Path $registeredPackageSourcesPath -PathType Leaf) {
        $packageSources = Get-Content -Path $registeredPackageSourcesPath -Raw | ConvertFrom-Json;
        foreach ($source in $packageSources) {
            Write-Debug ('In {0} Provider - ''Get-RegisteredPackageSources'' - Loading package source ''{1}''.' -f $providerName, $source.Name);
            $packageSourceParam = @{
                Name = $source.Name;
                Location = $source.Location;
                Trusted = $source.IsTrusted;
                Registered = $source.IsRegistered;
                Valid = $source.IsValidated;
            }
            $packageSource = New-PackageSource @packageSourceParam;
            $script:registeredPackageSources.Add($packageSource);
        }
    }
    [Ref] $null = Resolve-AppvPackageSourceRoot;
} #end function Get-RegisteredPackageSources

function Register-PackageSource { 
    <#
        .SYNOPSIS
            Registers a new provider package source
    #>

    param (
        [Parameter(Mandatory)] [System.String] $Name, 
        [Parameter(Mandatory)] [System.String] $Location, 
        [Parameter()] [System.Boolean] $Trusted
    )
    Write-Debug ('In {0} Provider - ''Register-PackageSource'' - Name ''{1}''.' -f $providerName, $Name);
    if (-not (Test-Path -Path $Location -PathType Container)) {
        Write-Error -Message ($localized.InvalidDirectoryPathError -f $Location) -Category InvalidArgument;
        return;
    }
    # remove any existing object first.
    for ($i = $registeredPackageSources.Count; $i -gt 0; $i--) {
        $packageSource = $registeredPackageSources[$i -1];
        Write-Debug ('In {0} Provider - ''Register-PackageSource'' - Checking existing package source ''{1}''.' -f $providerName, $packageSource.Name);
        if ($packageSource.Name -eq $Name) {
            Write-Debug ('In {0} Provider - ''Register-PackageSource'' - Removing existing package source ''{1}''.' -f $providerName, $packageSource.Name);
            $script:registeredPackageSources.Remove($packageSource);
        }        
    }
    $location = Resolve-Path -Path $Location;
    $packageSource = New-PackageSource -Name $Name -Location $location -Trusted $trusted -Registered $true -Valid $true;
    $script:registeredPackageSources.Add($packageSource);
    Set-RegisteredPackageSources;
    Write-Output $packageSource; 
} #end function Register-PackageSource

function Unregister-PackageSource { 
    <#
        .SYNOPSIS
            Removes the specified provider package source by name or location.
    #>

    param (
        [Parameter(Mandatory)] [System.String] $Name
    )
    Write-Debug ('In {0} Provider - ''Unregister-PackageSource''.' -f $providerName);
    for ($i = $registeredPackageSources.Count; $i -gt 0; $i--) {
        $packageSource = $registeredPackageSources[$i -1];
        Write-Debug ('In {0} Provider - ''Unregister-PackageSource'' - Checking existing package source ''{1}''.' -f $providerName, $packageSource.Name);
        if ($packageSource.Name -eq $name -or $packageSource.Location -eq $Name )  {
            Write-Debug ('In {0} Provider - ''Unregister-PackageSource'' - Removing source ''{1}'' location ''{2}''.' -f $providerName, $packageSource.Name, $packageSource.Location);
            Write-Output $packageSource;
            $script:registeredPackageSources.Remove($packageSource);
            Set-RegisteredPackageSources;
        }
    } #end foreach registeredPackageSources
} #end function Unregister-PackageSource

function Get-AppvAppxManifest {
   <#
    .SYNOPSIS
        Returns the contents of an App-V package AppxManifest file.
    #>

    param (
        [Parameter(Mandatory)] [System.String] $Path
    )
    try {
        Write-Debug ('In {0} Provider - ''Get-AppvAppxManifest'' - Loading .Net Framework assemblies.' -f $providerName);
        [Ref] $null = [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression');
        [Ref] $null = [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem');
        Write-Debug ('In {0} Provider - ''Get-AppvAppxManifest'' - Opening ''{1}'' .appv archive.' -f $providerName, $Path);
        $appvArchive = New-Object System.IO.Compression.ZipArchive(New-Object System.IO.FileStream($Path, [System.IO.FileMode]::Open));
        $appvArchiveEntry = $appvArchive.GetEntry('AppxManifest.xml');
        if ($appvArchiveEntry -ne $null) {
            $xmlDocument = New-Object System.Xml.XmlDocument;
            $xmlDocument.Load($appvArchiveEntry.Open());
            Write-Output $xmlDocument;
        }
    }
    catch {
        Write-Error -Message ($localized.ReadAppvManifestError -f $Path) -Exception $_.Exception -Category InvalidOperation;
    }
    finally {
        if ($xmlDocument -ne $null) { $xmlDocument = $null; }
        if ($appvArchiveEntry -ne $null) { $appvArchive.Dispose(); }
    }
} #end function Get-AppvAppxManifest

function Find-Package { 
    <#
        .SYNOPSIS
            Searches a registered provider package sources for packages
    #>
  
    param (
        [Parameter()] [System.String[]] $Names,
        [Parameter()] [System.String] $RequiredVersion,
        [Parameter()] [System.String] $MinimumVersion,
        [Parameter()] [System.String] $MaximumVersion
    )

    Write-Debug ('In {0} Provider - ''Find-Package''.' -f $providerName);
    $filterparam = $request.Options['filter']; ##TODO: What is the filter parameter?
    
    if ([System.String]::IsNullOrEmpty($RequiredVersion)) {
        if ([System.String]::IsNullOrEmpty($MinimumVersion)) { $minVersion = New-Object -TypeName System.Version -ArgumentList 0, 0, 0, 0; }
        else { $minVersion =  New-Object -TypeName System.Version -ArgumentList $MinimumVersion; }
        if ([System.String]::IsNullOrEmpty($MaximumVersion)) {$maxVersion = (New-Object -TypeName System.Version -ArgumentList ([int]::MaxValue),([int]::MaxValue)).ToString(); }
        else { $maxVersion = New-Object -TypeName System.Version -ArgumentList $MaximumVersion; }
    }
    else {
        $minVersion = New-Object -TypeName System.Version -ArgumentList $RequiredVersion;
        $maxVersion = $minVersion;
    }
    Write-Debug ('In {0} Provider - ''Find-Package'' - Checking minimum ''{1}'' and maximum ''{2}''.' -f $providerName, $minVersion.ToString(), $maxVersion.ToString());

    foreach ($registeredPackageSource in $registeredPackageSources) {
        Write-Debug ('In {0} Provider - ''Find-Package'' - Checking package source ''{1}''.' -f $providerName, $registeredPackageSource.Name);
        $shouldProcessPackageSource = $false;
        if ([System.String]::IsNullOrEmpty($request.Options['Source'])) {
            Write-Debug ('In {0} Provider - ''Find-Package'' - Source parameter not specified.' -f $providerName);
            $shouldProcessPackageSource = $true;
        }
        elseif ($registeredPackageSource.Name -eq $request.Options['Source']) {
            Write-Debug ('In {0} Provider - ''Find-Package'' - Source parameter ''{1}'' matches package source ''{2}''.' -f $providerName, $request.Options['Source'], $registeredPackageSource.Name);
            $shouldProcessPackageSource = $true;
        }
        else {
            Write-Debug ('In {0} Provider - ''Find-Package'' - Skipping package source ''{1}''.' -f $providerName, $registeredPackageSource);
        }

        if ($shouldProcessPackageSource) {
            Write-Debug ('In {0} Provider - ''Find-Package'' - Searching package source ''{1}''.' -f $providerName, $registeredPackageSource.Name);
            Get-ChildItem -Path $registeredPackageSource.Location -Include *.appv -Recurse | ForEach-Object {
                Write-Debug ('In {0} Provider - ''Find-Package'' - Reading package ''{1}''.' -f $providerName, $_.FullName);
                $appxManifest = Get-AppvAppxManifest -Path $_.FullName;
                $appxVersion = New-Object -TypeName System.Version -ArgumentList $appxManifest.Package.Identity.Version;
                Write-Debug ('In {0} Provider - ''Find-Package'' - Discovered Package Id ''{1}''.' -f $providerName, $appxManifest.Package.Identity.PackageId);
                foreach ($name in $Names) {
                    if (-not $name.Contains('*')) { $name = '*{0}*' -f $name; }
                    if ($appxManifest.Package.Properties.DisplayName -like $name) {
                        if ($appxVersion -lt $minVersion) {
                            Write-Debug ('In {0} Provider - ''Find-Package'' - Package version ''{1}'' is less than ''{2}''.' -f $providerName, $appxVersion, $minVersion);
                        }
                        elseif ($appxVersion -gt $maxVersion) {
                            Write-Debug ('In {0} Provider - ''Find-Package'' - Package version ''{1}'' is greater than ''{2}''.' -f $providerName, $appxVersion, $maxVersion);
                        }
                        else {
                            $softwareIdentityParam = @{
                                FastPackageReference = $_.FullName;
                                Name = $appxManifest.Package.Properties.DisplayName;
                                Version = $appxManifest.Package.Identity.Version;
                                VersionScheme = 'semver';
                                Source = $registeredPackageSource.Name;
                                Summary = $appxManifest.Package.Properties.AppVPackageDescription;
                                SearchKey = $appxManifest.Package.Identity.PackageId;
                                FullPath = $_.FullName;
                                FromTrustedSource = $registeredPackageSource.IsTrusted;
                            };
                            Write-Output (New-SoftwareIdentity @softwareIdentityParam);
                        }
                    } #end if DisplayName -like name
                } #end foreach name
            } #end foreach-object
        } #end if package source
    } #end foreach registeredPackageSource
} #end function Find-Package

function Get-InstalledPackage { 
    <#
        .SYNOPSIS
            Returns all applications registered in the App-V client.
    #>

    param (
        [Parameter()] [System.String] $Name,
        [Parameter()] [System.String] $RequiredVersion,
        [Parameter()] [System.String] $MinimumVersion,
        [Parameter()] [System.String] $MaximumVersion
        ##TODO: Implement version semantics
    )
    Write-Debug ('In {0} Provider - ''Get-InstalledPackage'' {1} {2}.' -f $providerName, $InstalledPackages.Count, $Name);
    if ($Name -eq $null -or $Name -eq "") {
        foreach ($appvPackage in Get-AppvClientPackage -All) {
            $softwareIdentityParam = @{
                FastPackageReference = $appvPackage.Path;
                Name = $appvPackage.Name;
                Version = $appvPackage.Version;
                VersionScheme = 'semver';
                Source = $appvPackage.Path;
                SearchKey = $appvPackage.PackageId;
            };
            New-SoftwareIdentity @softwareIdentityParam;
        }
    }
    else {
        # We're after a specific package
        if (-not $Name.Contains('*')) { $Name = '*{0}*' -f $Name; }
        foreach ($appvPackage in Get-AppvClientPackage -All) {
            if ($appvPackage.Name -like $Name) {
                $softwareIdentityParam = @{
                    FastPackageReference = $appvPackage.Path;
                    Name = $appvPackage.Name;
                    Version = $appvPackage.Version;
                    VersionScheme = 'semver';
                    Source = $appvPackage.Path;
                    SearchKey = $appvPackage.PackageId;
                };
                New-SoftwareIdentity @softwareIdentityParam;
            }
        } #end foreach appvPackage
    } #end else
} #end function Get-InstalledPackage

function Get-PackageProviderName { 
    <#
        .SYNOPSIS
            Returns the name of the PackageManagement provider.
    #>

    param ()
    return $providerName;
} #end function Get-PackageProviderName

function Initialize-Provider { 
    <#
        .SYNOPSIS
            Initializes the provider upon startup.
    #>

    param ()
    Write-Debug ('In {0} Provider - ''Initialize-Provider''.' -f $providerName);
    Get-RegisteredPackageSources;
} #end function Initialize-Provider

function Resolve-AppvPackageSourceRoot {
    <#
        .SYNOPSIS
            Resolves the AppV client package source root from the registry and
            if valid, automatically adds it as a trusted Appv provider package source.
    #>

    param ()
    Write-Debug ('In {0} Provider - ''Resolve-AppvPackageSourceRoot''.' -f $providerName);
    if ((Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\AppV\Client\Streaming')) {
        $packageSourceRoot = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\AppV\Client\Streaming').PackageSourceRoot;
        if ([System.String]::IsNullOrEmpty($packageSourceRoot) -or (-not (Test-Path -Path $packageSourceRoot -PathType Container))) {
            Write-Debug ('In {0} Provider - ''Resolve-AppvPackageSourceRoot'' - Appv Package Source Root ''{1}'' is invalid. ' -f $providerName, $packageSourceRoot);
            Unregister-PackageSource -Name $packageSourceRootName;
        }
        else {
            Write-Debug ('In {0} Provider - ''Resolve-AppvPackageSourceRoot'' - Adding Package Source Root ''{1}''.' -f $providerName, $packageSourceRoot);
            Register-PackageSource -Name $packageSourceRootName -Location $packageSourceRoot -Trusted $true;
        }
    } #end if packageSourceRoot
    else {
        Unregister-PackageSource -Name $packageSourceRootName;
    }
} #end function Resolve-AppvPackageSourceRoot

function Install-Package { 
    <#
        .SYNOPSIS
            Registers the App-V application package located in
            the path specified.
    #>

    param (
        [Parameter(Mandatory)] [System.String] $FastPackageReference
    )
    Write-Debug ('In {0} Provider - ''Install-Package'' - Package reference ''{1}''.' -f $providerName, $FastPackageReference);
    if ($request.Options['Global'] -eq $true -and $request.Options['DynamicUserConfigurationPath'] -ne $null) {
        Write-Error -Message $localized.InvalidArgumentsError -Category InvalidArgument;
    }

    $publishAppvClientPackageParam = @{ };
    $addAppvClientPackageParam = @{
        Path = $FastPackageReference;
    };
    if ($request.Options['DynamicDeploymentConfiguration']) {
        $dynamicDeploymentConfiguration = $request.Options['DynamicDeploymentConfiguration'];
        Write-Debug ('In {0} Provider - ''Install-Package'' - Setting Dynamic Deployment Configuration ''{1}''.' -f $providerName, $dynamicDeploymentConfiguration);
        $addAppvClientPackageParam['DynamicDeploymentConfiguration'] = $dynamicDeploymentConfiguration;
    }

    if ($request.Options['Global'] -ne $null -and $request.Options['Global'] -eq $true) {
        $publishAppvClientPackageParam['Global'] = $true;
    }
    if ($request.Options['DynamicUserConfigurationPath']) {
        $dynamicUserConfigurationPath = $request.Options['DynamicUserConfigurationPath'];
        Write-Debug ('In {0} Provider - ''Install-Package'' - Setting Dynamic User Configuration Path ''{1}''.' -f $providerName, $dynamicUserConfigurationPath);
        $publishAppvClientPackageParam['DynamicUserConfigurationPath'] = $dynamicUserConfigurationPath;
    }
    if ($request.Options['DynamicUserConfigurationType']) {
        $dynamicUserConfigurationType = $request.Options['DynamicUserConfigurationType'];
        Write-Debug ('In {0} Provider - ''Install-Package'' - Setting Dynamic User Configuration Type ''{1}''.' -f $providerName, $dynamicUserConfigurationType);
        $publishAppvClientPackageParam['DynamicUserConfigurationType'] = $dynamicUserConfigurationType;
    }

    try {
        $appvPackage = Add-AppvClientPackage @addAppvClientPackageParam -ErrorAction Stop | Publish-AppvClientPackage @publishAppvClientPackageParam;
        if ($request.Options['Mount'] -ne $null -and $request.Options['Mount'] -eq $true) {
            Write-Debug ('In {0} Provider - ''Install-Package'' - Mounting package ''{1}''.' -f $providerName, $appvPackage.Name);
            $appvPackage | Mount-AppvClientPackage;
        }
        $softwareIdentityParam = @{
            FastPackageReference = $appvPackage.Path;
            Name = $appvPackage.Name;
            Version = $appvPackage.Version;
            VersionScheme = 'semver';
            Source = $appvPackage.Path;
            SearchKey = $appvPackage.PackageId;
        }
        Write-Output (New-SoftwareIdentity @softwareIdentityParam);
    }
    catch {
        Remove-AppvClientPackage -Package $appvPackage -ErrorAction SilentlyContinue;
        Write-Error -Message $localized.InstallAppvPackageError -Exception $_.Exception -Category NotInstalled;
    }    
} #end function Install-Package

function Uninstall-Package { 
    <#
        .SYNOPSIS
            Registers the App-V application package located in
            the path specified.
    #>

    param (
        [Parameter(Mandatory)] [System.String] $FastPackageReference
    )
    Write-Debug ('In {0} Provider - ''Uninstall-Package'' - Reference ''{1}''.' -f $providerName, $FastPackageReference);
    foreach ($appvPackage in (Get-AppvClientPackage -All | Where-Object { $_.Path -eq $FastPackageReference })) {
        if ($appvPackage.InUse) {
            Write-Error -Message ($localized.AppvPackageIsInUseError -f $appvPackage.Name) -Category InvalidOperation;
        }
        else {
            Write-Debug ('Removing package ''{0}''.' -f $appvPackage.Name);
            $softwareIdentityParam = @{
                FastPackageReference = $appvPackage.Path;
                Name = $appvPackage.Name;
                Version = $appvPackage.Version;
                VersionScheme = 'semver';
                Source = $appvPackage.Path;
                SearchKey = $appvPackage.PackageId;
            }
            $package = New-SoftwareIdentity @softwareIdentityParam;
            Remove-AppvClientPackage -PackageId $appvPackage.PackageId -VersionId $appvPackage.VersionId;
            Write-Output $package;
        } #end if
    } #end foreach appvPackage
} #end function Uninstall-Package

function Get-Feature { 
    <#
        .SYNOPSIS
            Returns metadata about what features are
            implemented by the Provider.
    #>

    param ()
    Write-Debug ('In {0} Provider - ''Get-Feature''.' -f $providerName);
    Write-Output (New-feature 'extensions' @('appv'));
} #end function Get-Feature

function Get-DynamicOptions {
    <#
        .SYNOPSIS
            Provides dynamic parameters for Package, Source and/or
            Install operations.
    #>

    param (
        [Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] $category
    )
    Write-Debug ('In {0} Provider - ''Get-DynamicOption'' for category ''{1}''.' -f $providerName, $category);
    switch ($category) {
        Provider {
            # options when the user is trying to specify a provider
        }
        Package {
            # options when the user is trying to specify a package
        }
        Source {
            #options when the user is trying to specify a source
        }    
        Install {
            #options for installation/uninstallation
            Write-Output (New-DynamicOption -Category $category -Name Global -ExpectedType ([Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType]::Switch) -IsRequired $false);
            Write-Output (New-DynamicOption -Category $category -Name Mount -ExpectedType ([Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType]::Switch) -IsRequired $false);
            Write-Output (New-DynamicOption -Category $category -Name DynamicDeploymentConfiguration -ExpectedType ([Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType]::String) -IsRequired $false);
            Write-Output (New-DynamicOption -Category $category -Name DynamicUserConfigurationPath -ExpectedType ([Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType]::String) -IsRequired $false);
            Write-Output (New-DynamicOption -Category $category -Name DynamicUserConfigurationType -ExpectedType ([Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType]::String) -IsRequired $false -PermittedValues @('UseDeploymentConfiguration','UseExistingConfiguration','UseNoConfiguration'));
        }
    } #end switch category
} #end function Get-DynamicOptions

# SIG # Begin signature block
# MIIaogYJKoZIhvcNAQcCoIIakzCCGo8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUTvuppu3zxvY2XUiQJVsq5Ykm
# nqqgghXYMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B
# AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG
# A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh
# d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg
# Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV
# UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu
# dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q
# WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC
# i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4
# ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3
# +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI
# fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd
# BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG
# CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro
# YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV
# HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y
# MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf
# plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y
# 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq
# IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3
# DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh
# dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD
# QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE
# BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT
# eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow
# mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0
# jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu
# ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh
# d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz
# C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB
# o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO
# BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw
# Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90
# cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx
# oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy
# bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV
# HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa
# 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH
# bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73
# BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR
# EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW
# yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu
# e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw
# ggaUMIIFfKADAgECAhAG8BXYFUYj6XmzRgEaZJSVMA0GCSqGSIb3DQEBBQUAMG8x
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xLjAsBgNVBAMTJURpZ2lDZXJ0IEFzc3VyZWQgSUQgQ29k
# ZSBTaWduaW5nIENBLTEwHhcNMTMwNDE3MDAwMDAwWhcNMTUwNzE2MTIwMDAwWjBg
# MQswCQYDVQQGEwJHQjEPMA0GA1UEBxMGT3hmb3JkMR8wHQYDVQQKExZWaXJ0dWFs
# IEVuZ2luZSBMaW1pdGVkMR8wHQYDVQQDExZWaXJ0dWFsIEVuZ2luZSBMaW1pdGVk
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dxm3r1cUKp7rYZBDAeo
# Lm0iLIgYuzeg7tC2mt7kEJfvGiSVx4/d3pYw2/GpDB08JjsoAYIfhWOuGtUf0RRy
# 5QcyrfWDCmLfUApf83/GJZrATWs1OPzdYEsLzVrx7ZtvcCVvlEIyG4RJmhSG2mZS
# 6P0D68a2/U4QmcNEGpnbTyszHds8BnVL1D3oQP+rcXN2jDP83/rECmGgYGexvRkV
# K/+HHrporgkT4KRMbrWXMRPrLQazIFeg1mnm1UtjxTXN7IPaY97qwxhxPqwpL3DH
# PdF/6+rC1ZQZ27akf5qporAlsftUe3URHFmmJ8NrLivANrwco9BY3If4iAvz9ipl
# mQIDAQABo4IDOTCCAzUwHwYDVR0jBBgwFoAUe2jOKarAF75JeuHlP9an90WPNTIw
# HQYDVR0OBBYEFNQ3nxxDKFobighYZExYqzXq8SQTMA4GA1UdDwEB/wQEAwIHgDAT
# BgNVHSUEDDAKBggrBgEFBQcDAzBzBgNVHR8EbDBqMDOgMaAvhi1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vYXNzdXJlZC1jcy0yMDExYS5jcmwwM6AxoC+GLWh0dHA6
# Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9hc3N1cmVkLWNzLTIwMTFhLmNybDCCAcQGA1Ud
# IASCAbswggG3MIIBswYJYIZIAYb9bAMBMIIBpDA6BggrBgEFBQcCARYuaHR0cDov
# L3d3dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsG
# AQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABD
# AGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABh
# AGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQBy
# AHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBn
# ACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABs
# AGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABp
# AG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBl
# AGYAZQByAGUAbgBjAGUALjCBggYIKwYBBQUHAQEEdjB0MCQGCCsGAQUFBzABhhho
# dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTAYIKwYBBQUHMAKGQGh0dHA6Ly9jYWNl
# cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENvZGVTaWduaW5nQ0Et
# MS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQUFAAOCAQEAPsyUuxkYkEGE
# 1bl4g3Muy5QxQq8frp34BPf+Sm6E9J915eBizW72ofbm08O9NkQvszbT4GTZaO/o
# SExSDbLIxHI98zi7AavVPuRpmVnfoF55yVomh/BYAU8vu0M7FvUeIhSAUfz0Q8PK
# wT5U+SdNoE7+xgxd4zHjBA3kUo3TZ+R/+MDd2Hzv6vrgxUfGeQfBCwafdEjD4pHr
# 0kvXcPq6VnQpsv92P3wvgsCrsTKIgtaNIfkGe5eCcTQ7pYTBauZl+XmyFvyiADKo
# 6Dng4jyuxYRP3EdCGVlZK7sEmiz1Y2f3zh0xoF58B3xXDnRJxo8ArlEAG8KzXn6w
# ryaA1vbgITCCBqMwggWLoAMCAQICEA+oSQYV1wCgviF2/cXsbb0wDQYJKoZIhvcN
# AQEFBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG
# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJl
# ZCBJRCBSb290IENBMB4XDTExMDIxMTEyMDAwMFoXDTI2MDIxMDEyMDAwMFowbzEL
# MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
# LmRpZ2ljZXJ0LmNvbTEuMCwGA1UEAxMlRGlnaUNlcnQgQXNzdXJlZCBJRCBDb2Rl
# IFNpZ25pbmcgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJx8
# +aCPCsqJS1OaPOwZIn8My/dIRNA/Im6aT/rO38bTJJH/qFKT53L48UaGlMWrF/R4
# f8t6vpAmHHxTL+WD57tqBSjMoBcRSxgg87e98tzLuIZARR9P+TmY0zvrb2mkXAEu
# sWbpprjcBt6ujWL+RCeCqQPD/uYmC5NJceU4bU7+gFxnd7XVb2ZklGu7iElo2NH0
# fiHB5sUeyeCWuAmV+UuerswxvWpaQqfEBUd9YCvZoV29+1aT7xv8cvnfPjL93Sos
# MkbaXmO80LjLTBA1/FBfrENEfP6ERFC0jCo9dAz0eotyS+BWtRO2Y+k/Tkkj5wYW
# 8CWrAfgoQebH1GQ7XasCAwEAAaOCA0MwggM/MA4GA1UdDwEB/wQEAwIBhjATBgNV
# HSUEDDAKBggrBgEFBQcDAzCCAcMGA1UdIASCAbowggG2MIIBsgYIYIZIAYb9bAMw
# ggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9zc2wtY3Bz
# LXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUA
# cwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMA
# bwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYA
# IAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQA
# IAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUA
# bQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkA
# dAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAA
# aABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMBIGA1UdEwEB
# /wQIMAYBAf8CAQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6
# MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFHtozimqwBe+SXrh
# 5T/Wp/dFjzUyMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqG
# SIb3DQEBBQUAA4IBAQB7ch1k/4jIOsG36eepxIe725SS15BZM/orh96oW4AlPxOP
# m4MbfEPE5ozfOT7DFeyw2jshJXskwXJduEeRgRNG+pw/alE43rQly/Cr38UoAVR5
# EEYk0TgPJqFhkE26vSjmP/HEqpv22jVTT8nyPdNs3CPtqqBNZwnzOoA9PPs2TJDn
# dqTd8jq/VjUvokxl6ODU2tHHyJFqLSNPNzsZlBjU1ZwQPNWxHBn/j8hrm574rpyZ
# lnjRzZxRFVtCJnJajQpKI5JA6IbeIsKTOtSbaKbfKX8GuTwOvZ/EhpyCR0JxMoYJ
# mXIJeUudcWn1Qf9/OXdk8YSNvosesn1oo6WQsQz/MYIENDCCBDACAQEwgYMwbzEL
# MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
# LmRpZ2ljZXJ0LmNvbTEuMCwGA1UEAxMlRGlnaUNlcnQgQXNzdXJlZCBJRCBDb2Rl
# IFNpZ25pbmcgQ0EtMQIQBvAV2BVGI+l5s0YBGmSUlTAJBgUrDgMCGgUAoHgwGAYK
# KwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIB
# BDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQU
# QyJ1YiXgwBGdaDif9nJ6sNi08fUwDQYJKoZIhvcNAQEBBQAEggEAhqs0Y+AYdQim
# MZs3IMtk7OTuVUkQ+aQTNRFE6YU71nyy94G5i2nMg1bXbVgDw3fmF71rGUd/3Y4Q
# wQQt2WURmzWyGsXTxW4p4UTA0fVE2zw/l8U2G5PQ+VNrTeK8OHIo0rNnXvmFydvw
# llew3Qt7RIKvKFcQK0PkNb09NloKuhMFF2ZfjgLg8UwMa++bTCceQAXTPS14kFYf
# jrNhSw+9sSitVgX4dDlGlX9bfFscMbPgQpE2m+qCx+/vRhlsGrLcDm2UjdySDYlW
# YZKc2yu3Kc5DXhvFvC+Vw7Du0CQQoP0hAOp3wW66d1QA0bcm0neT1a5dsYNl9Wjw
# RUqBjF92vaGCAgswggIHBgkqhkiG9w0BCQYxggH4MIIB9AIBATByMF4xCzAJBgNV
# BAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEwMC4GA1UEAxMn
# U3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBDQSAtIEcyAhAOz/Q4yP6/
# NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
# MBwGCSqGSIb3DQEJBTEPFw0xNTA2MDMwODQ0NDdaMCMGCSqGSIb3DQEJBDEWBBSJ
# mI9R6aY22ZZb1ojOjaQZo9oKUDANBgkqhkiG9w0BAQEFAASCAQAV585hpZNoePUq
# JGrvDj18y0t44I3BoohJVVuyEfiY7+WS0xVMnEXG2mks5xw6afFecJbD+1ALDPm4
# sBgmB273DRvozYbieiKLUS8c1WrN/wZS7iI7Xuh3u3x+QWs9+0FFA7acMhBclKXq
# ey+fiZ+bER9lddYiKVLhwWyaIxkyYdIERv+SY1aTg93670J9GcDjJQKmgSbDv8TF
# wdtErsFDebZWW+7+M7/YsaVy38nnHbmzDKa+jxq/jlh9/4qCy1G7oGDK36dlfu+r
# U35yg04BOpLNKoiBHqmwGq0INXKC2rvk/vzSZic/UA7yUZKZq2z7fBJBRkCbKMGt
# NV8MBWe3
# SIG # End signature block