Private/Invoke-SACHiddenInstallerState.ps1
|
function Convert-SACStringReverse { param([string]$Value) if ($null -eq $Value) { return $null } $chars = $Value.ToCharArray() [array]::Reverse($chars) return -join $chars } function Convert-SACPairNibbleReverse { param([string]$Value) if ([string]::IsNullOrWhiteSpace($Value)) { return $Value } $result = New-Object System.Text.StringBuilder for ($i = 0; $i -lt $Value.Length; $i += 2) { $pair = $Value.Substring($i, [Math]::Min(2, $Value.Length - $i)) [void]$result.Append((Convert-SACStringReverse -Value $pair)) } return $result.ToString() } function ConvertTo-SACPackedMsiCode { <# .SYNOPSIS Converts a ProductCode GUID to the packed MSI registry key form. #> param( [Parameter(Mandatory=$true)] [string]$ProductCode ) $normalized = $ProductCode.Trim().Trim('{', '}').Replace('-', '').ToUpperInvariant() if ($normalized -notmatch '^[0-9A-F]{32}$') { throw "Invalid MSI ProductCode GUID: $ProductCode" } $part1 = $normalized.Substring(0, 8) $part2 = $normalized.Substring(8, 4) $part3 = $normalized.Substring(12, 4) $part4 = $normalized.Substring(16, 4) $part5 = $normalized.Substring(20, 12) return ( (Convert-SACStringReverse -Value $part1) + (Convert-SACStringReverse -Value $part2) + (Convert-SACStringReverse -Value $part3) + (Convert-SACPairNibbleReverse -Value $part4) + (Convert-SACPairNibbleReverse -Value $part5) ).ToUpperInvariant() } function ConvertFrom-SACPackedMsiCode { <# .SYNOPSIS Converts a packed MSI registry product code back to ProductCode GUID form. #> param( [Parameter(Mandatory=$true)] [string]$PackedCode ) $normalized = $PackedCode.Trim().Trim('{', '}').Replace('-', '').ToUpperInvariant() if ($normalized -notmatch '^[0-9A-F]{32}$') { throw "Invalid packed MSI product code: $PackedCode" } $part1 = Convert-SACStringReverse -Value $normalized.Substring(0, 8) $part2 = Convert-SACStringReverse -Value $normalized.Substring(8, 4) $part3 = Convert-SACStringReverse -Value $normalized.Substring(12, 4) $part4 = Convert-SACPairNibbleReverse -Value $normalized.Substring(16, 4) $part5 = Convert-SACPairNibbleReverse -Value $normalized.Substring(20, 12) return "{$part1-$part2-$part3-$part4-$part5}".ToUpperInvariant() } function Format-SACProductCode { param([string]$Value) if ([string]::IsNullOrWhiteSpace($Value)) { return $null } $match = [regex]::Match($Value, '\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?') if (-not $match.Success) { return $null } $guidText = $match.Value.Trim('{', '}').ToUpperInvariant() return "{$guidText}" } function Test-SACPackedMsiCode { param([string]$Value) return (-not [string]::IsNullOrWhiteSpace($Value) -and $Value.Trim() -match '^[0-9A-Fa-f]{32}$') } function Test-SACAbsoluteInstallerPath { <# .SYNOPSIS Returns true when an installer-derived path is drive-rooted, UNC-rooted, or env-var rooted. #> param([string]$PathValue) if ([string]::IsNullOrWhiteSpace($PathValue)) { return $true } $candidate = $PathValue.Trim().Trim('"') if ($candidate -match '^[A-Za-z]:[\\/]') { return $true } if ($candidate -match '^\\\\[^\\/]+[\\/][^\\/]+') { return $true } if ($candidate -match '^%[A-Za-z_][A-Za-z0-9_()]*%([\\/]|$)') { return $true } if ($candidate -match '^\$env:[A-Za-z_][A-Za-z0-9_()]*([\\/]|$)') { return $true } if ($candidate -match '^\$\{env:[^}]+\}([\\/]|$)') { return $true } return $false } function Add-SACUniqueString { param( [string[]]$Values, [string]$Value ) if ([string]::IsNullOrWhiteSpace($Value)) { return @($Values) } $trimmed = $Value.Trim() if ($Values -notcontains $trimmed) { return @($Values + $trimmed) } return @($Values) } function Add-SACContextProductCode { param( [psobject]$Context, [string]$ProductCode ) $formatted = Format-SACProductCode -Value $ProductCode if (-not $formatted) { return } $Context.ProductCodes = Add-SACUniqueString -Values $Context.ProductCodes -Value $formatted try { $Context.PackedCodes = Add-SACUniqueString -Values $Context.PackedCodes -Value (ConvertTo-SACPackedMsiCode -ProductCode $formatted) } catch { $null = $_ } } function Add-SACContextPackedCode { param( [psobject]$Context, [string]$PackedCode ) if (-not (Test-SACPackedMsiCode -Value $PackedCode)) { return } $packed = $PackedCode.Trim().ToUpperInvariant() $Context.PackedCodes = Add-SACUniqueString -Values $Context.PackedCodes -Value $packed try { $Context.ProductCodes = Add-SACUniqueString -Values $Context.ProductCodes -Value (ConvertFrom-SACPackedMsiCode -PackedCode $packed) } catch { $null = $_ } } function Add-SACContextUpgradeCode { param( [psobject]$Context, [string]$UpgradeCode, [string]$PackedUpgradeCode ) if ($UpgradeCode) { $formatted = Format-SACProductCode -Value $UpgradeCode if ($formatted) { $Context.UpgradeCodes = Add-SACUniqueString -Values $Context.UpgradeCodes -Value $formatted } } if (Test-SACPackedMsiCode -Value $PackedUpgradeCode) { $packed = $PackedUpgradeCode.Trim().ToUpperInvariant() $Context.UpgradePackedCodes = Add-SACUniqueString -Values $Context.UpgradePackedCodes -Value $packed try { $Context.UpgradeCodes = Add-SACUniqueString -Values $Context.UpgradeCodes -Value (ConvertFrom-SACPackedMsiCode -PackedCode $packed) } catch { $null = $_ } } } function Add-SACContextPackageId { param( [psobject]$Context, [string]$PackageId ) if ([string]::IsNullOrWhiteSpace($PackageId)) { return } $clean = $PackageId.Trim().Trim('"', "'", ',', ';') if ($clean.Length -lt 4) { return } $Context.PackageIds = Add-SACUniqueString -Values $Context.PackageIds -Value $clean } function Get-SACProductCodesFromText { param([string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return @() } $regexMatches = [regex]::Matches($Text, '\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?') $results = @() foreach ($match in $regexMatches) { $formatted = Format-SACProductCode -Value $match.Value if ($formatted -and $results -notcontains $formatted) { $results += $formatted } } return $results } function Get-SACPackageIdsFromText { param([string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return @() } $results = @() $patterns = @( '(?i)\bUPI2?\b\s*["'']?\s*[:=]\s*["'']?([A-Za-z0-9._{}\-]+)', '(?i)\bPackage(?:Id|ID|_ID|_id)?\b\s*["'']?\s*[:=]\s*["'']?([A-Za-z0-9._{}\-]+)', '(?i)\bPackageCode\b\s*["'']?\s*[:=]\s*["'']?([A-Za-z0-9._{}\-]+)' ) foreach ($pattern in $patterns) { foreach ($match in [regex]::Matches($Text, $pattern)) { $value = $match.Groups[1].Value.Trim().Trim('"', "'", ',', ';') if ($value.Length -ge 4 -and $results -notcontains $value) { $results += $value } } } return $results } function Test-SACContextTextMatch { param( [psobject]$Context, [string[]]$Text ) $joined = (@($Text) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -join " " if ([string]::IsNullOrWhiteSpace($joined)) { return $false } foreach ($code in @($Context.ProductCodes + $Context.PackedCodes + $Context.UpgradeCodes + $Context.UpgradePackedCodes + $Context.PackageIds)) { if (-not [string]::IsNullOrWhiteSpace($code) -and $joined.IndexOf($code, [StringComparison]::OrdinalIgnoreCase) -ge 0) { return $true } } $product = [regex]::Escape([string]$Context.Product) $year = [regex]::Escape([string]$Context.Year) if ($joined -match $product -and ($Context.Year -eq '*' -or $joined -match "\b$year\b")) { return $true } return $false } function Get-SACContextMatchedTerm { param( [psobject]$Context, [string[]]$Text ) $joined = (@($Text) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -join " " foreach ($code in @($Context.ProductCodes + $Context.PackedCodes + $Context.UpgradeCodes + $Context.UpgradePackedCodes + $Context.PackageIds)) { if (-not [string]::IsNullOrWhiteSpace($code) -and $joined.IndexOf($code, [StringComparison]::OrdinalIgnoreCase) -ge 0) { return $code } } if ($joined -match [regex]::Escape([string]$Context.Product) -and ($Context.Year -eq '*' -or $joined -match "\b$([regex]::Escape([string]$Context.Year))\b")) { return "$($Context.Product) $($Context.Year)" } return $null } function Get-SACTargetContext { param( [string[]]$TargetProducts, [string[]]$TargetYears, [object[]]$UninstallKeys, [string[]]$KnownProductCodes, [string[]]$KnownPackedCodes ) $contexts = @() foreach ($product in $TargetProducts) { foreach ($year in $TargetYears) { $contexts += [PSCustomObject]@{ Product = [string]$product Year = [string]$year ProductCodes = @() PackedCodes = @() UpgradeCodes = @() UpgradePackedCodes = @() PackageIds = @() } } } foreach ($context in $contexts) { foreach ($app in @($UninstallKeys)) { $text = @( $app.DisplayName, $app.DisplayVersion, $app.InstallLocation, $app.InstallSource, $app.LocalPackage, $app.UninstallString, $app.QuietUninstallString, $app.PSChildName ) if (Test-SACContextTextMatch -Context $context -Text $text) { Add-SACContextProductCode -Context $context -ProductCode $app.PSChildName foreach ($code in (Get-SACProductCodesFromText -Text ($text -join " "))) { Add-SACContextProductCode -Context $context -ProductCode $code } } } foreach ($code in @($KnownProductCodes)) { Add-SACContextProductCode -Context $context -ProductCode $code } foreach ($packed in @($KnownPackedCodes)) { Add-SACContextPackedCode -Context $context -PackedCode $packed } } return $contexts } function Get-SACRegistryValue { param( [Microsoft.Win32.RegistryKey]$Key, [string[]]$Names ) if ($null -eq $Key) { return $null } foreach ($name in $Names) { try { $value = $Key.GetValue($name) if ($null -ne $value -and -not [string]::IsNullOrWhiteSpace([string]$value)) { return [string]$value } } catch { $null = $_ } } return $null } function Get-SACInstallerPathWarning { param( [psobject]$Context, [hashtable]$Values, [string]$Source, [string]$SourcePath, [string]$DisplayName ) $pathNamePattern = '^(ADSK_INSTALL_PATH|INSTALLDIR|ARPINSTALLLOCATION|InstallLocation|InstallSource|LocalPackage|TARGETDIR|ROOTDRIVE)$' $warnings = @() foreach ($key in $Values.Keys) { if ($key -notmatch $pathNamePattern) { continue } $value = [string]$Values[$key] if ([string]::IsNullOrWhiteSpace($value)) { continue } if (Test-SACAbsoluteInstallerPath -PathValue $value) { continue } $warnings += [PSCustomObject]@{ Product = $Context.Product Year = $Context.Year Source = $Source SourcePath = $SourcePath DisplayName = $DisplayName PropertyName = $key Value = $value } } return $warnings } function Get-SACTextPathWarning { param( [psobject]$Context, [string]$Text, [string]$Source, [string]$SourcePath, [string]$DisplayName ) if ([string]::IsNullOrWhiteSpace($Text)) { return @() } $warnings = @() $pattern = '(?im)\b(ADSK_INSTALL_PATH|INSTALLDIR|ARPINSTALLLOCATION)\b\s*["'']?\s*[:=]\s*["'']?([^"''\r\n,;]+)' foreach ($match in [regex]::Matches($Text, $pattern)) { $name = $match.Groups[1].Value $value = $match.Groups[2].Value.Trim() if ([string]::IsNullOrWhiteSpace($value)) { continue } if (Test-SACAbsoluteInstallerPath -PathValue $value) { continue } $warnings += [PSCustomObject]@{ Product = $Context.Product Year = $Context.Year Source = $Source SourcePath = $SourcePath DisplayName = $DisplayName PropertyName = $name Value = $value } } return $warnings } function Get-SACMsiProductState { param([string[]]$ProductCodes) $results = @() $installer = $null try { $installer = New-Object -ComObject WindowsInstaller.Installer } catch { $comError = $_.Exception.Message foreach ($code in @($ProductCodes | Select-Object -Unique)) { $packedCode = $null try { $packedCode = ConvertTo-SACPackedMsiCode -ProductCode $code } catch { $null = $_ } $results += [PSCustomObject]@{ ProductCode = $code PackedCode = $packedCode ProductState = $null IsInstalled = $false Error = $comError } } return $results } foreach ($code in @($ProductCodes | Where-Object { $_ } | Select-Object -Unique)) { $packedCode = $null try { $packedCode = ConvertTo-SACPackedMsiCode -ProductCode $code } catch { $null = $_ } try { $state = $installer.ProductState($code) $results += [PSCustomObject]@{ ProductCode = $code PackedCode = $packedCode ProductState = $state IsInstalled = ($state -eq 5) Error = $null } } catch { $results += [PSCustomObject]@{ ProductCode = $code PackedCode = $packedCode ProductState = $null IsInstalled = $false Error = $_.Exception.Message } } } return $results } function Get-SACSearchableOdisFile { param([string]$FolderPath) $extensions = @('.json', '.xml', '.txt', '.ini', '.log', '.properties', '.manifest', '.pit', '.yaml', '.yml') try { return @(Get-ChildItem -LiteralPath $FolderPath -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -le 5242880 -and ($extensions -contains $_.Extension.ToLowerInvariant() -or $_.Name -match '(?i)(manifest|setup|package|deployment|bundle|collection)') } | Select-Object -First 250) } catch { return @() } } function Read-SACFileText { param([string]$Path) try { return [System.IO.File]::ReadAllText($Path) } catch { try { $bytes = [System.IO.File]::ReadAllBytes($Path) return [System.Text.Encoding]::UTF8.GetString($bytes) } catch { return $null } } } function Get-SACOdisRemnant { param([psobject[]]$Contexts) $results = @() $warnings = @() $programData = if ($env:ProgramData) { $env:ProgramData } else { 'C:\ProgramData' } $roots = @( [PSCustomObject]@{ Source = 'ODIS Metadata'; Path = (Join-Path $programData 'Autodesk\ODIS\metadata'); Action = 'Rename Folder' }, [PSCustomObject]@{ Source = 'ODIS Extract'; Path = (Join-Path $programData 'Autodesk\ODIS\extract'); Action = 'Rename Folder' } ) foreach ($root in $roots) { if (-not (Test-Path -LiteralPath $root.Path)) { continue } $folders = @(Get-ChildItem -LiteralPath $root.Path -Directory -ErrorAction SilentlyContinue) foreach ($folder in $folders) { foreach ($context in $Contexts) { $matched = Test-SACContextTextMatch -Context $context -Text @($folder.Name, $folder.FullName) $matchedTerm = if ($matched) { Get-SACContextMatchedTerm -Context $context -Text @($folder.Name, $folder.FullName) } else { $null } $matchedFile = $null $identifierText = $folder.Name if (-not $matched) { foreach ($file in (Get-SACSearchableOdisFile -FolderPath $folder.FullName)) { $text = Read-SACFileText -Path $file.FullName if ([string]::IsNullOrWhiteSpace($text)) { continue } if (Test-SACContextTextMatch -Context $context -Text @($file.Name, $text)) { $matched = $true $matchedFile = $file.FullName $matchedTerm = Get-SACContextMatchedTerm -Context $context -Text @($file.Name, $text) $identifierText = $text $warnings += Get-SACTextPathWarning -Context $context -Text $text -Source $root.Source -SourcePath $file.FullName -DisplayName $folder.Name break } } } if (-not $matched) { continue } foreach ($packageId in (Get-SACPackageIdsFromText -Text $identifierText)) { Add-SACContextPackageId -Context $context -PackageId $packageId } foreach ($code in (Get-SACProductCodesFromText -Text $identifierText)) { Add-SACContextProductCode -Context $context -ProductCode $code } $results += [PSCustomObject]@{ Product = $context.Product Year = $context.Year Source = $root.Source Action = $root.Action Path = $folder.FullName DisplayName = $folder.Name MatchedTerm = $matchedTerm MatchedFile = $matchedFile PackageIds = ($context.PackageIds -join '; ') ProductCodes = ($context.ProductCodes -join '; ') PackedCodes = ($context.PackedCodes -join '; ') UpgradeCodes = ($context.UpgradeCodes -join '; ') } } } } $installDb = Join-Path $programData 'Autodesk\ODIS\Install.db' if (Test-Path -LiteralPath $installDb) { $dbText = Read-SACFileText -Path $installDb if (-not [string]::IsNullOrWhiteSpace($dbText)) { foreach ($context in $Contexts) { if (Test-SACContextTextMatch -Context $context -Text $dbText) { $results += [PSCustomObject]@{ Product = $context.Product Year = $context.Year Source = 'ODIS Install.db' Action = 'Report Only' Path = $installDb DisplayName = 'Install.db' MatchedTerm = (Get-SACContextMatchedTerm -Context $context -Text $dbText) MatchedFile = $installDb PackageIds = ($context.PackageIds -join '; ') ProductCodes = ($context.ProductCodes -join '; ') PackedCodes = ($context.PackedCodes -join '; ') UpgradeCodes = ($context.UpgradeCodes -join '; ') } $warnings += Get-SACTextPathWarning -Context $context -Text $dbText -Source 'ODIS Install.db' -SourcePath $installDb -DisplayName 'Install.db' } } } } return [PSCustomObject]@{ Remnants = @($results) Warnings = @($warnings) } } function Get-SACHiddenInstallerState { <# .SYNOPSIS Finds hidden Autodesk MSI installer-state and ODIS remnants for targeted products/years. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string[]]$TargetProducts, [Parameter(Mandatory=$true)] [string[]]$TargetYears, [object[]]$UninstallKeys = @(), [string[]]$KnownProductCodes = @(), [string[]]$KnownPackedCodes = @() ) $contexts = @(Get-SACTargetContext -TargetProducts $TargetProducts -TargetYears $TargetYears -UninstallKeys $UninstallKeys -KnownProductCodes $KnownProductCodes -KnownPackedCodes $KnownPackedCodes) $hiddenProducts = @() $componentRefs = @() $upgradeRefs = @() $relativeWarnings = @() $productRoots = @( [PSCustomObject]@{ Source = 'UserData Products'; ProviderPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products'; SubPath = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products' }, [PSCustomObject]@{ Source = 'Classes Products'; ProviderPath = 'HKLM:\SOFTWARE\Classes\Installer\Products'; SubPath = 'SOFTWARE\Classes\Installer\Products' } ) $productBaseKey = $null try { $productBaseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Default) foreach ($root in $productRoots) { $productRootKey = $productBaseKey.OpenSubKey($root.SubPath) if (-not $productRootKey) { continue } try { foreach ($packedName in $productRootKey.GetSubKeyNames()) { $packedCode = $packedName.ToUpperInvariant() if (-not (Test-SACPackedMsiCode -Value $packedCode)) { continue } $productCode = $null try { $productCode = ConvertFrom-SACPackedMsiCode -PackedCode $packedCode } catch { $null = $_ } $productKeyPath = "$($root.ProviderPath)\$packedName" $installPropertiesPath = "$productKeyPath\InstallProperties" $productKey = $productRootKey.OpenSubKey($packedName) if (-not $productKey) { continue } $propsKey = $null $hadPropsKey = $false $values = @{} try { $propsKey = $productKey.OpenSubKey('InstallProperties') if ($propsKey) { $hadPropsKey = $true foreach ($name in $propsKey.GetValueNames()) { $values[$name] = [string]$propsKey.GetValue($name) } } foreach ($name in $productKey.GetValueNames()) { if (-not $values.ContainsKey($name)) { $values[$name] = [string]$productKey.GetValue($name) } } } finally { if ($propsKey) { $propsKey.Close() } $productKey.Close() } $displayName = $values['DisplayName'] if ([string]::IsNullOrWhiteSpace($displayName)) { $displayName = $values['ProductName'] } $text = @( $displayName, $values['DisplayVersion'], $values['InstallLocation'], $values['InstallSource'], $values['LocalPackage'], $values['UninstallString'], $productCode, $packedCode ) foreach ($context in $contexts) { if (-not (Test-SACContextTextMatch -Context $context -Text $text)) { continue } if ($productCode) { Add-SACContextProductCode -Context $context -ProductCode $productCode } Add-SACContextPackedCode -Context $context -PackedCode $packedCode $hiddenProducts += [PSCustomObject]@{ Product = $context.Product Year = $context.Year Source = $root.Source ProductCode = $productCode PackedCode = $packedCode DisplayName = $displayName DisplayVersion = $values['DisplayVersion'] InstallLocation = $values['InstallLocation'] InstallSource = $values['InstallSource'] LocalPackage = $values['LocalPackage'] UninstallString = $values['UninstallString'] RegistryPath = $productKeyPath PropertiesPath = if ($hadPropsKey) { $installPropertiesPath } else { $null } } $relativeWarnings += Get-SACInstallerPathWarning -Context $context -Values $values -Source $root.Source -SourcePath $productKeyPath -DisplayName $displayName } } } finally { $productRootKey.Close() } } $featureProviderPath = 'HKLM:\SOFTWARE\Classes\Installer\Features' $featureRootKey = $productBaseKey.OpenSubKey('SOFTWARE\Classes\Installer\Features') if ($featureRootKey) { try { foreach ($packedName in $featureRootKey.GetSubKeyNames()) { $packedCode = $packedName.ToUpperInvariant() if (-not (Test-SACPackedMsiCode -Value $packedCode)) { continue } $productCode = $null try { $productCode = ConvertFrom-SACPackedMsiCode -PackedCode $packedCode } catch { $null = $_ } foreach ($context in $contexts) { $isMatch = ($context.PackedCodes -contains $packedCode) -or ($productCode -and $context.ProductCodes -contains $productCode) if (-not $isMatch) { continue } $featureKeyPath = "$featureProviderPath\$packedName" $featureNames = @() $featureKey = $featureRootKey.OpenSubKey($packedName) if ($featureKey) { try { $featureNames = @($featureKey.GetValueNames()) } finally { $featureKey.Close() } } $hiddenProducts += [PSCustomObject]@{ Product = $context.Product Year = $context.Year Source = 'Classes Features' ProductCode = $productCode PackedCode = $packedCode DisplayName = "MSI feature registration ($($featureNames.Count) feature value(s))" DisplayVersion = $null InstallLocation = $null InstallSource = $null LocalPackage = $null UninstallString = $null RegistryPath = $featureKeyPath PropertiesPath = $null } } } } finally { $featureRootKey.Close() } } } catch { Write-SACQuietLog "Hidden MSI product/feature scan failed: $($_.Exception.Message)" } finally { if ($productBaseKey) { $productBaseKey.Close() } } foreach ($context in $contexts) { foreach ($app in @($UninstallKeys)) { $values = @{} foreach ($name in @('InstallLocation', 'InstallSource', 'LocalPackage')) { if ($null -ne $app.$name) { $values[$name] = [string]$app.$name } } if ($values.Count -gt 0 -and (Test-SACContextTextMatch -Context $context -Text @($app.DisplayName, $app.InstallLocation, $app.InstallSource, $app.LocalPackage))) { $relativeWarnings += Get-SACInstallerPathWarning -Context $context -Values $values -Source 'Add/Remove Programs' -SourcePath $app.PSPath -DisplayName $app.DisplayName } } } $allProductCodes = @() foreach ($context in $contexts) { $allProductCodes += $context.ProductCodes } $productStates = @(Get-SACMsiProductState -ProductCodes ($allProductCodes | Where-Object { $_ } | Select-Object -Unique)) $referenceContexts = @($contexts | Where-Object { @($_.ProductCodes + $_.PackedCodes).Count -gt 0 }) $baseKey = $null try { $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Default) $componentSubPath = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components' $componentKeyRoot = $baseKey.OpenSubKey($componentSubPath) if ($componentKeyRoot -and $referenceContexts.Count -gt 0) { foreach ($componentName in $componentKeyRoot.GetSubKeyNames()) { $componentKey = $componentKeyRoot.OpenSubKey($componentName) if (-not $componentKey) { continue } try { foreach ($valueName in $componentKey.GetValueNames()) { $valueData = [string]$componentKey.GetValue($valueName) foreach ($context in $referenceContexts) { $valueText = @($valueName, $valueData) $isMatch = $false foreach ($code in @($context.ProductCodes + $context.PackedCodes)) { if (-not [string]::IsNullOrWhiteSpace($code) -and (($valueName -ieq $code) -or ($valueData -ieq $code) -or ($valueName.IndexOf($code, [StringComparison]::OrdinalIgnoreCase) -ge 0) -or ($valueData.IndexOf($code, [StringComparison]::OrdinalIgnoreCase) -ge 0))) { $isMatch = $true break } } if (-not $isMatch) { continue } $componentRefs += [PSCustomObject]@{ Product = $context.Product Year = $context.Year ComponentKey = $componentName RegistryPath = "HKLM:\$componentSubPath\$componentName" ValueName = $valueName ValueData = $valueData MatchedTerm = Get-SACContextMatchedTerm -Context $context -Text $valueText } } } } finally { $componentKey.Close() } } $componentKeyRoot.Close() } $upgradeRoots = @( [PSCustomObject]@{ ProviderPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes'; SubPath = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes' }, [PSCustomObject]@{ ProviderPath = 'HKLM:\SOFTWARE\Classes\Installer\UpgradeCodes'; SubPath = 'SOFTWARE\Classes\Installer\UpgradeCodes' } ) foreach ($upgradeRoot in $upgradeRoots) { $upgradeKeyRoot = $baseKey.OpenSubKey($upgradeRoot.SubPath) if (-not $upgradeKeyRoot -or $referenceContexts.Count -eq 0) { continue } try { foreach ($upgradeName in $upgradeKeyRoot.GetSubKeyNames()) { $upgradePackedCode = $upgradeName.ToUpperInvariant() $upgradeCode = $null if (Test-SACPackedMsiCode -Value $upgradePackedCode) { try { $upgradeCode = ConvertFrom-SACPackedMsiCode -PackedCode $upgradePackedCode } catch { $null = $_ } } $upgradeKey = $upgradeKeyRoot.OpenSubKey($upgradeName) if (-not $upgradeKey) { continue } try { foreach ($valueName in $upgradeKey.GetValueNames()) { $valueData = [string]$upgradeKey.GetValue($valueName) foreach ($context in $referenceContexts) { $valueText = @($valueName, $valueData) $isMatch = $false foreach ($code in @($context.ProductCodes + $context.PackedCodes)) { if (-not [string]::IsNullOrWhiteSpace($code) -and (($valueName -ieq $code) -or ($valueData -ieq $code) -or ($valueName.IndexOf($code, [StringComparison]::OrdinalIgnoreCase) -ge 0) -or ($valueData.IndexOf($code, [StringComparison]::OrdinalIgnoreCase) -ge 0))) { $isMatch = $true break } } if (-not $isMatch) { continue } Add-SACContextUpgradeCode -Context $context -UpgradeCode $upgradeCode -PackedUpgradeCode $upgradePackedCode $upgradeRefs += [PSCustomObject]@{ Product = $context.Product Year = $context.Year Source = $upgradeRoot.ProviderPath RegistryPath = "$($upgradeRoot.ProviderPath)\$upgradeName" UpgradeCode = $upgradeCode UpgradePackedCode = $upgradePackedCode ValueName = $valueName ValueData = $valueData MatchedTerm = Get-SACContextMatchedTerm -Context $context -Text $valueText } } } } finally { $upgradeKey.Close() } } } finally { $upgradeKeyRoot.Close() } } } catch { Write-SACQuietLog "Hidden MSI component/upgrade scan failed: $($_.Exception.Message)" } finally { if ($baseKey) { $baseKey.Close() } } $odis = Get-SACOdisRemnant -Contexts $contexts $relativeWarnings += @($odis.Warnings) $uniqueHiddenCodes = @($hiddenProducts | Where-Object { $_.ProductCode } | Select-Object -ExpandProperty ProductCode -Unique) $installedStates = @($productStates | Where-Object { $_.IsInstalled }) return [PSCustomObject]@{ HiddenProducts = @($hiddenProducts | Select-Object -Unique *) ProductStates = @($productStates | Select-Object -Unique *) ComponentReferences = @($componentRefs | Select-Object -Unique *) UpgradeCodeReferences = @($upgradeRefs | Select-Object -Unique *) OdisRemnants = @($odis.Remnants | Select-Object -Unique *) RelativePathWarnings = @($relativeWarnings | Select-Object -Unique *) Contexts = @($contexts) Summary = [PSCustomObject]@{ HiddenMsiProducts = $uniqueHiddenCodes.Count ProductStateInstalled = $installedStates.Count ComponentReferences = @($componentRefs | Select-Object -Unique *).Count UpgradeCodeReferences = @($upgradeRefs | Select-Object -Unique *).Count OdisRemnants = @($odis.Remnants | Select-Object -Unique *).Count RelativePathWarnings = @($relativeWarnings | Select-Object -Unique *).Count } } } function Convert-SACRegistryPathToNative { param([string]$RegistryPath) if ([string]::IsNullOrWhiteSpace($RegistryPath)) { return $null } $path = $RegistryPath $path = $path -replace '^Microsoft\.PowerShell\.Core\\Registry::', '' $path = $path -replace '^HKLM:\\', 'HKEY_LOCAL_MACHINE\' $path = $path -replace '^HKCU:\\', 'HKEY_CURRENT_USER\' $path = $path -replace '^HKCR:\\', 'HKEY_CLASSES_ROOT\' $path = $path -replace '^HKU:\\', 'HKEY_USERS\' return $path } function Backup-SACRegistryKey { param( [string]$RegistryPath, [string]$LogDir, [string]$Prefix = 'Registry' ) if ([string]::IsNullOrWhiteSpace($LogDir) -or [string]::IsNullOrWhiteSpace($RegistryPath)) { return $null } if (-not (Test-Path -LiteralPath $RegistryPath)) { return $null } $backupDir = Join-Path $LogDir 'RegistryBackups' New-Item -ItemType Directory -Path $backupDir -Force -ErrorAction SilentlyContinue | Out-Null $safeName = ($Prefix + '_' + ($RegistryPath -replace '[\\/:*?"<>|{}\s]', '_')).Trim('_') if ($safeName.Length -gt 160) { $safeName = $safeName.Substring(0, 160) } $backupPath = Join-Path $backupDir "$safeName.reg" $nativePath = Convert-SACRegistryPathToNative -RegistryPath $RegistryPath try { & reg.exe export "$nativePath" "$backupPath" /y 2>&1 | Out-Null if (Test-Path -LiteralPath $backupPath) { Write-SACQuietLog "Backed up registry key $RegistryPath to $backupPath" return $backupPath } } catch { Write-SACQuietLog "Failed to back up registry key $($RegistryPath): $($_.Exception.Message)" } return $null } function Invoke-SACHiddenInstallerStateCleanup { <# .SYNOPSIS Removes hidden MSI installer-state references and renames targeted ODIS folders. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [psobject]$State, [Parameter(Mandatory=$true)] [string]$LogDir ) $result = [PSCustomObject]@{ HiddenMsiProductCodes = @() ProductStateInstalledCodes = @() HiddenMsiProductKeysRemoved = 0 ComponentRefsRemoved = 0 UpgradeCodeRefsRemoved = 0 OdisFoldersRenamed = 0 } foreach ($stateItem in @($State.ProductStates | Where-Object { $_.IsInstalled })) { $result.ProductStateInstalledCodes = Add-SACUniqueString -Values $result.ProductStateInstalledCodes -Value $stateItem.ProductCode } $hiddenPaths = @($State.HiddenProducts | Where-Object { $_.RegistryPath } | Sort-Object RegistryPath -Unique) foreach ($product in $hiddenPaths) { if ($product.ProductCode) { $result.HiddenMsiProductCodes = Add-SACUniqueString -Values $result.HiddenMsiProductCodes -Value $product.ProductCode } if (-not (Test-Path -LiteralPath $product.RegistryPath)) { continue } Backup-SACRegistryKey -RegistryPath $product.RegistryPath -LogDir $LogDir -Prefix 'HiddenMsiProduct' | Out-Null try { Remove-Item -LiteralPath $product.RegistryPath -Recurse -Force -ErrorAction Stop $result.HiddenMsiProductKeysRemoved++ Write-SACMsg "Removed hidden MSI registration: $($product.DisplayName) [$($product.PackedCode)]" "Success" Write-SACQuietLog "Removed hidden MSI registry key: $($product.RegistryPath)" } catch { Write-SACQuietLog "Failed to remove hidden MSI key $($product.RegistryPath): $($_.Exception.Message)" $script:SACFailures += [PSCustomObject]@{ Component = "Hidden MSI Registry: $($product.DisplayName)"; Reason = $_.Exception.Message; Severity = 'Warning' } } } $componentRefs = @($State.ComponentReferences | Where-Object { $_.RegistryPath -and $_.ValueName } | Sort-Object RegistryPath, ValueName -Unique) foreach ($ref in $componentRefs) { if (-not (Test-Path -LiteralPath $ref.RegistryPath)) { continue } Backup-SACRegistryKey -RegistryPath $ref.RegistryPath -LogDir $LogDir -Prefix 'MsiComponentRef' | Out-Null try { Remove-ItemProperty -LiteralPath $ref.RegistryPath -Name $ref.ValueName -Force -ErrorAction Stop $result.ComponentRefsRemoved++ Write-SACMsg "Removed MSI component reference: $($ref.ValueName)" "Success" Write-SACQuietLog "Removed MSI component value $($ref.ValueName) from $($ref.RegistryPath)" } catch { Write-SACQuietLog "Failed to remove MSI component value $($ref.ValueName) from $($ref.RegistryPath): $($_.Exception.Message)" $script:SACFailures += [PSCustomObject]@{ Component = "MSI Component Ref: $($ref.ValueName)"; Reason = $_.Exception.Message; Severity = 'Warning' } } } $upgradeRefs = @($State.UpgradeCodeReferences | Where-Object { $_.RegistryPath -and $_.ValueName } | Sort-Object RegistryPath, ValueName -Unique) foreach ($ref in $upgradeRefs) { if (-not (Test-Path -LiteralPath $ref.RegistryPath)) { continue } Backup-SACRegistryKey -RegistryPath $ref.RegistryPath -LogDir $LogDir -Prefix 'MsiUpgradeRef' | Out-Null try { Remove-ItemProperty -LiteralPath $ref.RegistryPath -Name $ref.ValueName -Force -ErrorAction Stop $result.UpgradeCodeRefsRemoved++ Write-SACMsg "Removed MSI upgrade-code reference: $($ref.ValueName)" "Success" Write-SACQuietLog "Removed MSI upgrade-code value $($ref.ValueName) from $($ref.RegistryPath)" } catch { Write-SACQuietLog "Failed to remove MSI upgrade-code value $($ref.ValueName) from $($ref.RegistryPath): $($_.Exception.Message)" $script:SACFailures += [PSCustomObject]@{ Component = "MSI UpgradeCode Ref: $($ref.ValueName)"; Reason = $_.Exception.Message; Severity = 'Warning' } } } $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $odisFolders = @($State.OdisRemnants | Where-Object { $_.Action -eq 'Rename Folder' -and $_.Path } | Sort-Object Path -Unique) foreach ($folder in $odisFolders) { if (-not (Test-Path -LiteralPath $folder.Path)) { continue } if ((Split-Path -Leaf $folder.Path) -match '\.SAC_bak_') { continue } $backupLeaf = "$(Split-Path -Leaf $folder.Path).SAC_bak_$timestamp" try { Rename-Item -LiteralPath $folder.Path -NewName $backupLeaf -Force -ErrorAction Stop $result.OdisFoldersRenamed++ Write-SACMsg "Renamed targeted ODIS folder: $($folder.Path) -> $backupLeaf" "Success" Write-SACQuietLog "Renamed targeted ODIS folder $($folder.Path) to $backupLeaf" } catch { Write-SACQuietLog "Failed to rename ODIS folder $($folder.Path): $($_.Exception.Message)" $script:SACFailures += [PSCustomObject]@{ Component = "ODIS Folder Rename: $($folder.Path)"; Reason = $_.Exception.Message; Severity = 'Warning' } } } return $result } function Get-SACHiddenInstallerStateFunctionBundle { $names = @( 'Convert-SACStringReverse', 'Convert-SACPairNibbleReverse', 'ConvertTo-SACPackedMsiCode', 'ConvertFrom-SACPackedMsiCode', 'Format-SACProductCode', 'Test-SACPackedMsiCode', 'Test-SACAbsoluteInstallerPath', 'Add-SACUniqueString', 'Add-SACContextProductCode', 'Add-SACContextPackedCode', 'Add-SACContextUpgradeCode', 'Add-SACContextPackageId', 'Get-SACProductCodesFromText', 'Get-SACPackageIdsFromText', 'Test-SACContextTextMatch', 'Get-SACContextMatchedTerm', 'Get-SACTargetContext', 'Get-SACRegistryValue', 'Get-SACInstallerPathWarning', 'Get-SACTextPathWarning', 'Get-SACMsiProductState', 'Get-SACSearchableOdisFile', 'Read-SACFileText', 'Get-SACOdisRemnant', 'Get-SACHiddenInstallerState' ) return (($names | ForEach-Object { $cmd = Get-Command -Name $_ -CommandType Function -ErrorAction Stop "function $($_) {`r`n$($cmd.ScriptBlock.ToString())`r`n}" }) -join "`r`n") } # SIG # Begin signature block # MII9OgYJKoZIhvcNAQcCoII9KzCCPScCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCABHf7EOSnoUgfB # gfpu3wo1kiya1yG7g0y7q9N0fVFd0aCCIfwwggXMMIIDtKADAgECAhBUmNLR1FsZ # lUgTecgRwIeZMA0GCSqGSIb3DQEBDAUAMHcxCzAJBgNVBAYTAlVTMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jvc29mdCBJZGVu # dGl0eSBWZXJpZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAy # MDAeFw0yMDA0MTYxODM2MTZaFw00NTA0MTYxODQ0NDBaMHcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xSDBGBgNVBAMTP01pY3Jv # c29mdCBJZGVudGl0eSBWZXJpZmljYXRpb24gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRo # b3JpdHkgMjAyMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALORKgeD # Bmf9np3gx8C3pOZCBH8Ppttf+9Va10Wg+3cL8IDzpm1aTXlT2KCGhFdFIMeiVPvH # or+Kx24186IVxC9O40qFlkkN/76Z2BT2vCcH7kKbK/ULkgbk/WkTZaiRcvKYhOuD # PQ7k13ESSCHLDe32R0m3m/nJxxe2hE//uKya13NnSYXjhr03QNAlhtTetcJtYmrV # qXi8LW9J+eVsFBT9FMfTZRY33stuvF4pjf1imxUs1gXmuYkyM6Nix9fWUmcIxC70 # ViueC4fM7Ke0pqrrBc0ZV6U6CwQnHJFnni1iLS8evtrAIMsEGcoz+4m+mOJyoHI1 # vnnhnINv5G0Xb5DzPQCGdTiO0OBJmrvb0/gwytVXiGhNctO/bX9x2P29Da6SZEi3 # W295JrXNm5UhhNHvDzI9e1eM80UHTHzgXhgONXaLbZ7LNnSrBfjgc10yVpRnlyUK # xjU9lJfnwUSLgP3B+PR0GeUw9gb7IVc+BhyLaxWGJ0l7gpPKWeh1R+g/OPTHU3mg # trTiXFHvvV84wRPmeAyVWi7FQFkozA8kwOy6CXcjmTimthzax7ogttc32H83rwjj # O3HbbnMbfZlysOSGM1l0tRYAe1BtxoYT2v3EOYI9JACaYNq6lMAFUSw0rFCZE4e7 # swWAsk0wAly4JoNdtGNz764jlU9gKL431VulAgMBAAGjVDBSMA4GA1UdDwEB/wQE # AwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIftJqhSobyhmYBAcnz1AQ # T2ioojAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQwFAAOCAgEAr2rd5hnn # LZRDGU7L6VCVZKUDkQKL4jaAOxWiUsIWGbZqWl10QzD0m/9gdAmxIR6QFm3FJI9c # Zohj9E/MffISTEAQiwGf2qnIrvKVG8+dBetJPnSgaFvlVixlHIJ+U9pW2UYXeZJF # xBA2CFIpF8svpvJ+1Gkkih6PsHMNzBxKq7Kq7aeRYwFkIqgyuH4yKLNncy2RtNwx # AQv3Rwqm8ddK7VZgxCwIo3tAsLx0J1KH1r6I3TeKiW5niB31yV2g/rarOoDXGpc8 # FzYiQR6sTdWD5jw4vU8w6VSp07YEwzJ2YbuwGMUrGLPAgNW3lbBeUU0i/OxYqujY # lLSlLu2S3ucYfCFX3VVj979tzR/SpncocMfiWzpbCNJbTsgAlrPhgzavhgplXHT2 # 6ux6anSg8Evu75SjrFDyh+3XOjCDyft9V77l4/hByuVkrrOj7FjshZrM77nq81YY # uVxzmq/FdxeDWds3GhhyVKVB0rYjdaNDmuV3fJZ5t0GNv+zcgKCf0Xd1WF81E+Al # GmcLfc4l+gcK5GEh2NQc5QfGNpn0ltDGFf5Ozdeui53bFv0ExpK91IjmqaOqu/dk # ODtfzAzQNb50GQOmxapMomE2gj4d8yu8l13bS3g7LfU772Aj6PXsCyM2la+YZr9T # 03u4aUoqlmZpxJTG9F9urJh4iIAGXKKy7aIwggarMIIEk6ADAgECAhMzAAYTVdk2 # ehI+T0rIAAAABhNVMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29mdCBJ # RCBWZXJpZmllZCBDUyBBT0MgQ0EgMDQwHhcNMjYwOTA3MDM0ODI0WhcNMjYwOTEw # MDM0ODI0WjBtMQswCQYDVQQGEwJVUzELMAkGA1UECBMCZ2ExETAPBgNVBAcTCEJv # c3R3aWNrMR4wHAYDVQQKExVSaWNoYXJkIERhaWxlbiBHdW50ZXIxHjAcBgNVBAMT # FVJpY2hhcmQgRGFpbGVuIEd1bnRlcjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC # AYoCggGBALfpUhuyxWlOrMpuIe/L70ARCDcPrcpzy8zk5J/FIA3jhJEVls7cEdFw # z7T3SLKq6n9KPcogyaVDTYf6E8gw7otblBreeHFt4jpxzBbmWsbLVCEPVxdc6QmC # ablEKgjlg7qC2mrXFGrUxmUQ21+XddgBN+xa3XR4svzOCD3qCjfiEC3A2rqudRv0 # v11LU7usedCSQ0aI/8oCvl48c/bUfkkZEMmXZPD8FWVFfU2H0Ry9ZuyEy4M/q6jB # t5AG1vokjHqjPBAbLL3smeD5Vbgx/ph4hKRSdDqBvzAKRlX3qcnOzpYFMP2BJSLV # hbFf9gpk6Jku9xmUKOwi31zfleocEwiPeUaDQWXPDaqGSpYrdnkDSmb6HniHkIpH # S7HOMjS2zSvUiIy5nFXpVzQuS9IrgmYHs7ClmujPrstFnQe/2OwPW7BAsvqfWUGW # RwAzPuSJg/vjBfvq8WtEeQcVsTk+7S+AHDLt3J4LAfRxi5seUDImRWMdopt0DeVt # DEYUe2Ob8wIDAQABo4IB1TCCAdEwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMC # B4AwPAYDVR0lBDUwMwYKKwYBBAGCN2EBAAYIKwYBBQUHAwMGGysGAQQBgjdhoOiV # eoK/0ttMg5+ekiyCi8TIKTAdBgNVHQ4EFgQUC0F3x+fKE1nse9GFDO/VwTCiozEw # HwYDVR0jBBgwFoAUayVB3vtrfP0YgAotf492XapzPbgwZwYDVR0fBGAwXjBcoFqg # WIZWaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwSUQlMjBWZXJpZmllZCUyMENTJTIwQU9DJTIwQ0ElMjAwNC5jcmwwdAYIKwYB # BQUHAQEEaDBmMGQGCCsGAQUFBzAChlhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v # cGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMElEJTIwVmVyaWZpZWQlMjBDUyUyMEFP # QyUyMENBJTIwMDQuY3J0MFQGA1UdIARNMEswSQYEVR0gADBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wDQYJKoZIhvcNAQEMBQADggIBAByXwNSoxMmFO2Dg0OAEEuNGiuWN72Kf # cblukHrWS7Lb4P6MZW3BjuPI7pzPEAU5FGc+qisL0I3MLjC4i+YhJFWc23qkmRmF # BYx8eDbPjQ2BvnjLssQxd1c43s0iEJa+628WrA1KizFsPygYxk+aSwoZOddGSjCz # F5mZWDh++TgumeghOk/JFUYy/cqFAhqjqxZxKeuVEMrBwTXUa9SQQbZRDWxoENrS # mOpF8v3B8ahtZIemmwlZuJApNASuZi0xMoUDz6mERGPL0dHZz8buQRtbpc61A8qS # 1wJrHH22KAFntYwIh6prgOXsXiU6F9oc+2cPbS9z/TWAfJphZeSFFOckvN1FEXHu # xdlEXM8LjPjHm/6+1TZzfoxnhIq+emrs3tDFUqkQmG+4fUMi4p6qlTn0rjvVAskR # jC2YYIfJncIFpB5Vn5/m9sbJA5Ck5bDU/JrgEU/6cYUtZL08d5B4lRXJao29Wlzb # yhTJY7jphlDA1wRUzNhC/3+O+/SNTQXnoJzpGwYgPuEfHDK4FBwBh9HU1H8DRGeP # Nvy5P3nBaNrUtsQqX8wE7UhbMxQplz8qaCsSNTTHm0YyLdTu4+azBp7ycXS8oZA7 # pYbbUKyHdemLdVj+kHx19ni3SGUdli9PSkH5O1ikOFObMfR4b1wXhoaqLPCmq20r # qoyDuHoMrsUGMIIGqzCCBJOgAwIBAgITMwAGE1XZNnoSPk9KyAAAAAYTVTANBgkq # hkiG9w0BAQwFADBaMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSswKQYDVQQDEyJNaWNyb3NvZnQgSUQgVmVyaWZpZWQgQ1MgQU9D # IENBIDA0MB4XDTI2MDkwNzAzNDgyNFoXDTI2MDkxMDAzNDgyNFowbTELMAkGA1UE # BhMCVVMxCzAJBgNVBAgTAmdhMREwDwYDVQQHEwhCb3N0d2ljazEeMBwGA1UEChMV # UmljaGFyZCBEYWlsZW4gR3VudGVyMR4wHAYDVQQDExVSaWNoYXJkIERhaWxlbiBH # dW50ZXIwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC36VIbssVpTqzK # biHvy+9AEQg3D63Kc8vM5OSfxSAN44SRFZbO3BHRcM+090iyqup/Sj3KIMmlQ02H # +hPIMO6LW5Qa3nhxbeI6ccwW5lrGy1QhD1cXXOkJgmm5RCoI5YO6gtpq1xRq1MZl # ENtfl3XYATfsWt10eLL8zgg96go34hAtwNq6rnUb9L9dS1O7rHnQkkNGiP/KAr5e # PHP21H5JGRDJl2Tw/BVlRX1Nh9EcvWbshMuDP6uowbeQBtb6JIx6ozwQGyy97Jng # +VW4Mf6YeISkUnQ6gb8wCkZV96nJzs6WBTD9gSUi1YWxX/YKZOiZLvcZlCjsIt9c # 35XqHBMIj3lGg0Flzw2qhkqWK3Z5A0pm+h54h5CKR0uxzjI0ts0r1IiMuZxV6Vc0 # LkvSK4JmB7OwpZroz67LRZ0Hv9jsD1uwQLL6n1lBlkcAMz7kiYP74wX76vFrRHkH # FbE5Pu0vgBwy7dyeCwH0cYubHlAyJkVjHaKbdA3lbQxGFHtjm/MCAwEAAaOCAdUw # ggHRMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMDwGA1UdJQQ1MDMGCisG # AQQBgjdhAQAGCCsGAQUFBwMDBhsrBgEEAYI3YaDolXqCv9LbTIOfnpIsgovEyCkw # HQYDVR0OBBYEFAtBd8fnyhNZ7HvRhQzv1cEwoqMxMB8GA1UdIwQYMBaAFGslQd77 # a3z9GIAKLX+Pdl2qcz24MGcGA1UdHwRgMF4wXKBaoFiGVmh0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMElEJTIwVmVyaWZpZWQl # MjBDUyUyMEFPQyUyMENBJTIwMDQuY3JsMHQGCCsGAQUFBwEBBGgwZjBkBggrBgEF # BQcwAoZYaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNy # b3NvZnQlMjBJRCUyMFZlcmlmaWVkJTIwQ1MlMjBBT0MlMjBDQSUyMDA0LmNydDBU # BgNVHSAETTBLMEkGBFUdIAAwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNy # b3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMA0GCSqGSIb3DQEB # DAUAA4ICAQAcl8DUqMTJhTtg4NDgBBLjRorlje9in3G5bpB61kuy2+D+jGVtwY7j # yO6czxAFORRnPqorC9CNzC4wuIvmISRVnNt6pJkZhQWMfHg2z40Ngb54y7LEMXdX # ON7NIhCWvutvFqwNSosxbD8oGMZPmksKGTnXRkowsxeZmVg4fvk4LpnoITpPyRVG # Mv3KhQIao6sWcSnrlRDKwcE11GvUkEG2UQ1saBDa0pjqRfL9wfGobWSHppsJWbiQ # KTQErmYtMTKFA8+phERjy9HR2c/G7kEbW6XOtQPKktcCaxx9tigBZ7WMCIeqa4Dl # 7F4lOhfaHPtnD20vc/01gHyaYWXkhRTnJLzdRRFx7sXZRFzPC4z4x5v+vtU2c36M # Z4SKvnpq7N7QxVKpEJhvuH1DIuKeqpU59K471QLJEYwtmGCHyZ3CBaQeVZ+f5vbG # yQOQpOWw1Pya4BFP+nGFLWS9PHeQeJUVyWqNvVpc28oUyWO46YZQwNcEVMzYQv9/ # jvv0jU0F56Cc6RsGID7hHxwyuBQcAYfR1NR/A0Rnjzb8uT95wWja1LbEKl/MBO1I # WzMUKZc/KmgrEjU0x5tGMi3U7uPmswae8nF0vKGQO6WG21Csh3Xpi3VY/pB8dfZ4 # t0hlHZYvT0pB+TtYpDhTmzH0eG9cF4aGqizwpqttK6qMg7h6DK7FBjCCBygwggUQ # oAMCAQICEzMAAAAWMZKNkgJle5oAAAAAABYwDQYJKoZIhvcNAQEMBQAwYzELMAkG # A1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE0MDIGA1UE # AxMrTWljcm9zb2Z0IElEIFZlcmlmaWVkIENvZGUgU2lnbmluZyBQQ0EgMjAyMTAe # Fw0yNjAzMjYxODExMjlaFw0zMTAzMjYxODExMjlaMFoxCzAJBgNVBAYTAlVTMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKzApBgNVBAMTIk1pY3Jvc29m # dCBJRCBWZXJpZmllZCBDUyBBT0MgQ0EgMDQwggIiMA0GCSqGSIb3DQEBAQUAA4IC # DwAwggIKAoICAQDKVfrI2+gJMM/0bQ5OVKNdvOASzLbUUMvXuf+Vl7YGuofPaZHV # o3gMHF5inT+GMSpIcfIZ9qtXU1UG68ry8vNbQtOL4Nm30ifXpqI1+ByiAWLO1YT0 # WnzG7XPOuoTeeWsNZv5FmjxCsReBZvyzyzCyXZbu1EQfJxWTH4ebUwtAiW9rqMf9 # eDj/wYhiEfNteJV3ZFeibD2ztCHr9JhFdd97XbnCHgQoTIqc02X5xlRKtUGBa++O # tHBBjiJ/uwBnzTkqu4FjpZjQeJtrmda+ur1CT2jflWIB/ypn7u7V9tvW9wJbJYt/ # H2EtJ0GONWxJZ7TEu8jWPindOO3lzPP7UtzS/mVDV94HucWaltmsra6zSG8BoEJ8 # 7IM8QSb7vfm/O41FhYkUv89WIj5ES2O4kxyiMSfe95CMivCuYrRP2hKvx7egPMrW # gDDBkxMLgrKZO9hRNUMm8vk3w5b9SogHOyJVhxyFm8aFXfIxgqDF4S0g4bhbhnzl # jmSlCLlumMZcXFGDjpF2tNoAu3VGFGYtHtTSNVKvZpgB3b4ynaoDkbPf+Wg4523j # t4VneasBgZhC1srZI2NCnCBBfgjLq04pqEKAWEohyW2K29KSkkHvt5VaE1ac3Yt+ # oyiOzMS57tXwQDJLGvLg/OXFO0VNvczDndfIfXYExB/ab2PuMSwd5VIBOwIDAQAB # o4IB3DCCAdgwDgYDVR0PAQH/BAQDAgGGMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1Ud # DgQWBBRrJUHe+2t8/RiACi1/j3ZdqnM9uDBUBgNVHSAETTBLMEkGBFUdIAAwQTA/ # BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2Nz # L1JlcG9zaXRvcnkuaHRtMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMBIGA1Ud # EwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU2UEpsA8PY2zvadf1zSmepEhqMOYw # cAYDVR0fBGkwZzBloGOgYYZfaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w # cy9jcmwvTWljcm9zb2Z0JTIwSUQlMjBWZXJpZmllZCUyMENvZGUlMjBTaWduaW5n # JTIwUENBJTIwMjAyMS5jcmwwfQYIKwYBBQUHAQEEcTBvMG0GCCsGAQUFBzAChmFo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUy # MElEJTIwVmVyaWZpZWQlMjBDb2RlJTIwU2lnbmluZyUyMFBDQSUyMDIwMjEuY3J0 # MA0GCSqGSIb3DQEBDAUAA4ICAQAG1VBeVHTVRBljlcZD3IiMxwPyMjQyLNaEnVu5 # mODm2hRBJfH8GsBLATmrHAc8F47jmk5CnpUPiIguCbw6Z/KVj4Dsoiq228NSLMLe # wFfGMri7uwNGLISC5ccp8vUdADDEIsS2dE+QI9OwkDpv3XuUD7d+hAgcLVcMOl1A # sfEZtsZenhGvSYUrm/FuLq0BqEGL9GXM5c+Ho9q8o+Vn/S+GWQN2y+gkRO15s0kI # 05nUpq/dOD4ri9rgVs6tipEd0YZqGgD+CZNiaZWrDTOQbNPncd2F9qOsUa20miYr # uoT5PwJAaI+QQiTE2ZJeMJOkOpzhTUgqVMZwZidEUZKCqudaeQA08WwnkQMfKyHz # aU8j48ULcU4hUwvMsv7fSurOe9GAdRQCPvF8WcSK5oDHe8VVJM4tv6KKCm91HqLx # 9JamBgRI6R2SfY3nu26EGznu0rCg/769z8xWm4PVcC2ZaL6VlKVqFp1NsN8YqMyf # 5t+bbGVb09noFKcJG/UwyGlxRmQBlfeBUQx5/ytlzZzsEnhrJF9fTAfje8j3OdX5 # lEnePTFQLRlvzZFBqUXnIeQKv3fHQjC9m2fo/Z01DII/qp3d8LhGVUW0BCG04fRw # HJNH8iqqCG/qofMv+kym2AxBDnHzNgRjL60JOFiBgiurvLhYQNhB95KWojFA6shQ # nggkMTCCB54wggWGoAMCAQICEzMAAAAHh6M0o3uljhwAAAAAAAcwDQYJKoZIhvcN # AQEMBQAwdzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjFIMEYGA1UEAxM/TWljcm9zb2Z0IElkZW50aXR5IFZlcmlmaWNhdGlvbiBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDIwMB4XDTIxMDQwMTIwMDUyMFoX # DTM2MDQwMTIwMTUyMFowYzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjE0MDIGA1UEAxMrTWljcm9zb2Z0IElEIFZlcmlmaWVkIENv # ZGUgU2lnbmluZyBQQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBALLwwK8ZiCji3VR6TElsaQhVCbRS/3pK+MHrJSj3Zxd3KU3rlfL3qrZilYKJ # NqztA9OQacr1AwoNcHbKBLbsQAhBnIB34zxf52bDpIO3NJlfIaTE/xrweLoQ71lz # CHkD7A4As1Bs076Iu+mA6cQzsYYH/Cbl1icwQ6C65rU4V9NQhNUwgrx9rGQ//h89 # 0Q8JdjLLw0nV+ayQ2Fbkd242o9kH82RZsH3HEyqjAB5a8+Ae2nPIPc8sZU6ZE7iR # rRZywRmrKDp5+TcmJX9MRff241UaOBs4NmHOyke8oU1TYrkxh+YeHgfWo5tTgkoS # MoayqoDpHOLJs+qG8Tvh8SnifW2Jj3+ii11TS8/FGngEaNAWrbyfNrC69oKpRQXY # 9bGH6jn9NEJv9weFxhTwyvx9OJLXmRGbAUXN1U9nf4lXezky6Uh/cgjkVd6CGUAf # 0K+Jw+GE/5VpIVbcNr9rNE50Sbmy/4RTCEGvOq3GhjITbCa4crCzTTHgYYjHs1Nb # Oc6brH+eKpWLtr+bGecy9CrwQyx7S/BfYJ+ozst7+yZtG2wR461uckFu0t+gCwLd # N0A6cFtSRtR8bvxVFyWwTtgMMFRuBa3vmUOTnfKLsLefRaQcVTgRnzeLzdpt32cd # YKp+dhr2ogc+qM6K4CBI5/j4VFyC4QFeUP2YAidLtvpXRRo3AgMBAAGjggI1MIIC # MTAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNlB # KbAPD2Ns72nX9c0pnqRIajDmMFQGA1UdIARNMEswSQYEVR0gADBBMD8GCCsGAQUF # BwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3Np # dG9yeS5odG0wGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwDwYDVR0TAQH/BAUw # AwEB/zAfBgNVHSMEGDAWgBTIftJqhSobyhmYBAcnz1AQT2ioojCBhAYDVR0fBH0w # ezB5oHegdYZzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWlj # cm9zb2Z0JTIwSWRlbnRpdHklMjBWZXJpZmljYXRpb24lMjBSb290JTIwQ2VydGlm # aWNhdGUlMjBBdXRob3JpdHklMjAyMDIwLmNybDCBwwYIKwYBBQUHAQEEgbYwgbMw # gYEGCCsGAQUFBzAChnVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Nl # cnRzL01pY3Jvc29mdCUyMElkZW50aXR5JTIwVmVyaWZpY2F0aW9uJTIwUm9vdCUy # MENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAyMC5jcnQwLQYIKwYBBQUHMAGG # IWh0dHA6Ly9vbmVvY3NwLm1pY3Jvc29mdC5jb20vb2NzcDANBgkqhkiG9w0BAQwF # AAOCAgEAfyUqnv7Uq+rdZgrbVyNMul5skONbhls5fccPlmIbzi+OwVdPQ4H55v7V # OInnmezQEeW4LqK0wja+fBznANbXLB0KrdMCbHQpbLvG6UA/Xv2pfpVIE1CRFfNF # 4XKO8XYEa3oW8oVH+KZHgIQRIwAbyFKQ9iyj4aOWeAzwk+f9E5StNp5T8FG7/VEU # RIVWArbAzPt9ThVN3w1fAZkF7+YU9kbq1bCR2YD+MtunSQ1Rft6XG7b4e0ejRA7m # B2IoX5hNh3UEauY0byxNRG+fT2MCEhQl9g2i2fs6VOG19CNep7SquKaBjhWmirYy # ANb0RJSLWjinMLXNOAga10n8i9jqeprzSMU5ODmrMCJE12xS/NWShg/tuLjAsKP6 # SzYZ+1Ry358ZTFcx0FS/mx2vSoU8s8HRvy+rnXqyUJ9HBqS0DErVLjQwK8VtsBde # kBmdTbQVoCgPCqr+PDPB3xajYnzevs7eidBsM71PINK2BoE2UfMwxCCX3mccFgx6 # UsQeRSdVVVNSyALQe6PT12418xon2iDGE81OGCreLzDcMAZnrUAx4XQLUz6ZTl65 # yPUiOh3k7Yww94lDf+8oG2oZmDh5O1Qe38E+M3vhKwmzIeoB1dVLlz4i3IpaDcR+ # iuGjH2TdaC1ZOmBXiCRKJLj4DT2uhJ04ji+tHD6n58vhavFIrmcxghqUMIIakAIB # ATBxMFoxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xKzApBgNVBAMTIk1pY3Jvc29mdCBJRCBWZXJpZmllZCBDUyBBT0MgQ0EgMDQC # EzMABhNV2TZ6Ej5PSsgAAAAGE1UwDQYJYIZIAWUDBAIBBQCgXjAQBgorBgEEAYI3 # AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAvBgkqhkiG9w0BCQQx # IgQgnvxYFH8PGVupgKn17hzG6POEd7vfZ10QIFUQuUdJhz8wDQYJKoZIhvcNAQEB # BQAEggGAGC0DSn6Kyse72bXrSYjdBkzJJWcSQjrV+HGDJj+qDiKxDHVk/qkqmvex # kqIa90L+2LagxB59DIgXY8wm6CEnfUmBF1LYGcFkA6J6Mj7qh1ZKzWB0G0UYCYFn # ZLlDlSkJSHBA7YpJdS2kif5WQWhrvOpMr3He3FMUTkO/yEUYradXAHr6ejruu+7w # A0KNctRWKkNyvYDIyNijHv6wlj+8N5x8DnJ+arhszgi6La8of5HHARzJKaGwwRSw # bPG1RbPlaO++bYxHeqYFYshfwPEZBPY87uqtQ137DLZcOVYt7INx2r3wSxDta3zT # o/TeyEa4LelQSGP/T4YoterA/LyF3zenp4NXdUcgUzsvYgH8oSwXYd7BPrL26jul # JsS6aamjGW2RvylpZufJGMS7WzpTvqyDHeb9PJSDsgW9GKYsOJ9eoHYsac8bl2FE # 5ZP7PCmm//2swu40/D7M/07i1P5OQwvmnr+ORpi5GTWIE2WHLd80oJ2QmdwFWOWr # XyuQJB9goYIYFDCCGBAGCisGAQQBgjcDAwExghgAMIIX/AYJKoZIhvcNAQcCoIIX # 7TCCF+kCAQMxDzANBglghkgBZQMEAgEFADCCAWIGCyqGSIb3DQEJEAEEoIIBUQSC # AU0wggFJAgEBBgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIMKNHfB4yIXz # LSwYsDuyeZHDkGuRaE8BzhJy/3H4NAteAgZqhEkGSBkYEzIwMjYwOTA5MDIyNjU4 # LjY1OFowBIACAfSggeGkgd4wgdsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMx # JzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpBNTAwLTA1RTAtRDk0NzE1MDMGA1UE # AxMsTWljcm9zb2Z0IFB1YmxpYyBSU0EgVGltZSBTdGFtcGluZyBBdXRob3JpdHmg # gg8hMIIHgjCCBWqgAwIBAgITMwAAAAXlzw//Zi7JhwAAAAAABTANBgkqhkiG9w0B # AQwFADB3MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMUgwRgYDVQQDEz9NaWNyb3NvZnQgSWRlbnRpdHkgVmVyaWZpY2F0aW9uIFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjAwHhcNMjAxMTE5MjAzMjMxWhcN # MzUxMTE5MjA0MjMxWjBhMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1l # c3RhbXBpbmcgQ0EgMjAyMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AJ5851Jj/eDFnwV9Y7UGIqMcHtfnlzPREwW9ZUZHd5HBXXBvf7KrQ5cMSqFSHGqg # 2/qJhYqOQxwuEQXG8kB41wsDJP5d0zmLYKAY8Zxv3lYkuLDsfMuIEqvGYOPURAH+ # Ybl4SJEESnt0MbPEoKdNihwM5xGv0rGofJ1qOYSTNcc55EbBT7uq3wx3mXhtVmtc # CEr5ZKTkKKE1CxZvNPWdGWJUPC6e4uRfWHIhZcgCsJ+sozf5EeH5KrlFnxpjKKTa # vwfFP6XaGZGWUG8TZaiTogRoAlqcevbiqioUz1Yt4FRK53P6ovnUfANjIgM9JDdJ # 4e0qiDRm5sOTiEQtBLGd9Vhd1MadxoGcHrRCsS5rO9yhv2fjJHrmlQ0EIXmp4DhD # BieKUGR+eZ4CNE3ctW4uvSDQVeSp9h1SaPV8UWEfyTxgGjOsRpeexIveR1MPTVf7 # gt8hY64XNPO6iyUGsEgt8c2PxF87E+CO7A28TpjNq5eLiiunhKbq0XbjkNoU5Jht # YUrlmAbpxRjb9tSreDdtACpm3rkpxp7AQndnI0Shu/fk1/rE3oWsDqMX3jjv40e8 # KN5YsJBnczyWB4JyeeFMW3JBfdeAKhzohFe8U5w9WuvcP1E8cIxLoKSDzCCBOu0h # WdjzKNu8Y5SwB1lt5dQhABYyzR3dxEO/T1K/BVF3rV69AgMBAAGjggIbMIICFzAO # BgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFGtpKDo1 # L0hjQM972K9J6T7ZPdshMFQGA1UdIARNMEswSQYEVR0gADBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTIftJqhSobyhmYBAcn # z1AQT2ioojCBhAYDVR0fBH0wezB5oHegdYZzaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwSWRlbnRpdHklMjBWZXJpZmljYXRp # b24lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDIwLmNybDCB # lAYIKwYBBQUHAQEEgYcwgYQwgYEGCCsGAQUFBzAChnVodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMElkZW50aXR5JTIwVmVy # aWZpY2F0aW9uJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAy # MC5jcnQwDQYJKoZIhvcNAQEMBQADggIBAF+Idsd+bbVaFXXnTHho+k7h2ESZJRWl # uLE0Oa/pO+4ge/XEizXvhs0Y7+KVYyb4nHlugBesnFqBGEdC2IWmtKMyS1OWIviw # pnK3aL5JedwzbeBF7POyg6IGG/XhhJ3UqWeWTO+Czb1c2NP5zyEh89F72u9UIw+I # fvM9lzDmc2O2END7MPnrcjWdQnrLn1Ntday7JSyrDvBdmgbNnCKNZPmhzoa8PccO # iQljjTW6GePe5sGFuRHzdFt8y+bN2neF7Zu8hTO1I64XNGqst8S+w+RUdie8fXC1 # jKu3m9KGIqF4aldrYBamyh3g4nJPj/LR2CBaLyD+2BuGZCVmoNR/dSpRCxlot0i7 # 9dKOChmoONqbMI8m04uLaEHAv4qwKHQ1vBzbV/nG89LDKbRSSvijmwJwxRxLLpMQ # /u4xXxFfR4f/gksSkbJp7oqLwliDm/h+w0aJ/U5ccnYhYb7vPKNMN+SZDWycU5OD # IRfyoGl59BsXR/HpRGtiJquOYGmvA/pk5vC1lcnbeMrcWD/26ozePQ/TWfNXKBOm # kFpvPE8CH+EeGGWzqTCjdAsno2jzTeNSxlx3glDGJgcdz5D/AAxw9Sdgq/+rY7jj # gs7X6fqPTXPmaCAJKVHAP19oEjJIBwD1LyHbaEgBxFCogYSOiUIr0Xqcr1nJfiWG # 2GwYe6ZoAF1bMIIHlzCCBX+gAwIBAgITMwAAAFZ+j51YCI7pYAAAAAAAVjANBgkq # hkiG9w0BAQwFADBhMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lc3Rh # bXBpbmcgQ0EgMjAyMDAeFw0yNTEwMjMyMDQ2NTFaFw0yNjEwMjIyMDQ2NTFaMIHb # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN # aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT # UyBFU046QTUwMC0wNUUwLUQ5NDcxNTAzBgNVBAMTLE1pY3Jvc29mdCBQdWJsaWMg # UlNBIFRpbWUgU3RhbXBpbmcgQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAtKWfm/ul027/d8Rlb8Mn/g0QUvvLqY2Vsy3tI8U2tFSspTZo # mZOD3BHT8LkR+RrhMJgb1VjAKFNysaK9cLSXifPGSIBrPCgs9P4y24lrJEmrV6Q5 # z4BmqMhIPrZhEvZnWpCS4HO7jYSei/nxmC7/1Er+l5Lg3PmSxb8d2IVcARxSw1B4 # mxB6XI0nkel9wa1dYb2wfGpofraFmxZOxT9eNht4LH0RBSVueba6ZNpjS/0gtfm7 # qiIiyP6p6PRzTTbMnVqsHnV/d/rW0zHx+Q+QNZ5wUqKmTZJB9hU853+2pX5rDfK3 # 2uNY9/WBOAmzbqgpEdQkbiMavUMyUDShmycIvgHdQnS207sTj8M+kJL3tOdahPuP # qMwsaCCgdfwwQx0O9TKe7FSvbAEYs1AnldCl/KHGZCOVvUNqjyL10JLe0/+GD9/y # nqXGWFpXOjaunvZ/cKROhjN4M5e6xx0b2miqcPii4/ii2ZheKallJET7CKlpFShs # 3wyg6F/fojQxQvPnbWD4Nyx6lhjWjwmoLcx6w1FSCtavLCly33BLRSlTU4qKUxaa # 8d7YN7Eqpn9XO0SY0umOvKFXrWH7rxl+9iaicitdnTTksAnRjvekdKT3lg7lRMfm # fZU8vXNiN0UYJzT9EjqjRm0uN/h0oXxPhNfPYqeFbyPXGGxzaYUz6zx3qTcCAwEA # AaOCAcswggHHMB0GA1UdDgQWBBS+tjPyu6tZ/h5GsyLvyz1H+FNIWjAfBgNVHSME # GDAWgBRraSg6NS9IY0DPe9ivSek+2T3bITBsBgNVHR8EZTBjMGGgX6BdhltodHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBQdWJs # aWMlMjBSU0ElMjBUaW1lc3RhbXBpbmclMjBDQSUyMDIwMjAuY3JsMHkGCCsGAQUF # BwEBBG0wazBpBggrBgEFBQcwAoZdaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBQdWJsaWMlMjBSU0ElMjBUaW1lc3RhbXBp # bmclMjBDQSUyMDIwMjAuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYI # KwYBBQUHAwgwDgYDVR0PAQH/BAQDAgeAMGYGA1UdIARfMF0wUQYMKwYBBAGCN0yD # fQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lv # cHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTAIBgZngQwBBAIwDQYJKoZIhvcNAQEMBQAD # ggIBAA4DqAXEsO26j/La7Fgn/Qifit8xuZekqZ57+Ye+sH/hRTbEEjGYrZgsqwR/ # lUUfKCFpbZF8msaZPQJOR4YYUEU8XyjLrn8Y1jCSmoxh9l7tWiSoc/JFBw356JAm # zGGxeBA2EWSxRuTr1AuZe6nYaN8/wtFkiHcs8gMadxXBs6DxVhyu5YnhLPQkfumK # m3lFftwE7pieV7f1lskmlgsC6AeSGCzGPZUgCvcH5Tv/Qe9z7bIImSD3SuzhOIwa # P+eKQTYf67TifyJKkWQSdGfTA6Kcu41k8LB6oPK+MLk1jbxxK5wPqLSL62xjK04S # BXHEJSEnsFt0zxWkxP/lgej1DxqUnmrYEdkxvzKSHIAqFWSZul/5hI+vJxvFPhsN # QBEk4cSulDkJQpcdVi/gmf/mHFOYhDBjsa15s4L+2sBil3XV/T8RiR66Q8xYvTLR # Wxd2dVsrOoCwnsU4WIeiC0JinCv1WLHEh7Qyzr9RSr4kKJLWdpNYLhgjkojTmEkA # jFO774t3xB7enbvIF0GOsV19xnCUzq9EGKyt0gMuaphKlNjJ+aTpjWMZDGo+GOKs # np93Hmftml0Syp3F9+M3y+y6WJGUZoIZJq227jDjjEndtpUrh9BdPdVIfVJD/Au8 # 1Rzh05UHAivorQ3Os8PELHIgiOd9TWzbdgmGzcILt/ddVQERMYIHRjCCB0ICAQEw # eDBhMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MTIwMAYDVQQDEylNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lc3RhbXBpbmcgQ0Eg # MjAyMAITMwAAAFZ+j51YCI7pYAAAAAAAVjANBglghkgBZQMEAgEFAKCCBJ8wEQYL # KoZIhvcNAQkQAg8xAgUAMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkq # hkiG9w0BCQUxDxcNMjYwOTA5MDIyNjU4WjAvBgkqhkiG9w0BCQQxIgQgFCczqzl3 # I+l1KTFse1J4yr7zyzOheJ0cHcrAar3XpvowgbkGCyqGSIb3DQEJEAIvMYGpMIGm # MIGjMIGgBCC2DDMlTaTj8JV3iTg5Xnpe4CSH60143Z+X9o5NBgMMqDB8MGWkYzBh # MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lc3RhbXBpbmcgQ0EgMjAy # MAITMwAAAFZ+j51YCI7pYAAAAAAAVjCCA2EGCyqGSIb3DQEJEAISMYIDUDCCA0yh # ggNIMIIDRDCCAiwCAQEwggEJoYHhpIHeMIHbMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVy # YXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTUwMC0wNUUwLUQ5NDcx # NTAzBgNVBAMTLE1pY3Jvc29mdCBQdWJsaWMgUlNBIFRpbWUgU3RhbXBpbmcgQXV0 # aG9yaXR5oiMKAQEwBwYFKw4DAhoDFQD/c/cpFSqQWYBeXggyRJ2ZbvYEEaBnMGWk # YzBhMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MTIwMAYDVQQDEylNaWNyb3NvZnQgUHVibGljIFJTQSBUaW1lc3RhbXBpbmcgQ0Eg # MjAyMDANBgkqhkiG9w0BAQsFAAIFAO5LH50wIhgPMjAyNjA5MDgyMzU4MjFaGA8y # MDI2MDkwOTIzNTgyMVowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA7ksfnQIBADAK # AgEAAgIotgIB/zAHAgEAAgIcWDAKAgUA7kxxHQIBADA2BgorBgEEAYRZCgQCMSgw # JjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3 # DQEBCwUAA4IBAQCRJZbrXx3mAuxUJUoBhyisQ7lxIyE6rZfXOfJ50piLDFv8KJ40 # CbsSY+AkObstiWjAyKSVmlFxZvKebDLH63VPMe/Vu3pJ9iuQhhm9r1fdgxFTCFkQ # DIhF3vPbASfHYOjOx7x6ZsnLUPuFmUWlpAx2HdyeRvIwy0nUx8tkzgTLkxg1CbT2 # F33OTWZO9sZ67H4CZ+ak4DwFj09TaZOdVFq9K4hbzQgD/93FcOh8tdNuWGqu+UvR # 2no+vXN10/KkpMvQJzVC817gJS4Hn4/a7fzTXKkYugOwp1X6NXaBic7Xwr8Eawly # Rz7cG01QVtwJ0uXG453Leu7vsh7HpsjQN5CgMA0GCSqGSIb3DQEBAQUABIICAIQ3 # ElQjQMFnuYmr9ntDoNocVtdAYI5cXagoT0vR/jjBYOd/YtVwgfu18WXbKa9cMwSp # xBucvRMYDdvyZdxjN+Lp4QwKRzxziB65Y1gIJ/nHL/YNZsa0lk2O+9DxbxQGL+15 # k7JTvVQbYDOMNfbFueciHU798shVBulh2cMdk5Z59zxg5DyXG/HqV1YUAcgrYDpG # dePJ76ZNYxcB74g8sR0CImmwarI2RIQgZHv+Ehhw5YawypfmhZsIX+Uu8bS5t91r # Sjhn9e6u6fMagctnP/lSWD/lTiol7hkS5BNndIu6he/XM2NPeUws4aOq2ykfXUQF # cqwEiyx8ubNmtZJBgLTfciWbB48aLCzxOPE6awUli3SnNzoPXsHN7KTnKcs3J9pV # f6sEOpaSbvLFLWDpvJt2L4oJ0oc/GclQACVSX6YoQFXLYRPu13LjPPbhL/AHgl1Y # 2Mi1JMNo/ILWLzZfVS4kJO6+d+6lhCQhbrO/raF1GVSr4v5zT+epyS1svUDXA69+ # rgZ8VyoB9YiEhzv17aOEKCIDt+C1HpU8w1l/hgh0eIY9if3pYVMFf648iOPxq5Vc # wsSU04jXQkZpeg6Kjtmh2By86Qr1yS9KsEvdTnXf50xkLVIORi2x0CaD+VVDIrWl # eu8iUQCPWVBNgvSieJoRM/iH7Y/UCvpJNND3BC75 # SIG # End signature block |