public/Get-KbUpdate.ps1
function Get-KbUpdate { <# .SYNOPSIS Gets download links and detailed information for KB files (SPs/hotfixes/CUs, etc) from local db, catalog.update.microsoft.com or WSUS. .DESCRIPTION Gets detailed information including download links for KB files (SPs/hotfixes/CUs, etc) from local db, catalog.update.microsoft.com or WSUS. By default, the local sqlite database (updated regularly) is searched first and if no result is found, the catalog will be searched as a failback. Because Microsoft's RSS feed does not work, this can result in slowness. Use the Simple parameter for simplified output and faster results when using the web option. If you'd prefer searching and downloading from a local WSUS source, this is an option as well. See the examples for more information. .PARAMETER Pattern Any pattern. Can be the KB name, number or even MSRC numbrer. For example, KB4057119, 4057119, or MS15-101. .PARAMETER Architecture Can be x64, x86, ia64, or ARM. .PARAMETER Language Specify one or more Language. Tab complete to see what's available. This is not an exact science, as the data itself is miscategorized. .PARAMETER OperatingSystem Specify one or more operating systems. Tab complete to see what's available. If anything is missing, please file an issue. .PARAMETER ComputerName Used to connect to a remote host - gets the Operating System and architecture information automatically .PARAMETER Credential The optional alternative credential to be used when connecting to ComputerName .PARAMETER Product Specify one or more products (SharePoint, SQL Server, etc). Tab complete to see what's available. If anything is missing, please file an issue. .PARAMETER Latest Filters out any patches that have been superseded by other patches in the batch .PARAMETER Simple A lil faster. Returns, at the very least: Title, Architecture, Language, UpdateId and Link .PARAMETER Source Search source. By default, Database is searched first, then if no matches are found, it tries finding it on the web. .PARAMETER EnableException By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. .NOTES Author: Chrissy LeMaire (@cl), netnerds.net Copyright: (c) licensed under MIT License: MIT https://opensource.org/licenses/MIT .EXAMPLE PS C:\> Get-KbUpdate KB4057119 Gets detailed information about KB4057119. .EXAMPLE PS C:\> Get-KbUpdate -Pattern KB4057119, 4057114 -Source Database Gets detailed information about KB4057119 and KB4057114. Only searches the database (useful for offline enviornments). .EXAMPLE PS C:\> Get-KbUpdate -Pattern MS15-101 -Source Web Downloads KBs related to MSRC MS15-101 to the current directory. Only searches the web and not the local db or WSUS. .EXAMPLE PS C:\> Connect-KbWsusServer -ComputerName server1 -SecureConnection PS C:\> Get-KbUpdate -Pattern KB2764916 This command will make a secure connection (Default: 443) to a WSUS server. Then use Wsus as a source for Get-KbUpdate. .EXAMPLE PS C:\> Connect-KbWsusServer -ComputerName server1 -SecureConnection PS C:\> Get-KbUpdate -Pattern KB2764916 -Source Database Search the database even if you've connected to WSUS in the same session. .EXAMPLE PS C:\> Get-KbUpdate -Pattern KB4057119, 4057114 -Simple A lil faster when using web as a source. Returns, at the very least: Title, Architecture, Language, UpdateId and Link .EXAMPLE PS C:\> Get-KbUpdate -Pattern "KB2764916 Nederlands" -Simple An alternative way to search for language specific packages #> [CmdletBinding()] param( [Parameter(Mandatory)] [Alias("Name", "HotfixId", "KBUpdate", "Id")] [string[]]$Pattern, [string[]]$Architecture, [string[]]$OperatingSystem, [PSFComputer[]]$ComputerName, [pscredential]$Credential, [string[]]$Product, [string[]]$Language, [switch]$Simple, [switch]$Latest, [ValidateSet("Wsus", "Web", "Database")] [string[]]$Source = @("Web", "Database"), [switch]$EnableException ) begin { if ($script:ConnectedWsus -and -not $PSBoundParameters.Source) { $Source = "Wsus" } function Get-KbItemFromDb { [CmdletBinding()] param($kb) process { # Join to dupe and check dupe $items = Invoke-SqliteQuery -DataSource $db -Query "select *, NULL AS SupersededBy, NULL AS Supersedes, NULL AS Link from kb where UpdateId in (select UpdateId from kb where UpdateId = '$kb' or Title like '%$kb%' or Id like '%$kb%' or Description like '%$kb%' or MSRCNumber like '%$kb%')" if (-not $items -and $Source -eq "Database") { Write-PSFMessage -Level Verbose -Message "No results found for $kb in the local database" } foreach ($item in $items) { $item.SupersededBy = Invoke-SqliteQuery -DataSource $db -Query "select KB, Description from SupersededBy where UpdateId = '$($item.UpdateId)'" $item.Supersedes = Invoke-SqliteQuery -DataSource $db -Query "select KB, Description from Supersedes where UpdateId = '$($item.UpdateId)'" $item.Link = (Invoke-SqliteQuery -DataSource $db -Query "select Link from Link where UpdateId = '$($item.UpdateId)'").Link $item } } } function Get-KbItemFromWsusApi ($kb) { $results = Get-PSWSUSUpdate -Update $kb foreach ($wsuskb in $results) { # cacher $guid = $wsuskb.UpdateID $hashkey = "$guid-$Simple" if ($script:kbcollection.ContainsKey($hashkey)) { $script:kbcollection[$hashkey] continue } $severity = $wsuskb.MsrcSeverity | Select-Object -First 1 $alert = $wsuskb.SecurityBulletins | Select-Object -First 1 if ($severity -eq "MsrcSeverity") { $severity = $null } if ($alert -eq "") { $alert = $null } $file = $wsuskb | Get-PSWSUSInstallableItem | Get-PSWSUSUpdateFile $link = $file.FileURI if ($null -ne $link -and "" -ne $link) { $link = $file.OriginUri } if ($link -eq "") { $link = $null } $null = $script:kbcollection.Add($hashkey, ( [pscustomobject]@{ Title = $wsuskb.Title Id = ($wsuskb.KnowledgebaseArticles | Select-Object -First 1) Architecture = $null Language = $null Hotfix = $null Description = $wsuskb.Description LastModified = $wsuskb.ArrivalDate Size = $wsuskb.Size Classification = $wsuskb.UpdateClassificationTitle SupportedProducts = $wsuskb.ProductTitles MSRCNumber = $alert MSRCSeverity = $severity RebootBehavior = $wsuskb.InstallationBehavior.RebootBehavior RequestsUserInput = $wsuskb.InstallationBehavior.CanRequestUserInput ExclusiveInstall = $null NetworkRequired = $wsuskb.InstallationBehavior.RequiresNetworkConnectivity UninstallNotes = $null # $wsuskb.uninstallnotes UninstallSteps = $null # $wsuskb.uninstallsteps UpdateId = $guid Supersedes = $null #TODO SupersededBy = $null #TODO Link = $link InputObject = $kb })) $script:kbcollection[$hashkey] } } function Get-GuidsFromWeb ($kb) { Write-PSFMessage -Level Verbose -Message "$kb" Write-Progress -Activity "Searching catalog for $kb" -Id 1 -Status "Contacting catalog.update.microsoft.com" $results = Invoke-TlsWebRequest -Uri "https://www.catalog.update.microsoft.com/Search.aspx?q=$kb" Write-Progress -Activity "Searching catalog for $kb" -Id 1 -Completed $kbids = $results.InputFields | Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } | Select-Object -ExpandProperty ID if (-not $kbids) { try { $null = Invoke-TlsWebRequest -Uri "https://support.microsoft.com/app/content/api/content/help/en-us/$kb" Stop-PSFFunction -EnableException:$EnableException -Message "Matches were found for $kb, but the results no longer exist in the catalog" return } catch { Write-PSFMessage -Level Verbose -Message "No results found for $kb at microsoft.com" return } } Write-PSFMessage -Level Verbose -Message "$kbids" # Thanks! https://keithga.wordpress.com/2017/05/21/new-tool-get-the-latest-windows-10-cumulative-updates/ $resultlinks = $results.Links | Where-Object ID -match '_link' | Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } # get the title too $guids = @() foreach ($resultlink in $resultlinks) { $itemguid = $resultlink.id.replace('_link', '') $itemtitle = ($resultlink.outerHTML -replace '<[^>]+>', '').Trim() if ($itemguid -in $kbids) { $guids += [pscustomobject]@{ Guid = $itemguid Title = $itemtitle } } } return $guids } function Get-KbItemFromWeb ($kb) { # Wishing Microsoft offered an RSS feed. Since they don't, we are forced to parse webpages. function Get-Info ($Text, $Pattern) { if ($Pattern -match "labelTitle") { # this should work... not accounting for multiple divs however? [regex]::Match($Text, $Pattern + '[\s\S]*?\s*(.*?)\s*<\/div>').Groups[1].Value } elseif ($Pattern -match "span ") { [regex]::Match($Text, $Pattern + '(.*?)<\/span>').Groups[1].Value } else { [regex]::Match($Text, $Pattern + "\s?'?(.*?)'?;").Groups[1].Value } } function Get-SuperInfo ($Text, $Pattern) { # this works, but may also summon cthulhu $span = [regex]::match($Text, $pattern + '[\s\S]*?<div id') switch -Wildcard ($span.Value) { "*div style*" { $regex = '">\s*(.*?)\s*<\/div>' } "*a href*" { $regex = "<div[\s\S]*?'>(.*?)<\/a" } default { $regex = '"\s?>\s*(\S+?)\s*<\/div>' } } $spanMatches = [regex]::Matches($span, $regex).ForEach( { $_.Groups[1].Value }) if ($spanMatches -eq 'n/a') { $spanMatches = $null } if ($spanMatches) { foreach ($superMatch in $spanMatches) { $detailedMatches = [regex]::Matches($superMatch, '\b[kK][bB]([0-9]{6,})\b') # $null -ne $detailedMatches can throw cant index null errors, get more detailed if ($null -ne $detailedMatches.Groups) { [PSCustomObject] @{ 'KB' = $detailedMatches.Groups[1].Value 'Description' = $superMatch } | Add-Member -MemberType ScriptMethod -Name ToString -Value { $this.Description } -PassThru -Force } } } } try { $guids = Get-GuidsFromWeb -kb $kb $scriptblock = { $guid = $psitem.Guid $itemtitle = $psitem.Title # cacher $hashkey = "$guid-$Simple" if ($script:kbcollection.ContainsKey($hashkey)) { $script:kbcollection[$hashkey] continue } Write-ProgressHelper -Activity "Found up to $($guids.Count) results for $kb" -Message "Getting results for $itemtitle" -TotalSteps $guids.Guid.Count -StepNumber $guids.Guid.IndexOf($guid) Write-Verbose -Message "Downloading information for $itemtitle" $post = @{ size = 0; updateID = $guid; uidInfo = $guid } | ConvertTo-Json -Compress $body = @{ updateIDs = "[$post]" } $downloaddialog = Invoke-TlsWebRequest -Uri 'https://www.catalog.update.microsoft.com/DownloadDialog.aspx' -Method Post -Body $body | Select-Object -ExpandProperty Content $title = Get-Info -Text $downloaddialog -Pattern 'enTitle =' $arch = Get-Info -Text $downloaddialog -Pattern 'architectures =' $longlang = Get-Info -Text $downloaddialog -Pattern 'longLanguages =' $updateid = Get-Info -Text $downloaddialog -Pattern 'updateID =' $ishotfix = Get-Info -Text $downloaddialog -Pattern 'isHotFix =' if ($ishotfix) { $ishotfix = "True" } else { $ishotfix = "False" } if ($longlang -eq "all") { $longlang = "All" } if ($arch -eq "") { $arch = $null } if ($arch -eq "AMD64") { $arch = "x64" } if ($title -match '64-Bit' -and $title -notmatch '32-Bit' -and -not $arch) { $arch = "x64" } if ($title -notmatch '64-Bit' -and $title -match '32-Bit' -and -not $arch) { $arch = "x86" } if (-not $Simple) { $detaildialog = Invoke-TlsWebRequest -Uri "https://www.catalog.update.microsoft.com/ScopedViewInline.aspx?updateid=$updateid" $description = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_desc">' $lastmodified = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_date">' $size = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_size">' $classification = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_labelClassification_Separator" class="labelTitle">' $supportedproducts = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_labelSupportedProducts_Separator" class="labelTitle">' $msrcnumber = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_labelSecurityBulliten_Separator" class="labelTitle">' $msrcseverity = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_msrcSeverity">' $kbnumbers = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_labelKBArticle_Separator" class="labelTitle">' $rebootbehavior = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_rebootBehavior">' $requestuserinput = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_userInput">' $exclusiveinstall = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_installationImpact">' $networkrequired = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_connectivity">' $uninstallnotes = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_labelUninstallNotes_Separator" class="labelTitle">' $uninstallsteps = Get-Info -Text $detaildialog -Pattern '<span id="ScopedViewHandler_labelUninstallSteps_Separator" class="labelTitle">' $supersededby = Get-SuperInfo -Text $detaildialog -Pattern '<div id="supersededbyInfo" TABINDEX="1" >' $supersedes = Get-SuperInfo -Text $detaildialog -Pattern '<div id="supersedesInfo" TABINDEX="1">' if ($uninstallsteps -eq "n/a") { $uninstallsteps = $null } if ($msrcnumber -eq "n/a") { $msrcnumber = $null } $products = $supportedproducts -split "," if ($products.Count -gt 1) { $supportedproducts = @() foreach ($line in $products) { $clean = $line.Trim() if ($clean) { $supportedproducts += $clean } } } } $downloaddialog = $downloaddialog.Replace('www.download.windowsupdate', 'download.windowsupdate') $links = $downloaddialog | Select-String -AllMatches -Pattern "(http[s]?\://download\.windowsupdate\.com\/[^\'\""]*)" | Select-Object -Unique foreach ($link in $links) { if ($kbnumbers -eq "n/a") { $kbnumbers = $null } $properties = $baseproperties if ($Simple) { $properties = $properties | Where-Object { $PSItem -notin "LastModified", "Description", "Size", "Classification", "SupportedProducts", "MSRCNumber", "MSRCSeverity", "RebootBehavior", "RequestsUserInput", "ExclusiveInstall", "NetworkRequired", "UninstallNotes", "UninstallSteps", "SupersededBy", "Supersedes" } } $ishotfix = switch ($ishotfix) { 'Yes' { $true } 'No' { $false } default { $ishotfix } } $requestuserinput = switch ($requestuserinput) { 'Yes' { $true } 'No' { $false } default { $requestuserinput } } $exclusiveinstall = switch ($exclusiveinstall) { 'Yes' { $true } 'No' { $false } default { $exclusiveinstall } } $networkrequired = switch ($networkrequired) { 'Yes' { $true } 'No' { $false } default { $networkrequired } } if ('n/a' -eq $uninstallnotes) { $uninstallnotes = $null } if ('n/a' -eq $uninstallsteps) { $uninstallsteps = $null } # may fix later $ishotfix = $null $null = $script:kbcollection.Add($hashkey, ( [pscustomobject]@{ Title = $title Id = $kbnumbers Architecture = $arch Language = $longlang Hotfix = $ishotfix Description = $description LastModified = $lastmodified Size = $size Classification = $classification SupportedProducts = $supportedproducts MSRCNumber = $msrcnumber MSRCSeverity = $msrcseverity RebootBehavior = $rebootbehavior RequestsUserInput = $requestuserinput ExclusiveInstall = $exclusiveinstall NetworkRequired = $networkrequired UninstallNotes = $uninstallnotes UninstallSteps = $uninstallsteps UpdateId = $updateid Supersedes = $supersedes SupersededBy = $supersededby Link = $link.matches.value InputObject = $kb })) $script:kbcollection[$hashkey] } } $guids | Invoke-Parallel -ImportVariables -ImportFunctions -ScriptBlock $scriptblock -ErrorAction Stop } catch { Stop-PSFFunction -EnableException:$EnableException -Message "Failure" -ErrorRecord $_ -Continue } } $properties = "Title", "Id", "Description", "Architecture", "Language", "Classification", "SupportedProducts", "MSRCNumber", "MSRCSeverity", "Size", "UpdateId", "RebootBehavior", "RequestsUserInput", "ExclusiveInstall", "NetworkRequired", "UninstallNotes", "UninstallSteps", "SupersededBy", "Supersedes", "LastModified", "Link" if ($Simple) { $properties = $properties | Where-Object { $PSItem -notin "ID", "LastModified", "Description", "Size", "Classification", "SupportedProducts", "MSRCNumber", "MSRCSeverity", "RebootBehavior", "RequestsUserInput", "ExclusiveInstall", "NetworkRequired", "UninstallNotes", "UninstallSteps", "SupersededBy", "Supersedes" } } if ($Source -eq "WSUS") { $properties = $properties | Where-Object { $PSItem -notin "Architecture", "Language", "Size", "ExclusiveInstall", "UninstallNotes", "UninstallSteps" } } # if latest is used, needs a collection $allkbs = @() } process { if ($Source -contains "Wsus" -and -not $script:ConnectedWsus) { Stop-PSFFunction -Message "Please use Connect-KbWsusServer before selecting WSUS as a Source" -EnableException:$EnableException return } if (Test-PSFPowerShell -Edition Core) { if (Was-Bound -Not -ParameterName Source) { Write-PSFMessage -Level Verbose -Message "Core detected. Switching source to Web." $Source = "Web" } else { if ($Source -ne "Web") { Stop-PSFFunction -Message "Core ony supports web scraping :(" -EnableException:$EnableException return } } } if ($Latest -and $Simple) { Write-PSFMessage -Level Warning -Message "Simple is ignored when Latest is specified, as latest requires detailed data" $Simple = $false } foreach ($computer in $Computername) { # tempting to add language but for now I won't $results = $script:compcollection[$computer] if (-not $results) { try { $results = Invoke-PSFCommand -ComputerName $computer -Credential $Credential -ScriptBlock { $proc = $env:PROCESSOR_ARCHITECTURE if ($proc -eq "AMD64") { $proc = "x64" } $os = Get-CimInstance Win32_OperatingSystem | Select-Object -ExpandProperty Caption $os = $os.Replace("Standard", "").Replace("Microsoft ", "").Replace(" Pro", "").Replace("Professional", "").Replace("Home", "").Replace("Enterprise", "").Replace("Datacenter", "").Trim() [pscustomobject]@{ Architecture = $proc OperatingSystem = $os } } -ErrorAction Stop } catch { Stop-PSFFunction -Message "Failure" -ErrorRecord $_ -EnableException:$EnableException return } $null = $script:compcollection.Add($computer, $results) } if ($results.Architecture) { if ($results.Architecture -notin $Architecture) { Write-PSFMessage -Level Verbose -Message "Adding $($results.Architecture)" $Architecture += $results.Architecture } } if ($results.OperatingSystem) { if ($results.OperatingSystem -notin $OperatingSystem) { Write-PSFMessage -Level Verbose -Message "Adding $($results.OperatingSystem)" $OperatingSystem += $results.OperatingSystem } } } $boundparams = @{ Architecture = $Architecture OperatingSystem = $OperatingSystem Product = $PSBoundParameters.Product Language = $PSBoundParameters.Language Source = $Source } foreach ($kb in $Pattern) { $result = $null if ($Source -contains "Wsus") { $result = Get-KbItemFromWsusApi $kb } if (-not $result -and $Source -contains "Database") { $result = Get-KbItemFromDb $kb } if (-not $result -and $Source -contains "Web") { $result = Get-KbItemFromWeb $kb } if ($Latest) { $allkbs += $result | Search-Kb @boundparams } else { $result | Search-Kb @boundparams | Select-DefaultView -Property $properties } } } end { # I'm not super awesome with the pipeline, and am open to suggestions if this is not the best way if ($Latest -and $allkbs) { $allkbs | Select-Latest | Select-DefaultView -Property $properties } } } # SIG # Begin signature block # MIIcYgYJKoZIhvcNAQcCoIIcUzCCHE8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU9S8uefrT/LHixztRDWAFw5bk # 46+ggheRMIIFGjCCBAKgAwIBAgIQAsF1KHTVwoQxhSrYoGRpyjANBgkqhkiG9w0B # AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz # c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTE3MDUwOTAwMDAwMFoXDTIwMDUx # MzEyMDAwMFowVzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMQ8wDQYD # VQQHEwZWaWVubmExETAPBgNVBAoTCGRiYXRvb2xzMREwDwYDVQQDEwhkYmF0b29s # czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAI8ng7JxnekL0AO4qQgt # Kr6p3q3SNOPh+SUZH+SyY8EA2I3wR7BMoT7rnZNolTwGjUXn7bRC6vISWg16N202 # 1RBWdTGW2rVPBVLF4HA46jle4hcpEVquXdj3yGYa99ko1w2FOWzLjKvtLqj4tzOh # K7wa/Gbmv0Si/FU6oOmctzYMI0QXtEG7lR1HsJT5kywwmgcjyuiN28iBIhT6man0 # Ib6xKDv40PblKq5c9AFVldXUGVeBJbLhcEAA1nSPSLGdc7j4J2SulGISYY7ocuX3 # tkv01te72Mv2KkqqpfkLEAQjXgtM0hlgwuc8/A4if+I0YtboCMkVQuwBpbR9/6ys # Z+sCAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrEuXsqCqOl6nEDwGD5LfZldQ5Y # MB0GA1UdDgQWBBRcxSkFqeA3vvHU0aq2mVpFRSOdmjAOBgNVHQ8BAf8EBAMCB4Aw # EwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1oDOgMYYvaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwNaAzoDGGL2h0 # dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3JsMEwG # A1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 # LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcBAQR4MHYwJAYI # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggrBgEFBQcwAoZC # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJ # RENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD # ggEBANuBGTbzCRhgG0Th09J0m/qDqohWMx6ZOFKhMoKl8f/l6IwyDrkG48JBkWOA # QYXNAzvp3Ro7aGCNJKRAOcIjNKYef/PFRfFQvMe07nQIj78G8x0q44ZpOVCp9uVj # sLmIvsmF1dcYhOWs9BOG/Zp9augJUtlYpo4JW+iuZHCqjhKzIc74rEEiZd0hSm8M # asshvBUSB9e8do/7RhaKezvlciDaFBQvg5s0fICsEhULBRhoyVOiUKUcemprPiTD # xh3buBLuN0bBayjWmOMlkG1Z6i8DUvWlPGz9jiBT3ONBqxXfghXLL6n8PhfppBhn # daPQO8+SqF5rqrlyBPmRRaTz2GQwggUwMIIEGKADAgECAhAECRgbX9W7ZnVTQ7Vv # lVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp # Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0Rp # Z2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEwMjIxMjAwMDBaFw0yODEw # MjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNI # QTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7RZmxOttE9X/lqJ3bMtdx # 6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p0WfTxvspJ8fTeyOU5JEj # lpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj6YgsIJWuHEqHCN8M9eJN # YBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grkV7tKtel05iv+bMt+dDk2 # DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHyDxL0xY4PwaLoLFH3c7y9 # hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMBAAGjggHNMIIByTASBgNV # HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF # BQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDig # NoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v # dENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwAAgQwKjAo # BggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAKBghghkgB # hv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHwYDVR0jBBgwFoAU # Reuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEBAD7sDVoks/Mi # 0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q3yBVN7Dh9tGSdQ9RtG6l # jlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/kLEbBw6RFfu6r7VRwo0k # riTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dcIFzZcbEMj7uo+MUSaJ/P # QMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6dGRrsutmQ9qzsIzV6Q3d # 9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT+hKUGIUukpHqaGxEMrJm # oecYpJpkUe8wggZqMIIFUqADAgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3 # DQEBBQUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX # BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3Vy # ZWQgSUQgQ0EtMTAeFw0xNDEwMjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJ # BgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQg # VGltZXN0YW1wIFJlc3BvbmRlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBAKNkXfx8s+CCNeDg9sYq5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sC # SVDZg85vZu7dy4XpX6X51Id0iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/US # s3OWCmejvmGfrvP9Enh1DqZbFP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYt # WQJhiGFyGGi5uHzu5uc0LzF3gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZb # esF6uHjHyQYuRhDIjegEYNu8c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJ # YczQCMxr7GJCkawCwO+k8IkRj3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIH # gDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0g # BIIBtjCCAbIwggGhBglghkgBhv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczov # L3d3dy5kaWdpY2VydC5jb20vQ1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4A # eQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQA # ZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUA # IABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAA # YQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcA # cgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIA # aQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQA # ZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsG # CWCGSAGG/WwDFTAfBgNVHSMEGDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNV # HQ4EFgQUYVpNJLZJMp1KKnkag0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmww # OKA2oDSGMmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ # RENBLTEuY3JsMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j # c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUF # AAOCAQEAnSV+GzNNsiaBXJuGziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd # 42yE5FpA+94GAYw3+puxnSR+/iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCg # e5fH9j/n4hFBpr1i2fAnPTgdKG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7k # A7YUq/OPQ6dxnSHdFMoVXZJB2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7 # Cqsc21xIJ2bIo4sKHOWV2q7ELlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIK # SK+w1G7g9BQKOhvjjz3Kr2qNe9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArr # Pye7uhswDQYJKoZIhvcNAQEFBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp # Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMb # RGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIx # MTExMDAwMDAwMFowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu # YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQg # QXNzdXJlZCBJRCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA # 6IItmfnKwkKVpYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5 # tHdJ3InECtqvy15r7a2wcTHrzzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIP # kg5QycaH6zY/2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2x # QaPtP77blUjE7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9I # hJtPQLnxTPKvmPv2zkBdXPao8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcK # J1Z8D2KkPzIUYJX9BwSiCQIDAQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsG # A1UdJQQ0MDIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwME # BggrBgEFBQcDCDCCAdIGA1UdIASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQw # OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVw # b3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUA # IABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4A # cwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQA # aABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQA # aABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUA # bgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkA # IABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUA # cgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMV # MBIGA1UdEwEB/wQIMAYBAf8CAQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQw # gYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUA # EisTmLKZB+0e36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3z # bcgPMA0GCSqGSIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvV # DQtBs+/sdR90OPKyXGGinJXDUOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3 # zCSl8wQZVann4+erYs37iy2QwsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1 # zh14dpQlc+Qqq8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3G # XZG5D2dFzdaD7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwI # VYUiuOsYGk38KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIEOzCCBDcC # AQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG # A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBB # c3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQAsF1KHTVwoQxhSrYoGRpyjAJBgUr # DgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMx # DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkq # hkiG9w0BCQQxFgQUQkkKio/wvHZnCgrg0J/vVi8AeXMwDQYJKoZIhvcNAQEBBQAE # ggEAXYQcH3htc82QhjoDH+cMdapsJlS6Sfeey9QozVl28lGjNX0HB8ovqfh5TBuv # 3CkmC+ydXVYQEi5zdZrWzKoVmarJ7QGySa7ejyIl9SjfIp7Yif2lFFptMTRETD6r # pzfLB/aLaRO2ZpI4v3cM8pcrKwnm5pb9mlnvyspvjw3zGMEzXzPfGNfNALHdgjyC # I0shE54RDdVOrUvA/BnH5fbcu74ThzLxkr06oUqUOQZHZ45CAxh0D/ajdpKF+ltb # TSMHqFuv7bHWc36GGyRpCBkQ1Kvc65qDwj+X8UL1abRGJ0RJuDFTWsYR5IvkIeWI # HA+tDRVpPPV/vxZFlDQpUyyC9KGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIB # ATB2MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNV # BAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQg # SUQgQ0EtMQIQAwGaAjr/WLFr1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjAwMjA0MDk1OTEyWjAj # BgkqhkiG9w0BCQQxFgQU8YFhx7FckwpISQK17lDaXXGqMIYwDQYJKoZIhvcNAQEB # BQAEggEAO/SYLVxU3GGL4nNb/Y67HPzX4rOCuWBNEBeIi5GtWMbnh9GXNTvVKj5X # rNN/MnnrXvED8sb8VNFoFhnFxgWRs/S2Wa48b5YYYzmL/wPmVENF0f5zhezjd443 # XcprfU5yFL/z/M1c6H4yyGpDK7nZ7odU6OD0s/b2i1vPtN6FuWuj+67IFhQL/Gxf # WGN0hx+kfPeCXRCcLNlAtY5c8qUuwIcrhmSyPLFACZ8/Ga4bWSuvb4YMOpqA8ILq # Pyzirrk968oeowYUyESubwrsEK8qr+iz+l3afdcM7ZvMKxsd0BzcR4C/3wLsuJO2 # S98a7dSbAx79f0/8w+JSJqjM+Q8ywg== # SIG # End signature block |