Src/Private/Private.ps1

<#
    .SYNOPSIS
        Ensures a directory path exists, creating it if necessary
#>

function Assert-Directory
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [System.String] $Path
    )
    process
    {
        if (Test-Path -Path $Path -PathType Leaf)
        {
            throw ($localized.SpecifiedPathIsNotEmptyError -f $Path)
        }
        elseif (-not (Test-Path -Path $Path))
        {
            Write-Verbose -Message ($localized.CreatingPackageDirectory -f $Path)
            $parentPath = Split-Path -Path $Path -Parent
            $childPath = Split-Path -Path $Path -Leaf
            try
            {
                $null = New-Item -Path $parentPath -Name $childPath -ItemType Directory -Force
            }
            catch
            {
                Write-Error -ErrorRecord $_ -ErrorAction Stop
            }
        }
    }
}

<#
    .SYNOPSIS
        Ensures that package metadata file is (or isn't) cached locally.
#>

function Assert-EvergreenPackageCacheMetadata
{
    [CmdletBinding()]
    param
    (
        ## Evergreen package(s) metadata to cache
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [System.String] $Name,

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

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

        ## Web proxy credential
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Default')]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

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

        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $NotPresent
    )
    process
    {
        $metadataFilename = '{0}.psd1' -f $Name
        $destinationPath = Join-Path -Path $cachedMetadataPath -ChildPath $metadataFilename
        $metadataItem = Get-Item -Path $destinationPath -ErrorAction SilentlyContinue

        if ($NotPresent)
        {
            if ($null -ne $metadataItem)
            {
                Write-Verbose -Message ($localized.RemovingCachedPackageMetadata -f $metadataItem.FullName)
                Remove-Item -Path $metadataItem.FullName -Force
            }
        }
        else
        {
            if ($Force -or ($null -eq $metadataItem))
            {
                $invokeEvergreenMetadataDownloadParams = @{
                    Name            = $Name
                    SubscriptionKey = $SubscriptionKey
                    Force           = $Force
                }
                if ($PSBoundParameters.ContainsKey('CustomerCode'))
                {
                    $invokeEvergreenMetadataDownloadParams['CustomerCode'] = $CustomerCode
                }
                if ($PSBoundParameters.ContainsKey('Credential'))
                {
                    $invokeEvergreenMetadataDownloadParams['Credential'] = $Credential
                }
                $null = Invoke-EvergreenMetadataDownload @invokeEvergreenMetadataDownloadParams
            }
        }
    }
}

<#
    .SYNOPSIS
        Downloads an Evergreen package (if not already cached) and extracting any Zip packages.
#>

function Assert-EvergreenPackageDownload
{
    [CmdletBinding()]
    [OutputType()]
    param
    (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $SubscriptionKey,

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

        ## Web proxy credential
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

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

        ## Download retry attempts
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Uint32] $Retry = 3
    )
    process
    {
        Write-Verbose -Message ($localized.FindingPackage -f $packageName)
        $saveEvergreenPackageParams = @{
            Name            = $Name
            SubscriptionKey = $SubscriptionKey
            Path            = $cachedMetadataPath
            Force           = $Force
            Platform        = 'Exe'
            Retry           = $Retry
        }
        if ($PSBoundParameters.ContainsKey('CustomerCode'))
        {
            $saveEvergreenPackageParams['CustomerCode'] = $CustomerCode
        }
        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $saveEvergreenPackageParams['Credential'] = $Credential
        }
        $exePackagePath = Save-EvergreenPackage @saveEvergreenPackageParams -ErrorAction SilentlyContinue

        if ($null -ne $exePackagePath)
        {
            Write-Output -InputObject ([PSCustomObject] @{
                Name     = $Name
                Path     = $exePackagePath
                TempPath = $null
            })
        }
        else
        {
            $saveEvergreenPackageParams['Platform'] = 'Zip'
            Write-Verbose -Message ($localized.TryingZipPackageBackup -f $Name)
            $zipPackagePath = Save-EvergreenPackage @saveEvergreenPackageParams -ErrorAction Stop

            ## Extract Zip to temporary folder
            $tempDirectory = New-TempDirectory
            Write-Verbose -Message ($localized.ExtractingArchive -f $tempDirectory)
            Expand-Archive -Path $zipPackagePath -DestinationPath $tempDirectory
            $exePackagePath = Get-Item -Path (Join-Path $tempDirectory -ChildPath 'Deploy-Application.exe')

            Write-Output -InputObject ([PSCustomObject] @{
                Name     = $Name
                Path     = $exePackagePath
                TempPath = $tempDirectory
            })
        }
    }
}

<#
    .SYNOPSIS
        Returns unique package identifier name from package metadata, e.g. 'Virtual-Engine-ACE' or
        'Microsoft-Visual-Studio-Code-x64'.
#>

function Get-PackageMetadataPackageIdentifier
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        [System.Collections.Hashtable] $Metadata
    )
    process
    {
        $properties = $Metadata.Properties
        $packageName = $properties.Name

        if (-not (Test-PackageExcludeArchitecture -Metadata $Metadata) -and $properties.Contains('Architecture'))
        {
            $packageName = '{0} {1}' -f $packageName, $properties.Architecture.ToLower()
        }

        if (-not (Test-PackageExcludePublisher -Metadata $metadata))
        {
            $packageName = '{0} {1}' -f $properties.Publisher, $packageName
        }

        if (-not (Test-PackageExcludeStoreApp -Metadata $metadata))
        {
            $packageName = '{0} UWP' -f $packageName
        }

        return $packageName.Replace(' ', '-')
    }
}

<#
    .SYNOPSIS
        Downloads an Evergreen package's metadata.
#>

function Invoke-EvergreenMetadataDownload
{
    [CmdletBinding()]
    param
    (
        ## Evergreen package(s) metadata to cache
        [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
        [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
    {
        ## Download metadata file
        Write-Verbose -Message ($localized.DownloadingPackageMetadata -f $Name)
        $invokeEvergreenPackageApiParams = @{
            Name            = $Name
            SubscriptionKey = $SubscriptionKey
            Platform        = 'Exe'
            Force           = $Force
        }
        if ($PSBoundParameters.ContainsKey('CustomerCode'))
        {
            $invokeEvergreenPackageApiParams['CustomerCode'] = $CustomerCode
        }
        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $invokeEvergreenPackageApiParams['Credential'] = $Credential
        }
        $package = Invoke-EvergreenPackageApi @invokeEvergreenPackageApiParams

        ## Evergreen API returns an empty string if there are no matching packages
        if ($package -is [System.String])
        {
            ## We have no Exe package so try Zip as backup
            $invokeEvergreenPackageApiParams['Platform'] = 'Zip'
            Write-Verbose -Message ($localized.TryingZipPackageBackup -f $Name)
            $package = Invoke-EvergreenPackageApi @invokeEvergreenPackageApiParams
        }

        if ($package -is [System.String])
        {
            throw ($localized.CannotFindPackageError -f $Name)
        }

        $invokeEvergreenPackageDownloadParams = @{
            Path     = $cachedMetadataPath
            Package  = $package
            Metadata = $true
            Force    = $Force
        }
        Invoke-EvergreenPackageDownload @invokeEvergreenPackageDownloadParams
    }
}

<#
    .SYNOPSIS
        Calls the Virtual Engine Evergreen Api.
#>

function Invoke-EvergreenPackageApi
{
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.String] $Name,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $SubscriptionKey,

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

        [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
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        [Parameter(ValueFromRemainingArguments)]
        $RemainingArgs
    )
    process
    {
        $uri = 'https://virtualengine.azure-api.net/evergreen/packages'

        $filters = @()
        if ($PSBoundParameters.ContainsKey('Name'))
        {
            $filters += 'name={0}' -f $Name
        }
        if ($PSBoundParameters.ContainsKey('CustomerCode'))
        {
            $filters += 'customercode={0}' -f $CustomerCode
        }
        if ($PSBoundParameters.ContainsKey('Platform'))
        {
            $filters += 'platform={0}' -f $Platform
        }
        if ($All)
        {
            $filters += 'all={0}' -f $All.ToBool().ToString().ToLower()
        }
        if ($Hidden)
        {
            $filters += 'hidden={0}' -f $Hidden.ToBool().ToString().ToLower()
        }
        if ($filters.Count -gt 0)
        {
            $filterString = [System.String]::Join('&', $filters)
            $uri          = '{0}?{1}' -f $uri, $filterString
        }

        $invokeRestMethodParams = @{
            Uri     = $uri
            Headers = @{ 'Ocp-Apim-Subscription-Key' = $SubscriptionKey }
            Verbose = $false
        }
        if ($PSBoundParameters.ContainsKey('Credential'))
        {
            $invokeRestMethodParams['ProxyCredential'] = $Credential
        }
        Write-Verbose ($localized.InvokingEvergreenApi -f $uri)
        Invoke-RestMethod @invokeRestMethodParams
    }
}

<#
    .SYNOPSIS
        Downloads an Evergreen package if not already cached.
#>

function Invoke-EvergreenPackageDownload
{
    [CmdletBinding()]
    param
    (
        ## Evergreen package
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSObject] $Package,

        ## Destination folder path
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.String] $Path,

        ## Web proxy credential
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        ## Download from the MetadataUri property
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $Metadata,

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

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

        [Parameter(ValueFromRemainingArguments)]
        $RemainingArgs
    )
    process
    {
        $destinationFilename = ($Package.Uri -as [System.Uri]).AbsolutePath.Split('/')[-1]
        $uri = $Package.Uri
        if ($Metadata)
        {
            $destinationFilename = '{0}.psd1' -f $Package.Name
            $uri                 = $Package.MetadataUri
        }
        $destinationPath = Join-Path -Path $Path -ChildPath $destinationFilename

        if ($Force -or (-not (Test-Path -Path $destinationPath)))
        {
            $invokeWebClientDownloadParams = @{
                DestinationPath = $destinationPath
                Uri             = $uri
                Retry           = $Retry
            }
            if ($PSBoundParameters.ContainsKey('Credential'))
            {
                $invokeWebClientDownloadParams['Credential'] = $Credential
            }
            Write-Verbose -Message ($localized.StartingDownload -f $destinationPath)
            Invoke-WebClientDownload @invokeWebClientDownloadParams
        }
        else
        {
            Write-Verbose -Message ($localized.ReturningCachedDownload -f $destinationPath)
            Get-Item -Path $destinationPath
        }
    }
}

function Invoke-WebClientDownload
{
<#
    .SYNOPSIS
        Downloads a (web) resource using System.Net.WebClient.
 
    .NOTES
        This solves issues when downloading resources using BITS under alternative credentials.
 
    .LINK
        https://github.com/VirtualEngine/Lability/blob/dev/Src/Private/Invoke-WebClientDownload.ps1
#>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.String] $DestinationPath,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $Uri,

        [Parameter(ValueFromPipelineByPropertyName)]
        [System.UInt32] $BufferSize = 64KB,

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

        [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential
    )
    process
    {
        for ($attempt = 1; $attempt -le $Retry; $attempt++)
        {
            try
            {
                [System.Net.WebClient] $webClient = New-Object -TypeName 'System.Net.WebClient'
                $webClient.Headers.Add('user-agent', $labDefaults.ModuleName)
                $webClient.Proxy = [System.Net.WebRequest]::GetSystemWebProxy()

                if (-not $webClient.Proxy.IsBypassed($Uri))
                {
                    $proxyInfo = $webClient.Proxy.GetProxy($Uri)
                    if ($null -ne $proxyInfo.AbsoluteUri)
                    {
                        Write-Verbose -Message ($localized.UsingProxyServer -f $proxyInfo.AbsoluteUri)
                    }
                }

                if ($Credential)
                {
                    $webClient.Credentials = $Credential
                    $webClient.Proxy.Credentials = $Credential
                }
                else
                {
                    $webClient.UseDefaultCredentials = $true
                    $webClient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
                }

                [System.IO.Stream] $inputStream = $webClient.OpenRead($Uri)
                [System.UInt64] $contentLength = $webClient.ResponseHeaders['Content-Length']
                $path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)
                [System.IO.Stream] $outputStream = [System.IO.File]::Create($path)
                [System.Byte[]] $buffer = New-Object -TypeName System.Byte[] -ArgumentList $BufferSize
                [System.UInt64] $bytesRead = 0
                [System.UInt64] $totalBytes = 0
                $writeProgessActivity = $localized.DownloadingActivity -f $Uri

                do
                {
                    $iteration ++
                    $bytesRead = $inputStream.Read($buffer, 0, $buffer.Length)
                    $totalBytes += $bytesRead
                    $outputStream.Write($buffer, 0, $bytesRead)
                    ## Avoid divide by zero
                    if ($contentLength -gt 0)
                    {
                        if ($iteration % 30 -eq 0)
                        {
                            [System.Byte] $percentComplete = ($totalBytes / $contentLength) * 100
                            $writeProgressParams = @{
                                Activity = $writeProgessActivity
                                PercentComplete = $percentComplete
                                Status = $localized.DownloadStatus -f $totalBytes, $contentLength, $percentComplete
                            }
                            Write-Progress @writeProgressParams
                        }
                    }
                }
                while ($bytesRead -ne 0)

                $outputStream.Close()
                return (Get-Item -Path $path)
            }
            catch
            {
                $sleepSeconds = [System.Math]::Pow(5, $attempt)
                Write-Warning -Message ($localized.WebResourceDownloadFailedWarning -f $Uri, $sleepSeconds)
                Start-Sleep -Seconds $sleepSeconds
            }
            finally
            {
                if ($null -ne $writeProgressActivity)
                {
                    Write-Progress -Activity $writeProgessActivity -Completed
                }
                if ($null -ne $outputStream)
                {
                    $outputStream.Close()
                }
                if ($null -ne $inputStream)
                {
                    $inputStream.Close()
                }
                if ($null -ne $webClient)
                {
                    $webClient.Dispose()
                }
            }
        }

        ## if we get here we have failed all attempts
        throw ($localized.WebResourceDownloadFailedError -f $Uri)
    }
}

<#
    .SYNOPSIS
        Creates a new temporary directory.
#>

function New-TempDirectory
{
    [Cmdletbinding()]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    param ( )
    begin
    {
        if ($PSBoundParameters.ContainsKey('Debug')) { $DebugPreference = 'Continue' }
    }
    process
    {
        do
        {
            $path = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
        }
        while ([System.IO.Directory]::Exists($path))
        Write-Debug ("Generated temp directory path '{0}'" -f $path)
        return [System.IO.Directory]::CreateDirectory($path).FullName
    }
}

<#
    .SYNOPSIS
        Starts and waits for a process to exit.
#>

function Start-WaitProcess
{
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([System.Int32])]
    param
    (
        # Path to process to start.
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [System.String] $FilePath,

        # Arguments (if any) to apply to the process.
        [Parameter()]
        [AllowNull()]
        [System.String[]] $ArgumentList,

        # Credential to start the process as.
        [Parameter()]
        [AllowNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.CredentialAttribute()]
        $Credential,

        # Working directory
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String] $WorkingDirectory = (Split-Path -Path $FilePath -Parent)
    )
    process
    {
        $startProcessParams = @{
            FilePath         = $FilePath
            WorkingDirectory = $WorkingDirectory
            NoNewWindow      = $true
            PassThru         = $true
        }
        $displayParams = '<None>'
        if ($ArgumentList)
        {
            $displayParams = [System.String]::Join(' ', $ArgumentList)
            $startProcessParams['ArgumentList'] = $ArgumentList
        }
        Write-Verbose ($localized.StartingProcess -f $FilePath, $displayParams)
        if ($Credential)
        {
            Write-Verbose ($localized.StartingProcessAs -f $Credential.UserName);
            $startProcessParams['Credential'] = $Credential
        }

        $exitCode = 0
        if ($PSCmdlet.ShouldProcess($FilePath, 'Start Process'))
        {
            $process = Start-Process @startProcessParams -ErrorAction Stop
            Write-Verbose ($localized.ProcessLaunched -f $process.Id)
            Wait-Process -InputObject $process
            $exitCode = [System.Convert]::ToInt32($process.ExitCode)
            Write-Verbose ($localized.ProcessExited -f $process.Id, $exitCode)
        }
        return $exitCode
    }
}

<#
    .SYNOPSIS
        Tests whether a package is installed using the package metadata Intune detection rules.
#>

function Test-EvergreenPackageInstall
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Collections.Hashtable]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        $Metadata,

        ## Package name used for message output.
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [System.String] $PackageName,

        ## Does not fail application test if no detection rules are defined.
        [Parameter(ValueFromPipelineByPropertyName)]
        [System.Management.Automation.SwitchParameter] $IgnoreEmptyDetectionRule
    )
    process
    {
        if (($Metadata.ContainsKey('Intune')) -and ($Metadata.Intune.ContainsKey('DetectionRules')))
        {
            foreach ($detectionRule in $Metadata.Intune.DetectionRules)
            {
                switch ($detectionRule.Type)
                {
                    File
                    {
                        if (-not (Test-EvergreenPackageInstallFile -DetectionRule $detectionRule))
                        {
                            return $false
                        }
                    }
                    Registry
                    {
                        if (-not (Test-EvergreenPackageInstallRegistry -DetectionRule $detectionRule))
                        {
                            return $false
                        }
                    }
                    Msi
                    {
                        if (-not (Test-EvergreenPackageInstallMsi -DetectionRule $detectionRule))
                        {
                            return $false
                        }
                    }
                    Default
                    {
                        Write-Warning -Message ($localized.UnsupportedPackageDectectionRuleWarning -f $detectionRule.Type)
                    }
                }
            }
        }
        else
        {
            if ($IgnoreEmptyDetectionRule)
            {
                ## Unable to determine installation state so return nothing
                Write-Verbose -Message ($localized.NoPackageDectectionRuleDefinedWarning -f $PackageName)
                return $null
            }
            else
            {
                throw ($localized.CannotFindPackageDetectionRuleError -f $PackageName)
            }
        }
        return $true
    }
}

<#
    .SYNOPSIS
        Tests a package metadata Intune file detection rule.
#>

function Test-EvergreenPackageInstallFile
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Collections.Hashtable] $DetectionRule
    )
    process
    {
        $path = $DetectionRule.Path
        if (([System.Environment]::Is64BitOperatingSystem) -and
            ($DetectionRule.ContainsKey('Check32BitOn64System')) -and
            ($DetectionRule.Check32BitOn64System -eq $true))
        {
            $path = $path -replace '%ProgramFiles%', '%ProgramFiles(x86)%'
        }
        $path = '{0}\{1}' -f [System.Environment]::ExpandEnvironmentVariables($path).TrimEnd('\'), $DetectionRule.FileOrFolderName

        switch ($DetectionRule.FileDetectionType)
        {
            NotConfigured
            {
                Write-Verbose -Message ($localized.EnumeratedFilePathNotConfigured -f $path)
            }
            Exists
            {
                Write-Verbose -Message ($localized.EnumeratingPathExists -f $path)
                return (Test-Path -Path $path)
            }
            DoesNotExist
            {
                Write-Verbose -Message ($localized.EnumeratingPathDoesNotExist -f $path)
                return -not (Test-Path -Path $path)
            }
            Version
            {
                Write-Verbose -Message ($localized.EvaluatingFilePath -f $path)
                if (-not (Test-Path -Path $path -PathType Leaf))
                {
                    return $false
                }
                $version = $DetectionRule.FileDetectionValue -as [System.Version]
                $currentVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($path).FileVersionRaw -as [System.Version]
                Write-Verbose -Message ($localized.EnumeratingFileVersion -f $currentVersion, $DetectionRule.FileDetectionOperator, $version)
                switch ($DetectionRule.FileDetectionOperator)
                {
                    NotConfigurated { }
                    Equal
                    {
                        return ($currentVersion -eq $version)
                    }
                    NotEqual
                    {
                        return ($currentVersion -ne $version)
                    }
                    GreaterThan
                    {
                        return ($currentVersion -gt $version)
                    }
                    GreaterThanOrEqual
                    {
                        return ($currentVersion -ge $version)
                    }
                    LessThan
                    {
                        return ($currentVersion -lt $version)
                    }
                    LessThanOrEqual
                    {
                        return ($currentVersion -le $version)
                    }
                }
            }
            Default
            {
                Write-Warning -Message ($localized.UnsupportedFileDectectionRuleWarning -f $DetectionRule.FileDetectionType)
            }
        }
        return $true
    }
}

<#
    .SYNOPSIS
        Tests a package metadata Intune MSI detection rule.
#>

function Test-EvergreenPackageInstallMsi
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Collections.Hashtable] $DetectionRule
    )
    process
    {
        $paths = 'HKLM:\Software\Microsoft\windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\WOW6432Node\Microsoft\windows\CurrentVersion\Uninstall\*'
        Write-Verbose -Message ($localized.EnumeratingMSIProductCodeExists -f $DetectionRule.MsiProductCode)
        $package =  Get-ItemProperty -Path $paths -ErrorAction SilentlyContinue |
                        Where-Object { $_.PSChildName -eq $DetectionRule.MsiProductCode }
        return ($null -ne $package)
    }
}

<#
    .SYNOPSIS
        Tests a package metadata Intune registry detection rule.
#>

function Test-EvergreenPackageInstallRegistry
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Collections.Hashtable] $DetectionRule
    )
    process
    {
        $path = $DetectionRule.RegistryKeyPath -replace 'HKEY_LOCAL_MACHINE', 'HKLM:' -replace 'HKEY_CURRENT_USER', 'HKCU:' -replace 'HKEY_CLASSES_ROOT', 'HKCR:'
        if (([System.Environment]::Is64BitOperatingSystem) -and
            ($DetectionRule.ContainsKey('Check32BitOn64System')) -and
            ($DetectionRule.Check32BitOn64System -eq $true))
        {
            $path = $path -replace '\\SOFTWARE\\', '\\SOFTWARE\\WOW6432Node\\'
        }

        if (($DetectionRule.ContainsKey('RegistryValue')) -and
            (-not [System.String]::IsNullOrEmpty($DetectionRule.RegistryValue)))
        {
            $registryValue = Get-ItemProperty -Path $path -Name $DetectionRule.RegistryValue -ErrorAction SilentlyContinue |
                                Select-Object -ExpandProperty $DetectionRule.RegistryValue
        }

        switch ($DetectionRule.RegistryDetectionType)
        {
            NotConfigured
            {
                Write-Verbose -Message ($localized.EnumeratedRegistryPathNotConfigured -f $path)
            }
            Exists
            {
                if (($DetectionRule.ContainsKey('RegistryValue')) -and
                    (-not [System.String]::IsNullOrEmpty($DetectionRule.RegistryValue)))
                {
                    Write-Verbose -Message ($localized.EnumeratingPathItemExists -f $path, $DetectionRule.RegistryValue)
                    return ($null -ne $registryValue)
                }
                else
                {
                    Write-Verbose -Message ($localized.EnumeratingPathExists -f $path)
                    return (Test-Path -Path $path)
                }
            }
            DoesNotExist
            {
                if (($DetectionRule.ContainsKey('RegistryValue')) -and
                    (-not [System.String]::IsNullOrEmpty($DetectionRule.RegistryValue)))
                {
                    Write-Verbose -Message ($localized.EnumeratingPathItemDoesNotExist -f $path, $DetectionRule.RegistryValue)
                    return ($null -eq $registryValue)
                }
                else
                {
                    Write-Verbose -Message ($localized.EnumeratingPathExists -f $path)
                    return -not (Test-Path -Path $path)
                }
            }
            Default
            {
                Write-Verbose -Message ($localized.EvaluatingRegistryPath -f $path)
                if ($null -eq $registryValue)
                {
                    Write-Verbose -Message ($localized.RegistryPathValueDoesNotExist -f $path, $DetectionRule.RegistryValue)
                    return $false
                }

                switch ($DetectionRule.RegistryDetectionType)
                {
                    Version
                    {
                        $value = $DetectionRule.RegistryDetectionValue -as [System.Version]
                        $currentValue = $registryValue -as [System.Version]
                    }
                    Integer
                    {
                        $value = $DetectionRule.RegistryDetectionValue -as [System.Int32]
                        $currentValue = $registryValue -as [System.Int32]
                    }
                    String
                    {
                        $value = $DetectionRule.RegistryDetectionValue -as [System.String]
                        $currentValue = $registryValue -as [System.String]
                    }
                }

                Write-Verbose -Message ($localized.EnumeratingRegistryValue -f $currentValue, $DetectionRule.RegistryDetectionOperator, $value)
                switch ($DetectionRule.RegistryDetectionOperator)
                {
                    NotConfigurated { }
                    Equal
                    {
                        return ($currentValue -eq $value)
                    }
                    NotEqual
                    {
                        return ($currentValue -ne $value)
                    }
                    GreaterThan
                    {
                        return ($currentValue -gt $value)
                    }
                    GreaterThanOrEqual
                    {
                        return ($currentValue -ge $value)
                    }
                    LessThan
                    {
                        return ($currentValue -lt $value)
                    }
                    LessThanOrEqual
                    {
                        return ($currentValue -le $value)
                    }
                }
            }
        }
        return $true
    }
}

<#
    .SYNOPSIS
        Tests metadata whether architecture should be excluded from package name.
#>

function Test-PackageExcludeArchitecture
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        [System.Collections.Hashtable] $Metadata
    )
    process
    {
        if ((($Metadata.Properties.ContainsKey('ExcludeArchitecture')) -and ($Metadata.Properties.ExcludeArchitecture -eq $true)))
        {
            return $true
        }
        else
        {
            return $false
        }
    }
}

<#
    .SYNOPSIS
        Tests metadata whether publisher name should be excluded.
#>

function Test-PackageExcludePublisher
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        [System.Collections.Hashtable] $Metadata
    )
    process
    {
        if ((($Metadata.Properties.ContainsKey('ExcludePublisher')) -and ($Metadata.Properties.ExcludePublisher -eq $true)) -or
            (($Metadata.Properties.ContainsKey('ExcludePublisherName')) -and ($Metadata.Properties.ExcludePublisherName -eq $true)))
        {
            return $true
        }
        else
        {
            return $false
        }
    }
}

<#
    .SYNOPSIS
        Tests metadata whether the UWP moniker should be excluded.
#>

function Test-PackageExcludeStoreApp
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory, ValueFromPipeline)]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()]
        [System.Collections.Hashtable] $Metadata
    )
    process
    {
        if ($Metadata.Properties.ContainsKey('IsStoreApp') -and ($Metadata.Properties.IsStoreApp -eq $true))
        {
            if ($Metadata.Properties.ContainsKey('ExcludeStoreApp') -and ($Metadata.Properties.ExcludeStoreApp -eq $true))
            {
                return $true
            }
            else
            {
                return $false
            }
        }
        else
        {
            ## Not a store app so always exclude
            return $true
        }
    }
}


# SIG # Begin signature block
# MIIuwAYJKoZIhvcNAQcCoIIusTCCLq0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDAwI7EsbM5cnH+
# dDYQlEKVbPWrEx7HvwZAf4QZUcN5Z6CCE6QwggWQMIIDeKADAgECAhAFmxtXno4h
# 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
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgs+kz4PxmJljzin39+S9VWUoP
# iN88znzvAu5jdsAAA9UwDQYJKoZIhvcNAQEBBQAEggIAGUR/fYAkf7wl1FdGe36s
# ZsKq6yH2sgElkMMcXAE0AhLVDJGEfPkYH9dZR6Mcq4HLngNJt+7+Hfsr4soT8ZFm
# fOqqDxxLBE8jd66UpaCF2c5qkxR3oKBG+UfDNhFeLKEnAfsffN0DoYEWd0/Zj0Fa
# PVl1MUCxxdpBbWr8auzltO9NTuKB6lr7iTLz1a9GVJaY+3vIuQkfFKcIBRPW5yfQ
# 3CwendUK61LJaaomNQs8ciRyHug7XmFrzSejZmIN6ilJSY9OmJv/J8kK9zNn5nNE
# 7KJryTFWBUmIFTRJBtSnUZZb9/4VtrSt7QjUNwkTKM60YhM7xJdRzDn6MAU7L4rw
# jLnq7+ScOcthPaaHAUTO2zW/n8tiDX07q8NfGBXPb5N40vjvf/icJD6V/Hl5Heqo
# lvtGio+VWLnfGY1egmxZAYYsUbGUQAFq65itPPF6JGjeKWyrYNQ2nGXKleebP41L
# XDwh6HRTsGv4v0MRwZcC2ASQ6Ke109dOFaJOg83n5ZO7JwJvD7SDm0n34/VpBGh8
# /9L+tOE1JYQJAHqF/FZBLuea2kTIEb9OBg1IL8lckzfI+Fu6ioDErbPp0FSOPzya
# e/q46cT8ixI8RdMBJKhsHpNSscxQKzugwa0oY2bweKJ64Jk5jwdh2SL7gcuupb7O
# b+T54sbnApMhlv23p4/zjlChghc/MIIXOwYKKwYBBAGCNwMDATGCFyswghcnBgkq
# hkiG9w0BBwKgghcYMIIXFAIBAzEPMA0GCWCGSAFlAwQCAQUAMHcGCyqGSIb3DQEJ
# EAEEoGgEZjBkAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgfeRtRbxl
# jMrGKYHOrnJqUD0brK83CPB12J+kfa7VtJ8CEDAdo92UVTa2hknVh7me+AYYDzIw
# MjMxMTI5MTE1MzI0WqCCEwkwggbCMIIEqqADAgECAhAFRK/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
# MTI5MTE1MzI0WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRm8CsywsLJD4JdzqqK
# ycZPGZzPQDAvBgkqhkiG9w0BCQQxIgQg4A6V++iEweJwdT8BcFsifpuYwlRohPkZ
# yVmFozu/BkAwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQg0vbkbe10IszR1EBXaEE2
# b4KK2lWarjMWr00amtQMeCgwDQYJKoZIhvcNAQEBBQAEggIAiaR5luJcw9FW5JVR
# +FgY6MwdyZp3mW5tvbTD4srB2j1DgIcCy7QYsmxF6Sw+jenUsfxDNvp09zPTGYCh
# yIk4VSiMmw1s0/vCXXhtGo8DDrn4YF56LdXfKGyrkLKoauk7RoDFx+/qKvWLUiCQ
# OIr6qSVokOV6+xNWFLc71lASlcx8lcVXM00fhNz+lVyliBQLy8izxCrRsVP9rkQS
# ZzUAw/Wt/D/G5Um7SAGcCCW+9kGbh9qEkDOy/zupBWLV6D+OWYSEVaFP2+GSSlK0
# /pZZY0ie17gjaDv2QkJLbCKiVuMNE4/oqSrKVfmxLkLgDfALeGSOXb5gWRg/xmdV
# uS4xTwnsJO9O3/vsOuD9zy1SjXyJz5U4hK09QJ3avUETjJP23A1rwIVUJJkdfb1n
# ycf7HngVI+WETfrVdBnfYdqwyk6p/vxY48Bt/el9Ro6+w5QDK1mNmpjCWtKSgRjV
# SXkw9vy4dAIkYOiK8oD7bn3rCI1+OcryrextXkuT1fvYe05NhfYuqiXHLALEtbZX
# X8184q1wyiuy08N7MwBTaPWoZGNO2XqpJBF8FqmucZQsKedVhR/0OidVqCDfkcaH
# InKpJpEzgV7djeIgURQFpPYHR8bhRhJso+iLqE28No1miVtzvbaj9/coBKIrZgA2
# 0nm3LCTo0p4fb6kBcR2e43ucrG8=
# SIG # End signature block