Src/Public/Public.ps1

<#
    .SYNOPSIS
        Clears cached Virtual Engine Evergreen package files.
 
    .DESCRIPTION
        Removes all Evergreen package installer and metadata files from the '%ALLUSERSPROFILE%\VirtualEngine\Evergreen' directory.
 
    .EXAMPLE
        Clear-EvergreenPackageCache
 
        Removes all cached Evergreen packages and package metadata.
#>

function Clear-EvergreenPackageCache
{
    [CmdletBinding(SupportsShouldProcess)]
    param
    (
        ## Force cached file removal.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Force
    )
    process
    {
        Remove-Item -Path "$cachedMetadataPath\*" -Exclude 'AppDeployToolkit*' -Force:$Force -Recurse
    }
}

<#
    .SYNOPSIS
        Searches for available Virtual Engine Evergreen packages.
 
    .DESCRIPTION
        Queries the Virtual Engine Evergreen API for one or more available packages.
 
    .EXAMPLE
        Find-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890
 
        Name : 7-Zip-x64
        Version : 19.0
        Platform : Intune
        Uri : https://../7-Zip-x64/7-Zip-x64.19.0.intunewin?sv=2019-02-02
        MetadataUri : https://../7-Zip-x64/7-Zip-x64.19.0.intunewin.metadata?sv=2019-02-02
 
        Name : 7-Zip-x64
        Version : 19.0
        Platform : Zip
        Uri : https://../7-Zip-x64/7-Zip-x64.19.0.zip?sv=2019-02-02
        MetadataUri :
 
        Searches for the latest 7-Zip-x64 package.
 
    .EXAMPLE
        Find-EvergreenPackage -Name 7-Zip-x64 -Platform Zip
 
        Name : 7-Zip-x64
        Version : 19.0
        Platform : Zip
        Uri : https://../7-Zip-x64/7-Zip-x64.19.0.zip?sv=2019-02-02
        MetadataUri :
 
        Searches for the latest 7-Zip-x64 archive package.
 
    .EXAMPLE
        Find-EvergreenPackage -Name Google-Chrome-Enterprise-x64 -Platform Intune -All
 
        Name : Google-Chrome-Enterprise-x64
        Version : 81.0.4044.129
        Platform : Intune
        Uri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.81.0.4044.129.intunewin?sv=2019-02-02
        MetadataUri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.81.0.4044.129.intunewin.metadata?sv=2019-02-02
 
        Name : Google-Chrome-Enterprise-x64
        Version : 81.0.4044.138
        Platform : Intune
        Uri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.81.0.4044.138.intunewin?sv=2019-02-02
        MetadataUri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.81.0.4044.138.intunewin.metadata?sv=2019-02-02
 
        Name : Google-Chrome-Enterprise-x64
        Version : 81.0.4044.92
        Platform : Intune
        Uri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.81.0.4044.92.intunewin?sv=2019-02-02
        MetadataUri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.81.0.4044.92.intunewin.metadata?sv=2019-02-02
 
        Name : Google-Chrome-Enterprise-x64
        Version : 83.0.4103.61
        Platform : Intune
        Uri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.83.0.4103.61.intunewin?sv=2019-02-02
        MetadataUri : https://../Google-Chrome-Enterprise-x64/Google-Chrome-Enterprise-x64.83.0.4103.61.intunewin.metadata?sv=2019-02-02
 
        Searches for all available Google-Chrome-Enterprise-x64 Intune packages.
#>

function Find-EvergreenPackage
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSObject])]
    param
    (
        ## Evergreen package(s) to search for.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String[]] $Name,

        ## Virtual Engine Evergreen Api subscription key.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('Key')]
        [System.String] $SubscriptionKey,

        ## Enumerate customer package repository.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $CustomerCode,

        ## Evergreen package format.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Exe', 'Zip', 'Intune')]
        [System.String] $Platform,

        ## Return all available package versions.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $All,

        ## Include hidden (test) packages.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Hidden,

        ## Web proxy credential required to access Virtual Engine Evergreen Api.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential
    )
    process
    {
        $invokeEvergreenPackageApiParams = @{
            SubscriptionKey = $SubscriptionKey
            Hidden          = $Hidden
            All             = $All
        }
        if ($PSBoundParameters.ContainsKey('CustomerCode'))
        {
            $invokeEvergreenPackageApiParams['CustomerCode'] = $CustomerCode
        }
        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $invokeEvergreenPackageApiParams['Credential'] = $Credential
        }
        if ($PSBoundParameters.ContainsKey('Platform'))
        {
            $invokeEvergreenPackageApiParams['Platform'] = $Platform
        }
        if ($PSBoundParameters.ContainsKey('Name'))
        {
            foreach ($packageName in $Name)
            {
                $invokeEvergreenPackageApiParams['Name'] = $packageName
                $packages = Invoke-EvergreenPackageApi @invokeEvergreenPackageApiParams

                ## Evergreen API returns an empty string if there are no matching packages
                if ($packages -is [System.String])
                {
                    Write-Error ($localized.CannotFindPackageError -f $packageName)
                }
                elseif (($packages -is [System.Array]) -and ($packages.Count -eq 0))
                {
                    Write-Error ($localized.CannotFindPackageError -f $packageName)
                }
                else
                {
                    $packages | ForEach-Object {

                        $package = [PSCustomObject] @{
                            Name        = $_.Name
                            Description = $_.Description
                            Version     = $_.Version -as [System.Version]
                            Revision    = $_.Revision -as [System.Int32]
                            Platform    = $_.Platform
                            PublishDate = $_.Date
                            Uri         = $_.Uri
                            MetadataUri = $_.MetadataUri
                            IsCustom    = $PSBoundParameters.ContainsKey('CustomerCode')
                        }
                        $package.PSObject.TypeNames.Insert(0, 'VirtualEngine.Evergreen.Package')
                        Write-Output -InputObject $package
                    }
                }
            }
        }
        else
        {
            $packages = Invoke-EvergreenPackageApi @invokeEvergreenPackageApiParams
            if (($packages -is [System.Object[]]) -and ($packages[0] -isnot [System.Management.Automation.PSObject]))
            {
                ## Deserialisation sometimes results in an [Object[]] with a nested [PSObject]s
                $packages[0] | ForEach-Object {

                    $package = [PSCustomObject] @{
                        Name        = $_.Name
                        Description = $_.Description
                        Version     = $_.Version -as [System.Version]
                        Revision    = $_.Revision -as [System.Int32]
                        Platform    = $_.Platform
                        PublishDate = $_.Date
                        Uri         = $_.Uri
                        MetadataUri = $_.MetadataUri
                        IsCustom    = $PSBoundParameters.ContainsKey('CustomerCode')
                    }
                    $package.PSObject.TypeNames.Insert(0, 'VirtualEngine.Evergreen.Package')
                    Write-Output -InputObject $package
                }
            }
            else
            {
                $packages | ForEach-Object {

                    $package = [PSCustomObject] @{
                        Name        = $_.Name
                        Description = $_.Description
                        Version     = $_.Version -as [System.Version]
                        Revision    = $_.Revision -as [System.Int32]
                        Platform    = $_.Platform
                        PublishDate = $_.Date
                        Uri         = $_.Uri
                        MetadataUri = $_.MetadataUri
                        IsCustom    = $PSBoundParameters.ContainsKey('CustomerCode')
                    }
                    $package.PSObject.TypeNames.Insert(0, 'VirtualEngine.Evergreen.Package')
                    Write-Output -InputObject $package
                }
            }
        }
    }
}

<#
    .SYNOPSIS
        Returns Virtual Engine Evergreen package metadata.
 
    .DESCRIPTION
        Returns the cached Virtual Engine Evergreen package metadata. Downloads package metadata to the local file system if necessary.
#>

function Get-EvergreenPackageCacheMetadata
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        ## Evergreen package name.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $Name,

        ## Evergreen Api subscription key.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $SubscriptionKey,

        ## Enumerate customer package repository.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $CustomerCode,

        ## Web proxy credential required to access Virtual Engine Evergreen Api.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        ## Force refresh of cached package metadata.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Force
    )
    process
    {
        Write-Verbose -Message ($localized.RetrievingPackageMetadata -f $Name)
        try
        {
            $destinationFilename = '{0}.psd1' -f $Name
            $destinationPath = Join-Path -Path $cachedMetadataPath -ChildPath $destinationFilename
            $metadataPath = (Get-Item -Path $destinationPath -ErrorAction SilentlyContinue).FullName
            if ($Force -or ($null -eq $metadataPath))
            {
                $invokeEvergreenMetadataDownloadParams = @{
                    Name            = $Name
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $invokeEvergreenMetadataDownloadParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $invokeEvergreenMetadataDownloadParams['Credential'] = $Credential
                }
                $metadataPath = (Invoke-EvergreenMetadataDownload @invokeEvergreenMetadataDownloadParams).FullName
            }
            else
            {
                Write-Verbose -Message ($localized.UsingCachedPackage -f $metadataPath)
            }

            return Import-PowerShellDataFile -Path $metadataPath
        }
        catch
        {
            Write-Error $_
        }
    }
}

<#
    .SYNOPSIS
        Installs one or more Virtual Engine Evergreen package(s).
 
    .DESCRIPTION
        Downloads Virtual Engine Evergreen packages to the local file system and intitiates the install.
 
    .EXAMPLE
        Install-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890
 
        Downloads and installs the latest '7-Zip-x64' Evergreen package.
 
    .EXAMPLE
        Install-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890 -Verify
 
        Downloads, installs the latest '7-Zip-x64' Evergreen package and tests the installation is detected successfully.
        Throws an error when no Intune detection rule has been defined or defined Intune detection rule(s) fail.
 
    .EXAMPLE
        Install-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890 -Verify -IgnoreEmptyDetectionRule
 
        Downloads, installs the latest '7-Zip-x64' Evergreen package and tests the installation is detected successfully.
        Does not throw an error when no Intune detection rule has been defined.
#>

function Install-EvergreenPackage
{
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
    param
    (
        ## Evergreen package(s) to install.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String[]] $Name,

        ## Evergreen Api subscription key.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $SubscriptionKey,

        ## Run the application's detection rules after installation to verify the application is detected as installed.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Verify')]
        [System.Management.Automation.SwitchParameter] $Verify,

        ## Enumerate customer package repository.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $CustomerCode,

        ## Custom package installation arguments.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $Arguments,

        ## Web proxy credential required to access Virtual Engine Evergreen Api.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        ## Force refresh of cached package metadata.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Force,

        ## Does not fail application verification if no detection rules are defined.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Verify')]
        [System.Management.Automation.SwitchParameter] $IgnoreEmptyDetectionRule,

        ## Download retry attempts.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Uint32] $Retry = 3
    )
    process
    {
        foreach ($packageName in $Name)
        {
            try
            {
                ## Cache metadata file to ensure any future Test-EvergreenPackage uses the
                ## detection rules used when initially installing the package
                $assertEvergreenPackageCacheMetadataParams = @{
                    Name            = $packageName
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force.ToBool()
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $assertEvergreenPackageCacheMetadataParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $assertEvergreenPackageCacheMetadataParams['Credential'] = $Credential
                }
                Assert-EvergreenPackageCacheMetadata @assertEvergreenPackageCacheMetadataParams

                ## Test to see if the package is already installed (assuming detection rules are defined)
                $isEvergreenPackageInstalled = Test-EvergreenPackage @assertEvergreenPackageCacheMetadataParams -IgnoreEmptyDetectionRule
                if ($null -eq $isEvergreenPackageInstalled)
                {
                    ## Unable to determine installation state
                    if ($Verify -and (-not $IgnoreEmptyDetectionRule))
                    {
                        ## We have a package with no detection rules and we are not ignoring them
                        throw ($localized.CannotFindPackageDetectionRuleError -f $packageName)
                    }
                }
                elseif ($isEvergreenPackageInstalled -eq $true)
                {
                    ## Already installed/present
                    continue
                }

                ## Ensure the binaries are cached
                $assertEvergreenPackageDownloadParams = @{
                    Name            = $packageName
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force.ToBool()
                    Retry           = $Retry
                    ErrorAction     = 'Stop'
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $assertEvergreenPackageDownloadParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $assertEvergreenPackageDownloadParams['Credential'] = $Credential
                }
                $package = Assert-EvergreenPackageDownload @assertEvergreenPackageDownloadParams

                ## Start the installation
                $customArguments = @()
                if ($null -eq $package.TempPath)
                {
                    ## We have a self-extracting .EXE
                    $customArguments += '-y'
                }
                $customArguments += '-DeploymentType','Install'
                if ($PSBoundParameters.ContainsKey('Arguments'))
                {
                    $customArguments += $Arguments
                }

                $startWaitProcessParams = @{
                    FilePath     = $package.Path
                    ArgumentList = $customArguments
                }
                Write-Verbose -Message ($localized.InstallingPackage -f $packageName)
                $null = Start-WaitProcess @startWaitProcessParams

                if ($Verify)
                {
                    $assertEvergreenPackageCacheMetadataParams['IgnoreEmptyDetectionRule'] = $IgnoreEmptyDetectionRule.ToBool()
                    $isEvergreenPackageInstalled = Test-EvergreenPackage @assertEvergreenPackageCacheMetadataParams
                    if ($isEvergreenPackageInstalled -eq $false)
                    {
                        throw ($localized.PackageIsNotInstalled -f $packageName)
                    }
                }
            }
            catch
            {
                Write-Error $_
            }
            finally
            {
                ## Clear up temp files, folders and downloads
                if ($null -ne $package)
                {
                    if (($null -ne $package.TempPath) -and (Test-Path -Path $package.TempPath))
                    {
                        Write-Verbose -Message ($localized.RemoveTemporaryDirectory -f $package.TempPath)
                        Remove-Item -Path $package.TempPath -Force -Recurse -WhatIf:$false
                    }
                }
            }
        }
    }
}

<#
    .SYNOPSIS
        Saves Virtual Engine Evergreen packages.
 
    .DESCRIPTION
        Downloads Virtual Engine evergreen packages to the local file system.
 
    .EXAMPLE
        Save-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890
 
        Directory: C:\Users\User\Documents
 
        Mode LastWriteTime Length Name
        ---- ------------- ------ ----
        -a---- 20/05/2020 12:56 2095648 7-Zip-x64.19.0.intunewin
        -a---- 20/05/2020 12:56 17519 7-Zip-x64.19.0.intunewin.metadata
        -a---- 20/05/2020 12:56 2056278 7-Zip-x64.19.0.zip
 
        Saves all available formats of the latest 7-Zip-x64 packages to the current path.
 
    .EXAMPLE
        Save-EvergreenPackage -Name 7-Zip-x64,KeePass -SubscriptionKey abcdef1234567890 -Platform Zip -Path ~\Downloads
 
        Directory: C:\Users\User\Downloads
 
        Mode LastWriteTime Length Name
        ---- ------------- ------ ----
        -a---- 20/05/2020 13:41 2056278 7-Zip-x64.19.0.zip
        -a---- 20/05/2020 13:41 3533503 KeePass.2.45.zip
 
        Saves the latest 7-Zip-x64 and KeePass archive packages to the user's Downloads directory.
#>

function Save-EvergreenPackage
{
    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    param
    (
        ## Evergreen package name.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String[]] $Name,

        ## Evergreen Api subscription key.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $SubscriptionKey,

        ## Enumerate customer package repository.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $CustomerCode,

        ## Evergreen package format.
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateSet('Exe', 'Zip', 'Intune')]
        [System.String] $Platform,

        ## Return all available package versions.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $All,

        ## File path to save the packages.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $Path = (Get-Location -PSProvider Filesystem).Path,

        ## Web proxy credential required to access Virtual Engine Evergreen Api.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        ## Overwrite any existing file(s).
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Force,

        ## Download retry attempts.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Uint32] $Retry = 3,

        ## Save package metadata.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Metadata
    )
    begin
    {
        if (-not (Test-Path -Path $Path -PathType Container))
        {
            throw ($localized.InvalidDestinationPathError -f $Path)
        }
    }
    process
    {
        $null = $PSBoundParameters.Remove('Path')
        $null = $PSBoundParameters.Remove('Force')
        $null = $PSBoundParameters.Remove('Metadata')
        $null = $PSBoundParameters.Remove('Retry')
        $availablePackages = Find-EvergreenPackage @PSBoundParameters

        foreach ($package in $availablePackages)
        {
            if ($Metadata)
            {
                $destinationPath = Join-Path -Path $Path -ChildPath ($package.MetadataUri -as [System.Uri]).AbsolutePath.Split('/')[-1]
            }
            else
            {
                $destinationPath = Join-Path -Path $Path -ChildPath ($package.Uri -as [System.Uri]).AbsolutePath.Split('/')[-1]
            }

            if (-not $Force -and (Test-Path -Path $destinationPath))
            {
                Write-Verbose -Message ($localized.UsingCachedPackage -f $destinationPath)
                Get-Item -Path $destinationPath
            }
            else
            {
                if (-not $Metadata)
                {
                    ## Download primary package
                    $uri = $package.Uri -as [System.Uri]
                    $filename = $uri.AbsolutePath.Split('/')[-1]
                    Write-Verbose -Message ($localized.DownloadingActivity -f $filename)
                    $invokeWebClientDownloadParams = @{
                        Uri             = $package.Uri
                        DestinationPath = $destinationPath
                        Retry           = $Retry
                    }
                    if ($PSBoundParameters.ContainsKey('Credential'))
                    {
                        $invokeWebClientDownloadParams['Credential'] = $Credential
                    }
                    Invoke-WebClientDownload @invokeWebClientDownloadParams
                }

                if ($Metadata -or ($Platform -eq 'Intune'))
                {
                    $uri = $package.MetadataUri -as [System.Uri]
                    $filename = $uri.AbsolutePath.Split('/')[-1]
                    $destinationPath = Join-Path -Path $Path -ChildPath $filename
                    if (-not $Force -and (Test-Path -Path $destinationPath))
                    {
                        Write-Verbose -Message ($localized.UsingCachedPackage -f $destinationPath)
                        Get-Item -Path $destinationPath
                    }
                    else
                    {
                        Write-Verbose -Message ($localized.DownloadingActivity -f $filename)
                        $invokeWebClientDownloadParams = @{
                            Uri             = $package.MetadataUri
                            DestinationPath = $destinationPath
                            Retry           = $Retry
                        }
                        if ($PSBoundParameters.ContainsKey('Credential'))
                        {
                            $invokeWebClientDownloadParams['Credential'] = $Credential
                        }
                        Invoke-WebClientDownload @invokeWebClientDownloadParams
                    }
                }
            }
        }
    }
}

<#
    .SYNOPSIS
        Tests whether one or more Virtual Engine Evergreen packages are installed.
 
    .DESCRIPTION
        Downloads Virtual Engine Evergreen package metadata to the local file system to test whether the package is installed (or not).
 
    .EXAMPLE
        Test-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890
 
        False
 
        Downloads and latest '7-Zip-x64' Evergreen package metadata and tests wheter the package is installed.
#>

function Test-EvergreenPackage
{
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Boolean])]
    param
    (
        ## Evergreen package name.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.String[]] $Name,

        ## Evergreen Api subscription key.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.String] $SubscriptionKey,

        ## Enumerate customer package repository.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.String] $CustomerCode,

        ## Evergreen package metadata file path.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Metadata')]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        [System.Collections.Hashtable] $Metadata,

        ## Test all applications in the local cache.
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Cached')]
        [System.Management.Automation.SwitchParameter]
        $Cached,

        ## Web proxy credential required to access Virtual Engine Evergreen Api.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        ## Does not fail application test if no detection rules are defined.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $IgnoreEmptyDetectionRule,

        ## Force refresh of cached package metadata.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.SwitchParameter] $Force
    )
    process
    {
        $metadataCollection = @()

        if ($PSCmdlet.ParameterSetName -eq 'Cached')
        {
            $cachedMetadataItems = Get-ChildItem -Path $cachedMetadataPath -Filter '*.psd1'
            foreach ($cachedMetadataItem in $cachedMetadataItems)
            {
                $metadataCollection += Import-PowerShellDataFile -Path $cachedMetadataItem.FullName
            }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'Default')
        {
            foreach ($packageName in $Name)
            {
                $getEvergreenPackageCacheMetadataParams = @{
                    Name            = $packageName
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force.ToBool()
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $getEvergreenPackageCacheMetadataParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $getEvergreenPackageCacheMetadataParams['Credential'] = $Credential
                }
                $metadataCollection += Get-EvergreenPackageCacheMetadata @getEvergreenPackageCacheMetadataParams
            }
        }
        else
        {
            $metadataCollection += $Metadata
        }

        $isCompliant = $true

        foreach ($metadataItem in $metadataCollection)
        {
            $packageName = Get-PackageMetadataPackageIdentifier -Metadata $metadataItem
            Write-Verbose -Message ($localized.TestingPackage -f $packageName)
            $testPackageDetectionRuleParams = @{
                Metadata                 = $metadataItem
                PackageName              = $packageName
                IgnoreEmptyDetectionRule = $IgnoreEmptyDetectionRule.ToBool()
            }
            $isInstalled = Test-PackageDetectionRule @testPackageDetectionRuleParams

            if (($null -eq $isInstalled) -and ($IgnoreEmptyDetectionRule -eq $true))
            {
                Write-Warning -Message ($localized.UnableToDetermineInstallStateWarning -f $packageName)
                $isCompliant = $null
            }
            elseif ($isInstalled -eq $true)
            {
                Write-Verbose -Message ($localized.PackageIsInstalled -f $packageName)
            }
            else
            {
                Write-Verbose -Message ($localized.PackageIsNotInstalled -f $packageName)
                $isCompliant = $false
            }
        }

        return $isCompliant
    }
}

<#
    .SYNOPSIS
        Uninstalls one or more Virtual Engine Evergreen package(s).
 
    .DESCRIPTION
        Downloads Virtual Engine Evergreen packages to the local file system and intitiates the uninstall.
 
    .EXAMPLE
        Uninstall-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890
 
        Downloads and uninstalls the '7-Zip-x64' package.
 
    .EXAMPLE
        Uninstall-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890 -Verify
 
        Uninstalls the cached '7-Zip-x64' Evergreen package and tests the installation is no longer detected successfully.
        Throws an error when no Intune detection rule has been defined or defined Intune detection rule(s) pass.
 
    .EXAMPLE
        Uninstall-EvergreenPackage -Name 7-Zip-x64 -SubscriptionKey abcdef1234567890 -Verify -IgnoreEmptyDetectionRule
 
        Uninstalls the cached '7-Zip-x64' Evergreen package and tests the installation is no longer detected successfully.
        Does not throw an error when no Intune detection rule has been defined.
#>

function Uninstall-EvergreenPackage
{
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
    param
    (
        ## Evergreen package(s) to uninstall.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String[]] $Name,

        ## Evergreen Api subscription key.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $SubscriptionKey,

        ## Run the application's detection rules after installation to verify the application is detected as installed.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Verify')]
        [System.Management.Automation.SwitchParameter] $Verify,

        ## Enumerate customer package repository.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $CustomerCode,

        ## Custom package installation arguments.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $Arguments,

        ## Web proxy credential required to access Virtual Engine Evergreen Api.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        ## Force refresh of cached package metadata.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Force,

        ## Does not fail application verification if no detection rules are defined.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Verify')]
        [System.Management.Automation.SwitchParameter] $IgnoreEmptyDetectionRule,

        ## Download retry attempts.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Uint32] $Retry = 3,

        ## Cached files are removed after uninstall if the process exit code matches/is successful.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.UInt32[]] $ValidExitCodes = @(0, 3010)
    )
    process
    {
        foreach ($packageName in $Name)
        {
            try
            {
                ## Cache metadata file to ensure any future Test-EvergreenPackage uses the
                ## detection rules used when initially installing the package
                $assertEvergreenPackageCacheMetadataParams = @{
                    Name            = $packageName
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force.ToBool()
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $assertEvergreenPackageCacheMetadataParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $assertEvergreenPackageCacheMetadataParams['Credential'] = $Credential
                }
                Assert-EvergreenPackageCacheMetadata @assertEvergreenPackageCacheMetadataParams

                ## Test to see if the package is already installed (assuming detection rules are defined)
                $isEvergreenPackageInstalled = Test-EvergreenPackage @assertEvergreenPackageCacheMetadataParams -IgnoreEmptyDetectionRule
                if ($null -eq $isEvergreenPackageInstalled)
                {
                    ## Unable to determine installation state
                    if ($Verify -and (-not $IgnoreEmptyDetectionRule))
                    {
                        ## We have a package with no detection rules and we are not ignoring them
                        throw ($localized.CannotFindPackageDetectionRuleError -f $packageName)
                    }
                }
                elseif ($isEvergreenPackageInstalled -eq $false)
                {
                    ## Already uninstalled/not present
                    continue
                }

                ## Ensure the binaries are cached
                $assertEvergreenPackageDownloadParams = @{
                    Name            = $packageName
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force.ToBool()
                    Retry           = $Retry
                    ErrorAction     = 'Stop'
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $assertEvergreenPackageDownloadParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $assertEvergreenPackageDownloadParams['Credential'] = $Credential
                }
                $package = Assert-EvergreenPackageDownload @assertEvergreenPackageDownloadParams

                ## Start the uninstallation
                $customArguments = @()
                if ($null -eq $package.TempPath)
                {
                    ## We have a self-extracting .EXE
                    $customArguments += '-y'
                }
                $customArguments += '-DeploymentType','Uninstall'
                if ($PSBoundParameters.ContainsKey('Arguments'))
                {
                    $customArguments += $Arguments
                }

                $startWaitProcessParams = @{
                    FilePath     = $package.Path
                    ArgumentList = $customArguments
                }
                Write-Verbose -Message ($localized.UninstallingPackage -f $package.Name)
                $exitCode = Start-WaitProcess @startWaitProcessParams

                if ($Verify)
                {
                    $assertEvergreenPackageCacheMetadataParams['IgnoreEmptyDetectionRule'] = $IgnoreEmptyDetectionRule.ToBool()
                    $isEvergreenPackageInstalled = Test-EvergreenPackage @assertEvergreenPackageCacheMetadataParams
                    if ($isEvergreenPackageInstalled -eq $true)
                    {
                        throw ($localized.PackageIsInstalled -f $packageName)
                    }
                }

                if ($exitCode -in $ValidExitCodes)
                {
                    ## Remove any cached metadata files to ensure any future Test-EvergreenPackage
                    ## detection rules are downloaded again
                    $assertEvergreenPackageCacheMetadataParams = @{
                        Name            = $packageName
                        SubscriptionKey = $SubscriptionKey
                        Force           = $true
                        NotPresent      = $true
                    }
                    Assert-EvergreenPackageCacheMetadata @assertEvergreenPackageCacheMetadataParams
                }
            }
            catch
            {
                Write-Error $_
            }
            finally
            {
                ## Clear up temp files, folders and downloads
                if ($null -ne $package)
                {
                    if (($null -ne $package.TempPath) -and (Test-Path -Path $package.TempPath))
                    {
                        Write-Verbose -Message ($localized.RemoveTemporaryDirectory -f $package.TempPath)
                        Remove-Item -Path $package.TempPath -Force -Recurse -WhatIf:$false
                    }
                }
            }
        }
    }
}


# SIG # Begin signature block
# MIIuwAYJKoZIhvcNAQcCoIIusTCCLq0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBHEWWdMwA+ydvg
# zImSnYq8UPnKFTZxZmhfHcFRnEyw8qCCE6QwggWQMIIDeKADAgECAhAFmxtXno4h
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggdYMIIFQKADAgECAhAIfHT3o/FeY5ksO94AUhTmMA0GCSqGSIb3DQEBCwUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjMxMDE4MDAwMDAwWhcNMjYxMjE2MjM1OTU5WjBgMQsw
# CQYDVQQGEwJHQjEPMA0GA1UEBxMGTG9uZG9uMR8wHQYDVQQKExZWaXJ0dWFsIEVu
# Z2luZSBMaW1pdGVkMR8wHQYDVQQDExZWaXJ0dWFsIEVuZ2luZSBMaW1pdGVkMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtyhrsCMi6pgLcX5sWY7I09dO
# WKweRHfDwW5AN6ffgLCYO9dqWWxvqu95FqnNVRyt1VNzEl3TevKVhRE0GGdirei3
# VqnFFjLDwD2jHhGY8qoSYyfffj/WYq2DkvNI62C3gUwSeP3FeqKRalc2c3V2v4jh
# yEYhrgG3nfnWQ/Oq2xzuiCqHy1E4U+IKKDtrXls4JX2Z4J/uAHZIAyKfrcTRQOhZ
# R4ZS1cQkeSBU9Urx578rOmxL0si0GAoaYQC49W7OimRelbahxZw/R+f5ch+C1ycU
# CpeXLg+bFhpa0+EXnkGidlILZbJiZJn7qvMQTZgipQKZ8nhX3rtJLqTeodPWzcCk
# tXQUy0q5fxhR3e6Ls7XQesq/G2yMcCMTCd6eltze0OgabvL6Xkhir5lATHaJtnmw
# FlcKzRr1YXK1k1D84hWfKSAdUg8T1O72ztimbqFLg6WoC8M2qqsHcm2DOc9hM3i2
# CWRpegikitRvZ9i1wkA9arGh7+a7UD+nLh2hnGmO06wONLNqABOEn4JOVnNrQ1gY
# eDeH9FDx7IYuAvMsfXG9Bo+I97TR2VfwDAx+ccR+UQLON3aQyFZ3BefYnvUu0gUR
# ikEAnAS4Jnc3BHizgb0voz0iWRDjFoTTmCmrInCVDGc+5KMy0xyoUwdQvYvRGAWB
# 61OCWnXBXbAEPniTZ80CAwEAAaOCAgMwggH/MB8GA1UdIwQYMBaAFGg34Ou2O/hf
# EYb7/mF7CIhl9E5CMB0GA1UdDgQWBBRuAv58K4EDYLmb7WNcxt5+r4NfnzA+BgNV
# HSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMD
# MIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEu
# Y3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDCBlAYIKwYB
# BQUHAQEEgYcwgYQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
# bTBcBggrBgEFBQcwAoZQaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcnQw
# CQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnXMg6efkBrwLIvd1Xmuh0dam
# 9FhUtDEj+P5SIqdP/U4veOv66NEQhBHLbW2Dvrdm6ec0HMj9b4e8pt4ylKFzHIPj
# fpuRffHVR9JQSx8qpryN6pP49DfCkAYeZGqjY3pGRzd/xQ0cfwcuYbUF+vwVk7tj
# q8c93VHCM0rb5M4N2hD1Ze1pvZxtaf9QnFKFzgXZjr02K6bswQc2+n5jFCp7zV1f
# KTstyb68rhSJBWKK1tMeFk6a6HXr5buTD3skluC0oyPmD7yAd97r2owjDMEveEso
# kADP/z7XQk7wqbwbpi4W6Uju2qHK/9UUsVRF5KTVEAIzVw2V1Aq/Jh3JuSV7b7C1
# 4CghNekltBb+w7YVp8/IFcj7axqnpNQ/+f7RVc3A5hyjV+MkoSwn8Sg7a7hn6SzX
# jec/TfRVvWCmG94MQHko+6206uIXrZnmQ6UQYFyOHRlyKDEozzkZhIcVlsZloUjL
# 3FZ5V/l8TIIzbc3bkEnu4iByksNvRxI6c5264OLauYlWv50ZUPwXmZ9gX8bs3BqZ
# avbGUrOW2PIjtWhtvs4zHhMBCoU1Z0OMvXcF9rUDqefmVCZK46xz3DGKVkDQnxY6
# UWQ3GL60/lEzju4YS99LJVQks2UGmP6LAzlEZ1dnGqi1aQ51OidCYEs39B75PsvO
# By2iAR8pBVi/byWBypExghpyMIIabgIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBH
# NCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTECEAh8dPej8V5j
# mSw73gBSFOYwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA
# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgDzBLT0pqIBjtUdDMwbxb68nU
# hKF1D006llMzDNhoYc4wDQYJKoZIhvcNAQEBBQAEggIATItlTEevgW2ryuigWyfS
# t5xir7AWJLNCHUXiJQl0JNf4yDewz/fTdrk5wcMRGHXCUH+4247KyMYKVSBgDRbN
# 3ddqxZuILyW9M91wqaeagRAU7VJ53U4l4GPX7VeALYmhTG0JPotqvHrGCXLhZKvB
# n3FvLGdYcDVIAo2jy3UjseYcs7EFKXDLg63FguohjIcXtEdxAQhCiwOlquFBPjPP
# RYgOiZvxbaYBOkTpT7oojLhrzZSmRSTSQWH/FPxTn2srU/X3uEGLKvhXXgq+kRvM
# eIrJ+MsHNMCIU7AoZ05l9U1q2uIJ7vlHj4ZjzuB4AZBwBETy+ltPHGbBUyXLGbUW
# RdNnwiYRCX7qjpPB2CkmZJ2BTFf32K31nc0leJ3I3KWZA80Flp8nKC3FOXScedO8
# 3vQEh5tozbk5n4DUnB72Be3JsSdqtt1eUdva6naIPa1E6PoVgMTvXaU596TpB1BN
# sxa7mwZE68z1wauz7eF03ssDdBmiak/5yyIXAUGTV8vBheEWsq3qMTidyxgkQeMm
# eJP88lbxO/72aIMa4EqfnHDAwdxSzmMi9Q27nx0w2wTPrhx+L2Q1BopcTUaKIJDd
# dsr7UUKWqNEbyIcUp7i7MnkmqIi6CD1ZbLS/L3+vlvt79/5qp9NQptTLrXsEdlcC
# FvWrFtFUhwaTaCwymKtcwQChghc/MIIXOwYKKwYBBAGCNwMDATGCFyswghcnBgkq
# hkiG9w0BBwKgghcYMIIXFAIBAzEPMA0GCWCGSAFlAwQCAQUAMHcGCyqGSIb3DQEJ
# EAEEoGgEZjBkAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgWDz3LQ7v
# OSlphqKH5il7amBBjq8567vSMhLnZ7F481QCEEUAfV+utSmIDxi99GB+cbQYDzIw
# MjMxMjEzMTIwODMwWqCCEwkwggbCMIIEqqADAgECAhAFRK/zlJ0IOaa/2z9f5WEW
# MA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy
# dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNI
# QTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0MDAwMDAwWhcNMzQxMDEzMjM1
# OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xIDAe
# BgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIzMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DPn9fl0uddoQ4J3C9Io5d6Oyqc
# Z9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX65RQjxwg6seaOy+WZuNp52n+
# W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSkghraarrYO8pd3hkYhftF6g1h
# bJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2SC1eRXWWdf7dEKEbg8G45lKV
# tUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG7rSkIWRw69XloNpjsy7pBe6q
# 9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k/XtzPjLuUjT71Lvr1KAsNJvj
# 3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP4FhB+9ixLOFRr7StFQYU6mII
# E9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7Qv1zfe7dCv95NBB+plwKWEwA
# PoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOuprAbD3+yqG7HtSOKmYCaFxsmxx
# rz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBxPZySkwS0aXAnDU+3tTbRyV8I
# pHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E42FEHypS34lCh8zrTioPLQHsC
# AwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1Ud
# JQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG
# /WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAdBgNVHQ4EFgQU
# pbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1NlRp
# bWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYIKwYBBQUHMAGG
# GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZMaHR0cDovL2Nh
# Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5NlNIQTI1
# NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAgRrW3qCptZgX
# vHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn48XtJoKKcS8Y3U623mzX4WCc
# K+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S2sJAOJ9dyKAuJXglnSoFeoQp
# mLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedtQVyMadG5K8TGe8+c+njikxp2
# oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6ubzBaRm6zxbygzc0brBBJt3e
# WpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXhRsUo063nQwBw3syYnhmJA+rU
# kTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC1o/jF5HRqsBV44a/rCcsQdCa
# M0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH5U81PAC9vpwqbHkB3NpE5jre
# ODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLKO+uagjVXKBbLafIymrLS2Dq4
# sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc9bahuEMs305MfR5ocMB3CtQC
# 4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdEQcm4RtNsMnxYL2dHZeUbc7aZ
# +WssBkbvQR7w8F/g29mtkIBEr4AQQYowggauMIIElqADAgECAhAHNje3JFR82Ees
# /ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT
# GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAz
# MjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j
# LjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBU
# aW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG
# hjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6
# ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/
# qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3Hxq
# V3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVj
# bOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcp
# licu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZ
# girHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZG
# s506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHz
# NklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2
# ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJ
# ASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYD
# VR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8w
# HwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGG
# MBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBD
# BgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl
# cnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgB
# hv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4Q
# TRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfN
# thKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1g
# tqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1Ypx
# dmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/um
# nXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+U
# zTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhz
# q6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11
# LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCY
# oCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvk
# dgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3
# OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG
# 9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw
# FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1
# cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBi
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
# RzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAi
# MGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnny
# yhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE
# 5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm
# 7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5
# w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsD
# dV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1Z
# XUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS0
# 0mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hk
# pjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m8
# 00ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+i
# sX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB
# /zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReui
# r/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0w
# azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF
# BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk
# SURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAG
# BgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9
# mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxS
# A8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/
# 6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSM
# b++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt
# 9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMYIDdjCC
# A3ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x
# OzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGlt
# ZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQCAQUAoIHR
# MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjMx
# MjEzMTIwODMwWjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRm8CsywsLJD4JdzqqK
# ycZPGZzPQDAvBgkqhkiG9w0BCQQxIgQgREAytERzRfin1FwJU4LDUWvLHRKqoryh
# 0697xeH/y7MwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQg0vbkbe10IszR1EBXaEE2
# b4KK2lWarjMWr00amtQMeCgwDQYJKoZIhvcNAQEBBQAEggIATmOnslVX+04GwbN0
# /z/WB1sGwA8h9c8o1CJ08a+BJjsmcjaBNP57rFY7vxtc0r4u7DxyvFGY2fP69a99
# LyxONEBR+MGRk6bnbYVd7kPkqKz0C8zjsQxa5uzaNYTKLmGCb2w5Qjueunbj9V7K
# 3f+qJL5YrDMSQiIA+//kcNjyRcjhZ8KLQVWn7UOtET4NKMTsOygcQmg5uirQd4ls
# O2ZjaQokYPro4yTv362EDC12jy4S42vTjZD7APeJSSiXPf/+iM9pPfc4BiNnBpTm
# wMq6s8IZY42lDe2tdgc8JZa9o9ms3tyEXRwIP4HhtSAboW2tuAhot0yDf7wz3doh
# MevGRkxc0DTDcIGJvscNeE6oR6gx0c0aixA8PLuBpU/cwSGBqp5okUYp6OnlB+yo
# ReR4RKgLpU3w6xYqvTSc4BK2hRHpU02X+EagbQWTtVe2oIY//Lx3cLa+5XcVTStT
# U3NfpF2omWjijmSQJbJN0R82gnCr4Ay6HEPEMt0S1NDQipFPqqx/eDiw+K3i9lT2
# +SHj/A211C3syYm/wHaUq1NY3twirfif0N4zm5F+6nJvNdERexc5mAF4WQRvsrAW
# Ze0gADgy3/wcZriZtQGUq30ubU/O/VQi0qClCEmFqnaq1TSeQCXsvQuMbtNicxzK
# 1fTaVtnH4M46cTTjUxZOhbGD914=
# SIG # End signature block