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 file version from the specified path. .NOTES Required to test 'Test-PackageDetectionRuleFile' #> function Get-FileVersion { [CmdletBinding()] [OutputType([System.Version])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Path ) process { [System.Diagnostics.FileVersionInfo]::GetVersionInfo($path).FileVersionRaw -as [System.Version] } } <# .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 Writes byte array ([Byte[]]) to a file. #> function Set-FileBytes { [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [OutputType()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Byte[]] $InputObject, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $Path ) process { if ($PSVersionTable['PSEdition'] -eq 'Core') { Set-Content -Path $Path -Value $InputObject -AsByteStream } else { Set-Content -Path $Path -Value $InputObject -Encoding Byte } } } <# .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-PackageDetectionRule { [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, [Parameter(ValueFromPipelineByPropertyName)] [System.String] $Path = $PWD, ## Does not fail application test if no detection rules are defined. [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $IgnoreEmptyDetectionRule ) process { if (($Metadata.Contains('Intune')) -and ($Metadata.Intune.Contains('DetectionRules'))) { foreach ($detectionRule in $Metadata.Intune.DetectionRules) { switch ($detectionRule.Type) { File { if (-not (Test-PackageDetectionRuleFile -DetectionRule $detectionRule)) { return $false } } Registry { if (-not (Test-PackageDetectionRuleRegistry -DetectionRule $detectionRule)) { return $false } } Msi { if (-not (Test-PackageDetectionRuleMsi -DetectionRule $detectionRule)) { return $false } } PowerShell { if (-not (Test-PackageDetectionRuleScript -DetectionRule $detectionRule -Path $Path)) { 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-PackageDetectionRuleFile { [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.EnumeratingPathDoesExist -f $path) if (Test-Path -Path $path) { Write-Verbose -Message ($localized.EnumeratedPathDoesExist -f $path) return $true } else { Write-Verbose -Message ($localized.EnumeratedPathDoesNotExist -f $path) return $false } } DoesNotExist { Write-Verbose -Message ($localized.EnumeratingPathDoesNotExist -f $path) if (-not (Test-Path -Path $path)) { Write-Verbose -Message ($localized.EnumeratedPathDoesNotExist -f $path) return $true } else { Write-Verbose -Message ($localized.EnumeratedPathDoesExist -f $path) return $false } } Version { if (-not (Test-Path -Path $path -PathType Leaf)) { Write-Verbose -Message ($localized.EnumeratingPathDoesExist -f $path) Write-Verbose -Message ($localized.EnumeratedPathDoesNotExist -f $path) return $false } $version = $DetectionRule.FileDetectionValue -as [System.Version] $currentVersion = Get-FileVersion -Path $path $detectionOperator = $DetectionRule.FileDetectionOperator Write-Verbose -Message ($localized.EnumeratingFileVersion -f $path, $detectionOperator, $version) switch ($DetectionRule.FileDetectionOperator) { NotConfigurated { } Equal { if ($currentVersion -eq $Version) { Write-Verbose -Message ($localized.EnumeratedFileVersionIs -f $currentVersion, $detectionOperator, $version) return $true } else { Write-Verbose -Message ($localized.EnumeratedFileVersionIsNot -f $currentVersion, $detectionOperator, $version) return $false } } NotEqual { if ($currentVersion -ne $version) { Write-Verbose -Message ($localized.EnumeratedFileVersionIs -f $currentVersion, $detectionOperator, $version) return $true } else { Write-Verbose -Message ($localized.EnumeratedFileVersionIsNot -f $currentVersion, $detectionOperator, $version) return $false } } GreaterThan { if ($currentVersion -gt $version) { Write-Verbose -Message ($localized.EnumeratedFileVersionIs -f $currentVersion, $detectionOperator, $version) return $true } else { Write-Verbose -Message ($localized.EnumeratedFileVersionIsNot -f $currentVersion, $detectionOperator, $version) return $false } } GreaterThanOrEqual { if ($currentVersion -ge $version) { Write-Verbose -Message ($localized.EnumeratedFileVersionIs -f $currentVersion, $detectionOperator, $version) return $true } else { Write-Verbose -Message ($localized.EnumeratedFileVersionIsNot -f $currentVersion, $detectionOperator, $version) return $false } } LessThan { if ($currentVersion -lt $version) { Write-Verbose -Message ($localized.EnumeratedFileVersionIs -f $currentVersion, $detectionOperator, $version) return $true } else { Write-Verbose -Message ($localized.EnumeratedFileVersionIsNot -f $currentVersion, $detectionOperator, $version) return $false } } LessThanOrEqual { if ($currentVersion -le $version) { Write-Verbose -Message ($localized.EnumeratedFileVersionIs -f $currentVersion, $detectionOperator, $version) return $true } else { Write-Verbose -Message ($localized.EnumeratedFileVersionIsNot -f $currentVersion, $detectionOperator, $version) return $false } } } } Default { Write-Warning -Message ($localized.UnsupportedFileDectectionRuleWarning -f $DetectionRule.FileDetectionType) } } return $true } } <# .SYNOPSIS Tests a package metadata Intune MSI detection rule. #> function Test-PackageDetectionRuleMsi { [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.EnumeratingMSIProductCodeDoesExist -f $DetectionRule.MsiProductCode) $package = Get-ItemProperty -Path $paths -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -eq $DetectionRule.MsiProductCode } if ($null -ne $package) { Write-Verbose -Message ($localized.EnumeratedMSIProductCodeDoesExist -f $DetectionRule.MsiProductCode) return $true } else { Write-Verbose -Message ($localized.EnumeratedMSIProductCodeDoesNotExist -f $DetectionRule.MsiProductCode) return $false } } } <# .SYNOPSIS Tests a package metadata Intune registry detection rule. #> function Test-PackageDetectionRuleRegistry { [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 -ErrorAction SilentlyContinue } 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.EnumeratingPathItemDoesExist -f $path, $DetectionRule.RegistryValue) if ($null -ne $registryValue) { Write-Verbose -Message ($localized.EnumeratedPathItemDoesExist -f $path, $DetectionRule.RegistryValue) return $true } else { Write-Verbose -Message ($localized.EnumeratedPathItemDoesNotExist -f $path, $DetectionRule.RegistryValue) return $false } } else { Write-Verbose -Message ($localized.EnumeratingPathExists -f $path) if (Test-Path -Path $path) { Write-Verbose -Message ($localized.EnumeratedPathExists -f $path, $DetectionRule.RegistryValue) return $true } else { Write-Verbose -Message ($localized.EnumeratedPathDoesNotExist -f $path, $DetectionRule.RegistryValue) return $false } } } DoesNotExist { if (($DetectionRule.ContainsKey('RegistryValue')) -and (-not [System.String]::IsNullOrEmpty($DetectionRule.RegistryValue))) { Write-Verbose -Message ($localized.EnumeratingPathItemDoesNotExist -f $path, $DetectionRule.RegistryValue) if ($null -eq $registryValue) { Write-Verbose -Message ($localized.EnumeratedPathItemDoesNotExist -f $path, $DetectionRule.RegistryValue) return $true } else { Write-Verbose -Message ($localized.EnumeratedPathItemDoesExist -f $path, $DetectionRule.RegistryValue) return $false } } else { Write-Verbose -Message ($localized.EnumeratingPathDoesNotExist -f $path) if (-not (Test-Path -Path $path)) { Write-Verbose -Message ($localized.EnumeratedPathDoesNotExist -f $path) return $true } else { Write-Verbose -Message ($localized.EnumeratedPathDoesExist -f $path) return $false } } } Default { if ($null -eq $registryValue) { Write-Verbose -Message ($localized.EnumeratedRegistryPathValueDoesNotExist -f $path, $DetectionRule.RegistryValue) return $false } Write-Verbose -Message ($localized.EnumeratingRegistryPathValue -f $path, $DetectionRule.RegistryValue) 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] } } $registryDetectionOperator = $DetectionRule.RegistryDetectionOperator Write-Verbose -Message ($localized.EnumeratingRegistryValue -f $currentValue, $registryDetectionOperator, $value) switch ($DetectionRule.RegistryDetectionOperator) { NotConfigurated { Write-Verbose -Message ($localized.EnumeratedRegistryPathNotConfigured -f $path) } Equal { if ($currentValue -eq $value) { Write-Verbose -Message ($localized.EnumeratedRegistryValueIs -f $currentValue, $registryDetectionOperator, $value) return $true } else { Write-Verbose -Message ($localized.EnumeratedRegistryValueIsNot -f $currentValue, $registryDetectionOperator, $value) return $false } } NotEqual { if ($currentValue -ne $value) { Write-Verbose -Message ($localized.EnumeratedRegistryValueIsNot -f $currentValue, $registryDetectionOperator, $value) return $true } else { Write-Verbose -Message ($localized.EnumeratedRegistryValueIs -f $currentValue, $registryDetectionOperator, $value) return $false } } GreaterThan { if ($currentValue -gt $value) { Write-Verbose -Message ($localized.EnumeratedRegistryValueIs -f $currentValue, $registryDetectionOperator, $value) return $true } else { Write-Verbose -Message ($localized.EnumeratedRegistryValueIsNot -f $currentValue, $registryDetectionOperator, $value) return $false } } GreaterThanOrEqual { if ($currentValue -ge $value) { Write-Verbose -Message ($localized.EnumeratedRegistryValueIs -f $currentValue, $registryDetectionOperator, $value) return $true } else { Write-Verbose -Message ($localized.EnumeratedRegistryValueIsNot -f $currentValue, $registryDetectionOperator, $value) return $false } } LessThan { if ($currentValue -lt $value) { Write-Verbose -Message ($localized.EnumeratedRegistryValueIs -f $currentValue, $registryDetectionOperator, $value) return $true } else { Write-Verbose -Message ($localized.EnumeratedRegistryValueIsNot -f $currentValue, $registryDetectionOperator, $value) return $false } } LessThanOrEqual { if ($currentValue -le $value) { Write-Verbose -Message ($localized.EnumeratedRegistryValueIs -f $currentValue, $registryDetectionOperator, $value) return $true } else { Write-Verbose -Message ($localized.EnumeratedRegistryValueIsNot -f $currentValue, $registryDetectionOperator, $value) return $false } } } } } return $true } } <# .SYNOPSIS Tests a package metadata Intune detection/requirement script rule. #> function Test-PackageDetectionRuleScript { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] $DetectionRule, [Parameter(ValueFromPipelineByPropertyName)] [System.String] $Path = $PWD ) process { $isEncodedCommand = $false try { $encodedBytes = [System.Convert]::FromBase64String($DetectionRule.ScriptFile) $isEncodedCommand = $true Write-Verbose -Message $localized.EncodedPowerShellScriptDetected $scriptPath = [System.IO.Path]::GetTempFileName().Replace('.tmp','.ps1') Write-Verbose -Message ($localized.CreatingTemporaryFile -f $scriptPath) Set-FileBytes -Path $scriptPath -InputObject $encodedBytes } catch { $scriptPath = Join-Path -Path $Path -ChildPath $DetectionRule.ScriptFile Write-Verbose -Message ($localized.EnumeratingPathDoesExist -f $scriptPath) } $isCompliant = $true if (Test-Path -Path $scriptPath -PathType Leaf) { Write-Verbose -Message ($localized.EnumeratedPathDoesExist -f $scriptPath) $scriptPathItem = Get-Item -Path $scriptPath Write-Verbose -Message ($localized.EnumeratingPowerShellScript -f $scriptPathItem.Name) $powerShellPath = "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" if ($DetectionRule.RunAs32Bit -eq $true) { ## Assumption here is that we are running a x64 PowerShell process to launch a x86 process $powerShellPath = "$env:WINDIR\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" } Write-Verbose -Message ($localized.LaunchingPowerShellProcess -f $powerShellPath) $scriptOutput = & $powerShellPath -NoProfile -NonInteractive -NoLogo -File "$scriptPath" if ($LASTEXITCODE -eq 0) { Write-Verbose -Message ($localized.EnumeratedScriptExitCodeIsValid -f $LASTEXITCODE) } else { $isCompliant = $false Write-Verbose -Message ($localized.EnumeratedScriptExitCodeIsNotValid -f $LASTEXITCODE) } if ($null -ne $scriptOutput) { Write-Verbose -Message ($localized.EnumeratedScriptOutputIsValid) $scriptOutput -split "(`r)?`n" | ForEach-Object { Write-Verbose -Message " $_" } } else { $isCompliant = $false Write-Verbose -Message ($localized.EnumeratedScriptOutputIsNotValid) } if ($DetectionRule.EnforceSignatureCheck -eq $true) { Write-Verbose -Message ($localized.EnumeratingPowerShellScriptSignature -f $scriptPathItem.Name) $authenticodeSignature = Get-AuthenticodeSignature -FilePath $scriptPath -ErrorAction SilentlyContinue if (($null -eq $authenticodeSignature) -or ($authenticodeSignature.Status -ne 'Signed')) { Write-Verbose -Message ($localized.InvalidDetectionScriptSignatureWarning -f $scriptPathItem.Name) $isCompliant = $false } elseif ($authenticodeSignature.Status -eq 'Signed') { Write-Verbose -Message ($localized.DetectionScriptSignatureIsValid -f $scriptPathItem.Name) } } } else { Write-Verbose -Message ($localized.EnumeratedPathDoesNotExist -f $scriptPath) $isCompliant = $false } if($isEncodedCommand) { Write-Verbose -Message ($localized.RemovingTemporaryFile -f $scriptPath) Remove-Item -Path $scriptPath -Force } return $isCompliant } } <# .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 # MIIuwQYJKoZIhvcNAQcCoIIusjCCLq4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCqb5bqWRW8EkLe # 3jwx2fhd8kPxJdn2i3x/TLdUr8FnXaCCE6QwggWQMIIDeKADAgECAhAFmxtXno4h # 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/byWBypExghpzMIIabwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYD # VQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBH # NCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTECEAh8dPej8V5j # mSw73gBSFOYwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA # oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w # DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgCYVf58Ajf/uBEL2oFe8HdEoZ # 5JV8HgIS5mCuXKRtefUwDQYJKoZIhvcNAQEBBQAEggIAmVdHfke78i5covUYwVkW # 5sF/5wZlsdmyx18TVx1v6GdInAApzzG2I534g87cPBH6G7trDBW4+rFb9VDzFo11 # C1aY70r5vD9W0EYNSrFYOKwEwCppmbqbE4kMUnlMB81BN4jD1UN5eLLwPpLUeaB/ # v0oy8kST8k5b3Icwo0CliHNB+xhyLYluzIpRsHHbTibiv6+1ymbJQ+HGevTyqihf # adxtFQ22W6l+uP1HJ8UnpRaT/gBVWuvwRZYpETsDd7wTsELxNVXON/6Ignbh8lVe # lJ+7j50AGzvGMhAXIw4ER76jEvVg8jRJhIHrIBFJr0FJKFlsE6ZMeo9YTpl85DdG # 5zl54YiZ8bX6BIfXOcJBqLK0K75xGioZMUfurcHMTI8/ia0SjEKLrSpSg0xqCTQb # 8Ft6OCbkvT4l3Gsg9HyFQFHLSneRyJVlfDgE58pVWAuG3exmsZf2T0Phy5WCKsy7 # +qufDXEcRcZtOvQfFoZzVVhw7c0t9k71R9f2H7rSb9gtqF+GXxeTp8onvs17yYdO # zaplKYN2iJUh1VpQ6rM46NrEPuD8GFmsHs5VSdefJ2BUF40uK5NQ7rs8WyQf5E+s # u5oa924cBuknXbRDigujEn5UsYKrT75af3Rl22xLMhsIjLMLdR61MYgtdHqddBwt # JallFRn2lnNSTCJxBtp4z5mhghdAMIIXPAYKKwYBBAGCNwMDATGCFywwghcoBgkq # hkiG9w0BBwKgghcZMIIXFQIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3DQEJ # EAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQg48zU6tAZ # d4UtYCXJpdJyuuREgcGBMzIQLnETkyo7VNgCEQCI2/EAEATaZR2+g4tNsoHuGA8y # MDIzMTIwODE3MTA1NlqgghMJMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/X+Vh # FjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl # cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBT # SEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAxMzIz # NTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSAw # HgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcNAQEB # BQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOXejsq # nGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbjaedp # /lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7ReoN # YWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PBuOZS # lbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu6QXu # qvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769SgLDSb # 495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUGFOpi # CBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZcClhM # AD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmhcbJs # ca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U20clf # CKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qDy0B7 # AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNV # HSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgB # hv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYE # FKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9j # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU # aW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy # NTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6gqbWY # F7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s1+Fg # nCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0qBXqE # KZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp44pMa # dqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6wQSbd # 3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4ZiQPq # 1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wnLEHQ # mjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5AdzaROY6 # 3jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy0tg6 # uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDAdwrU # AuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2XlG3O2 # mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIGrjCCBJagAwIBAgIQBzY3tyRUfNhH # rP0oZipeWzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM # RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQD # ExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcw # MzIyMjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu # Yy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYg # VGltZVN0YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # xoY1BkmzwT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo # +n3znIkLf50fng8zH1ATCyZzlm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQX # f6sZKz5C3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8 # ald68Dd5n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6l # Y2zkpsUdzTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lX # KZYnLvWHpo9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU # 2YIqx5K/oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2 # RrOdOqPVA+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR # 8zZJTYsg0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015L # dhJRk8mMDDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNY # CQEoAA6EVO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIG # A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshv # MB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIB # hjATBgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUH # MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDov # L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQw # QwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lD # ZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI # AYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBgu # EE0TzzBTzr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFn # zbYSlm/EUExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/t # YLaqT5Fmniye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWK # cXZl2szwcqMj+sAngkSumScbqyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7 # pp1yr8THwcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkP # lM05et3/JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4 # c6umAU+9Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kad # dSweJywm228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYaw # mKAr7ZVBtzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL # 5HYCJtnwZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3b # NzgaoSv27dZ8/DCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZI # hvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ # MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNz # dXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVow # YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290 # IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjww # IjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J5 # 8soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMH # hOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6 # Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQ # ecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4b # A3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9 # WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCU # tNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvo # ZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/J # vNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCP # orF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMB # Af8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXr # oq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRt # MGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEF # BQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl # ZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgw # BgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cH # vZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8 # UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTn # f+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxU # jG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8j # LfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDGCA3Yw # ggNyAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRp # bWVTdGFtcGluZyBDQQIQBUSv85SdCDmmv9s/X+VhFjANBglghkgBZQMEAgEFAKCB # 0TAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTIz # MTIwODE3MTA1NlowKwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQUZvArMsLCyQ+CXc6q # isnGTxmcz0AwLwYJKoZIhvcNAQkEMSIEILxGrPnufOu8PdYOZJlOPvvkx+5ct0Re # 215wCDuYIn1CMDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEINL25G3tdCLM0dRAV2hB # Nm+CitpVmq4zFq9NGprUDHgoMA0GCSqGSIb3DQEBAQUABIICAJis8BS2mL6Xv+c5 # 0sB3ikH+6pPEy7hbCsc8Tt4aFM2eXxrEfuuPFWOM0ZhiTnld5mApbcUuNiIatTj9 # mMRWh+TKwnu/3kY75aOquDoxbN0wrOrz1cjDExj9xfG3P404+MJ3YzbxH+qIDoYM # JSo7liuerbs6dhc2dYMs5d/JvA+cM5pfOvxnBRbU2iiMWuqSEGLFwcJIvGNqAZup # Cc8oklLEhlYPB3Auwr0/wM4+j0B0i/Ejc74QwEMzn+x2jZMfa2fH1nst0rIJBKaO # x1lNuCUSNifiAK04q426ey6jVaYVya30QMiOxjx312NhddHPN95/zKLCPFzf5Id1 # F/2R/8UGnss0FiH50F2teD/P2+T3f82BZvIrff1Dntrsh6gu3XWQoJbeqe9qgKqp # dlGT2imrO1BLhABz0NdWBxS7IJ7u80OqxLbYFdT9XQgc2nEOx9Qc7qeKvcLncNIH # hWZ3YB61hy2GWnCzu5hw045x9NF5oHrYvt9U5lIyJ88tS90+uDuGs9VKOIv3gWfh # 94gjnoHWbOKXcDY47jLNChk5B5N+Oa7V1342a6aD7PtqAeKC/GXnxMWi5yenG9vf # 2zskIraYmG4HbYjZdJ3nGzz1B9Llz4hCNijdkN0Ydsz85PCDdNDWtd6zm2mXsXtZ # FHPuRlwwJxanJWDu4u3wkvE9SI/h # SIG # End signature block |