public/Uninstall-KbUpdate.ps1

function Uninstall-KbUpdate {
    <#
    .SYNOPSIS
        Uninstalls KB updates on Windows-based systems
 
    .DESCRIPTION
        Uninstalls KB updates on Windows-based systems
 
        Note that sometimes, an uninstall will leave registry entries and Get-KbInstalledSoftware will report the product is installed. This is the behavior of some patches and happens even when using the Windows uninstall GUI.
 
    .PARAMETER ComputerName
        Used to connect to a remote host
 
    .PARAMETER Credential
        The optional alternative credential to be used when connecting to ComputerName
 
    .PARAMETER HotfixId
        The HotfixId of the patch
 
    .PARAMETER InputObject
        Allows results to be piped in from Get-KbInstalledSoftware
 
    .PARAMETER ArgumentList
        Allows you to override our automatically determined ArgumentList
 
    .PARAMETER NoQuiet
        By default, we add a /quiet switch to the argument list to ensure the command can run from the command line.
 
        Some commands may not support this switch, however, so to remove it use NoQuiet.
 
        Not required if you use ArgumentList.
 
    .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), Jess Pomfret (@jpomfret)
        Copyright: (c) licensed under MIT
        License: MIT https://opensource.org/licenses/MIT
 
    .EXAMPLE
        PS C:\> Uninstall-KbUpdate -ComputerName sql2017 -HotfixId kb4498951
 
        Uninstalls kb4498951 on sql2017
 
    .EXAMPLE
        PS C:\> Uninstall-KbUpdate -ComputerName sql2017 -HotfixId kb4498951 -Confirm:$false
 
        Uninstalls kb4498951 on sql2017 without prompts
 
    .EXAMPLE
        PS C:\> Get-KbInstalledSoftware -ComputerName server23, server24 -Pattern kb4498951 | Uninstall-KbUpdate
 
        Uninstalls kb4498951 from server23 and server24
 
    .EXAMPLE
        PS C:\> Uninstall-KbUpdate -ComputerName sql2017 -HotfixId KB4534273 -WhatIf
 
        Shows what would happen if the command were to run but does not execute any changes
 
    .EXAMPLE
        PS C:\> Install-KbUpdate -ComputerName sql2017 -FilePath \\dc\sql\windows10.0-kb4486129-x64_0b61d9a03db731562e0a0b49383342a4d8cbe36a.msu
        PS C:\> Get-KbInstalledSoftware -Pattern kb4486129 -ComputerName sql2017 | Uninstall-KbUpdate
 
        Quick lil example to show an install, followed by an uninstall
#>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [PSFComputer[]]$ComputerName,
        [PSCredential]$Credential,
        [Alias("Name", "KBUpdate", "Id")]
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$HotfixId,
        [Parameter(ValueFromPipeline)]
        [pscustomobject[]]$InputObject,
        [string]$ArgumentList,
        [switch]$NoQuiet,
        [switch]$EnableException
    )
    begin {
        $programscriptblock = {
            param (
                $Program,
                $ArgumentList,
                $hotfix,
                $Name,
                $VerbosePreference
            )
            function Invoke-UninstallCommand ($Program, $ArgumentList) {
                $pinfo = New-Object System.Diagnostics.ProcessStartInfo
                $pinfo.FileName = $Program
                $pinfo.RedirectStandardError = $true
                $pinfo.RedirectStandardOutput = $true
                $pinfo.UseShellExecute = $false
                $pinfo.Arguments = $ArgumentList
                $p = New-Object System.Diagnostics.Process
                $p.StartInfo = $pinfo
                $null = $p.Start()
                $p.WaitForExit()
                [pscustomobject]@{
                    stdout   = $p.StandardOutput.ReadToEnd()
                    stderr   = $p.StandardError.ReadToEnd()
                    ExitCode = $p.ExitCode
                }
            }
            $firstarg = $ArgumentList -split " " | Select-Object -First 1
            if ($firstarg -match ".exe") {
                $ArgumentList = $ArgumentList.Replace($firstarg, "")
                $Program = "$Program$firstarg"
            }
            Write-Verbose -Message "Program = $Program"
            Write-Verbose -Message "ArgumentList = $ArgumentList"
            $results = $null
            $results = Invoke-UninstallCommand -Program $Program -ArgumentList $ArgumentList
            $output = $results.stdout.Trim()

            # -2067919934 is reboot needed but the output already tells you to reboot
            # Perhaps suggest people check out C:\Windows\Logs\CBS\CBS.log
            # Only package owners can remove package: Package_10_for_KB4532947~31bf3856ad364e35~amd64~~10.0.1.2565 [HRESULT = 0x80070005 - E_ACCESSDENIED]

            <#
            0 { "Uninstallation command triggered successfully" }
            2 { "You don't have sufficient permissions to trigger the command on $Computer" }
            3 { "You don't have sufficient permissions to trigger the command on $Computer" }
            8 { "An unknown error has occurred" }
            9 { "Path Not Found" }
            9 { "Invalid Parameter"}
            #>

            switch ($results.ExitCode) {
                -2068052310 {
                    $output = "$output`n`nThe exit code suggests that you need to mount the SQL Server ISO so the uninstaller can find the setup files."
                }
                -2068643839 {
                    $output = "$output`n`nThe exit code suggests that you need to mount the SQL Server ISO so the uninstaller can find the setup files."
                }
                -2068709375 {
                    $output = "$output`n`nYou likely need to reboot $env:ComputerName."
                }
                -2067919934 {
                    $output = "$output`n`nThe exit code suggests that something is corrupt. See if this tutorial helps: http://www.sqlcoffee.com/Tips0026.htm"
                }
                3010 {
                    $output = "You have successfully uninstalled $Name. A restart is now required to finalize the uninstall."
                }
                0 {
                    if ($output.Trim()) {
                        $output = "$output`n`nYou have successfully uninstalled $Name"
                    } else {
                        if ($Name) {
                            $output = "$Name has been successfully uninstalled"
                        }
                    }
                }
            }

            [pscustomobject]@{
                ComputerName = $env:ComputerName
                Name         = $Name
                HotfixID     = $hotfix
                ExitCode     = $results.ExitCode
                Results      = $output
            }
        }
    }
    process {
        if (-not $InputObject -and $HotfixId) {
            foreach ($hotfix in $HotfixId) {
                if (-not $hotfix.ToUpper().StartsWith("KB") -and $PSBoundParameters.HotfixId) {
                    $hotfix = "KB$hotfix"
                }

                foreach ($computer in $ComputerName) {
                    Write-PSFMessage -Level Verbose -Message "Adding uninstall for $hotfix to queue on $computer"

                    $exists = Get-KbInstalledSoftware -Pattern $hotfix -ComputerName $computer -IncludeHidden
                    if (-not $exists) {
                        Write-PSFMessage -Level Warning -Message "$hotfix is not installed on $computer"
                    } else {
                        # turns out this is not true
                        # Stop-PSFFunction -EnableException:$EnableException -Message "You must restart before #you can uninstall $hotfix on $computer" -Continue
                        #} else {
                        if ($exists.FastPackageReference -and $exists.FastPackageReference -notin $InputObject.FastPackageReference) {
                            $InputObject += $exists
                        } elseif ($exists.PackageObject -and $exists.PackageObject -notin $InputObject.PackageObject) {
                            $InputObject += $exists
                        }
                    }
                }
            }
            $InputObject = $InputObject | Sort-Object FastPackageReference -Unique
        }
        if ($IsLinux -or $IsMacOs) {
            Stop-PSFFunction -Message "This command uses remoting and only supports Windows at this time" -EnableException:$EnableException
            return
        }

        foreach ($update in $InputObject) {
            $computer = $update.ComputerName
            $packagename = $update.Name
            if (-not $packagename) {
                $packagename = $update.HotFixID
            }

            if (-not $packagename) {
                $packagename = $update.HotFixID
            }
            if (-not $packagename) {
                $packagename = $update.InstallName
            }
            if (-not $computer) {
                Stop-PSFFunction -Message "No computername associated with $packagename, moving on" -Continue -EnableException:$EnableException
            }

            Write-PSFMessage -Level Verbose -Message "Processing $computer"

            if (-not (Test-ElevationRequirement -ComputerName $computer)) {
                Stop-PSFFunction -Message "To run this command locally, you must run as admin." -Continue -EnableException:$EnableException
            }

            if ($PSBoundParameters.ArgumentList) {
                $needuninstallstring = $false
                if ($update.QuietUninstallString) {
                    $string = $update.QuietUninstallString
                } else {
                    $string = $update.UninstallString
                }
                $path = $string -match '^(".+") (/.+) (/.+)'
                if ($matches) {
                    $program = $matches[1]
                }
                if (-not $path) {
                    $program = Split-Path $string
                }
            } elseif ($update.QuietUninstallString -and $update.ProviderName -eq "Programs" -and -not $NoQuiet) {
                $path = $update.QuietUninstallString -match '^(".+") (/.+) (/.+)'
                if ($matches) {
                    $needuninstallstring = $false
                    $program = $matches[1]
                }
                if (-not $path) {
                    $program = Split-Path $update.QuietUninstallString
                }
                $ArgumentList = $update.QuietUninstallString.Replace($program, "")
            } elseif ($update.UninstallString -and $update.ProviderName -eq "Programs" -and $update.UninstallString -notmatch "SetupARP.exe") {
                $path = $update.UninstallString -match '^(".+") (/.+) (/.+)'
                if ($matches) {
                    $needuninstallstring = $false
                    $program = $matches[1]
                }
                if (-not $path) {
                    $program = Split-Path $update.UninstallString
                }
                $ArgumentList = $update.UninstallString.Replace($program, "")
                if ($ArgumentList -match "msedge") {
                    $ArgumentList = "$ArgumentList --force-uninstall"
                } elseif ($ArgumentList -notmatch "/quiet" -and -not $NoQuiet -and -not $PSBoundParameters.ArgumentList -and $ArgumentList -ne "/S" -and $ArgumentList -ne "/Q") {
                    $ArgumentList = "$ArgumentList /quiet"
                }
            }

            if ($needuninstallstring) {
                <#
                    I have so many notes from so many different attempts to address this flawlessly
 
                    GET-PACKAGE
                    Get-Package | Uninstall-Package is buggy per https://stackoverflow.com/questions/54740151/get-package-notepad-uninstall-package-force-not-working
                    The Uninstall-Package cmdlet won't work with these entries (i.e. ones where "ProviderName" is "Programs").
 
                    Another BIG gotcha with PackageManagement/PowerShellGet Modules that I ran into recently - if you uninstall a Program that was installed via PackageManagement via the Control Panel GUI,
                    the Get-Package cmdlet will still show it as installed until you run the Uninstall-Package cmdlet on the erroneous entry.
 
                    PKGMGR
                    http://msiworld.blogspot.com/2012/04/silent-install-and-uninstall-of-msu.html
                    pkgmgr = DISM
                    $ArgumentList = "/up:$installname"
 
                    WUSA
                    Newer versions of win10 doesnt support old-style wusa, go for DISM /quiet /norestart
                    https://support.microsoft.com/en-us/help/934307/description-of-the-windows-update-standalone-installer-in-windows
 
                    MSIEXEC WITH PACKAGE GUID + GUID OF PATCH
                    Could never figure out how to get GUID-OF-PRODUCT
                    https://docs.microsoft.com/en-us/windows/win32/msi/uninstalling-patches?redirectedfrom=MSDN
                    Msiexec /i {installpath_of_product} MSIPATCHREMOVE={installpath_of_patch} /qb
                    Msiexec /package {GUID-OF-PRODUCT} /uninstall {GUID_OF_PATCH} /passive
 
                    WMIC
                    Took too long
                    wmic product where "name like 'Java 8%%'" and not name 'Java 8 Update 101%%'" call uninstall /nointeractive
 
                    VARIOUS ARTISTS
                    provides various ways from https://support.symantec.com/us/en/article.howto42396.html
                    introduced me to msipatchremove and how to reverse enginer guid
                    https://docs.microsoft.com/en-us/office/troubleshoot/installation/automate-uninstall-office-update
 
                    DISM
                    props for highlighting that the installversion is important for win10
                    this allowed me to find the InstallName
                    https://social.technet.microsoft.com/Forums/Lync/en-US/f6594e00-2400-4276-85a1-fb06485b53e6/issues-with-wusaexe-and-windows-10-enterprise?forum=win10itprogeneral
                    #>

                $installname = $update.InstallName

                if (-not $installname) {
                    $installname = ($update.CBSPackageObject).PSChildName
                }

                if (-not $installname) {
                    Stop-PSFFunction -EnableException:$EnableException -Message "Couldn't determine a way to uninstall $($update.Name). It may be marked as a permanent install or part of another package that contains the unintaller." -Continue
                }
                $program = "dism"
                $parms = @("/Online /Remove-Package /quiet /norestart")
                foreach ($install in $installname) {
                    $parms += "/PackageName:$install"
                }
                $ArgumentList = $parms -join " "
            }

            # I tried to get this working using DSC but in end end, a Start-Process equivalent was it for the convenience of not having to specify a filename, tho that can be added as a backup
            if ($ArgumentList -match ".exe") {
                $exec = "$program$ArgumentList".Trim()
            } else {
                $exec = "$program $ArgumentList".Trim()
            }

            if ($exec.Length -lt 4) {
                Stop-PSFFunction -Message "Failure on $computer while attempting to uninstall $packagename | Uninstaller cannot be found" -EnableException:$EnableException -Continue
            }
            if ($PSCmdlet.ShouldProcess($computer, "Uninstalling Hotfix $packagename by executing $exec")) {
                try {
                    $jobs = @()
                    foreach ($computer in $ComputerName) {
                        Write-PSFMessage -Level Verbose -Message "Adding job for $computer"
                        $arglist = [pscustomobject]@{
                            ComputerName = $computer
                            Credential   = $Credential
                            ScriptBlock  = $programscriptblock
                            ArgumentList = $Program, $ArgumentList, $object.HotfixId, $packagename, $VerbosePreference
                            ModulePath   = $script:dependencies
                        }
                        $invokeblock = {
                            foreach ($path in $args.ModulePath) {
                                $null = Import-Module $path 4>$null
                            }
                            $sb = [scriptblock]::Create($args.ScriptBlock)
                            $parms = @{
                                ComputerName = $args.ComputerName
                                Credential   = $args.Credential
                                ScriptBlock  = $sb
                                ArgumentList = $args.ArgumentList
                            }
                            Invoke-KbCommand @parms -ErrorAction Stop
                        }
                        $jobs += Start-Job -Name $computer -ScriptBlock $invokeblock -ArgumentList $arglist -ErrorAction Stop
                    }
                } catch {
                    Stop-PSFFunction -Message "Failure on $computer while attempting to uninstall $packagename" -ErrorRecord $_ -EnableException:$EnableException
                }

                if ($jobs.Name) {
                    try {
                        $jobs | Start-JobProcess -Activity "Uninstalling software" -Status "uninstalling software" |
                        Select-Object -Property * -ExcludeProperty PSComputerName, RunspaceId
                    } catch {
                        Stop-PSFFunction -Message "Failure" -ErrorRecord $PSItem -EnableException:$EnableException -Continue
                    }
                }
            }
        }
    }
}
# SIG # Begin signature block
# MIIjYAYJKoZIhvcNAQcCoIIjUTCCI00CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCdtldS1s8bvnxZ
# D49Ln1BZLZ94rMoCP4VxR5qImt2lsaCCHVkwggUaMIIEAqADAgECAhADBbuGIbCh
# Y1+/3q4SBOdtMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcN
# MjAwNTEyMDAwMDAwWhcNMjMwNjA4MTIwMDAwWjBXMQswCQYDVQQGEwJVUzERMA8G
# A1UECBMIVmlyZ2luaWExDzANBgNVBAcTBlZpZW5uYTERMA8GA1UEChMIZGJhdG9v
# bHMxETAPBgNVBAMTCGRiYXRvb2xzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAvL9je6vjv74IAbaY5rXqHxaNeNJO9yV0ObDg+kC844Io2vrHKGD8U5hU
# iJp6rY32RVprnAFrA4jFVa6P+sho7F5iSVAO6A+QZTHQCn7oquOefGATo43NAadz
# W2OWRro3QprMPZah0QFYpej9WaQL9w/08lVaugIw7CWPsa0S/YjHPGKQ+bYgI/kr
# EUrk+asD7lvNwckR6pGieWAyf0fNmSoevQBTV6Cd8QiUfj+/qWvLW3UoEX9ucOGX
# 2D8vSJxL7JyEVWTHg447hr6q9PzGq+91CO/c9DWFvNMjf+1c5a71fEZ54h1mNom/
# XoWZYoKeWhKnVdv1xVT1eEimibPEfQIDAQABo4IBxTCCAcEwHwYDVR0jBBgwFoAU
# WsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYEFPDAoPu2A4BDTvsJ193ferHL
# 454iMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzB3BgNVHR8E
# cDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk
# LWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt
# YXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAwEwKjAoBggr
# BgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBBAEw
# gYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
# cnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/
# BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAj835cJUMH9Y2pBKspjznNJwcYmOxeBcH
# Ji+yK0y4bm+j44OGWH4gu/QJM+WjZajvkydJKoJZH5zrHI3ykM8w8HGbYS1WZfN4
# oMwi51jKPGZPw9neGS2PXrBcKjzb7rlQ6x74Iex+gyf8z1ZuRDitLJY09FEOh0BM
# LaLh+UvJ66ghmfIyjP/g3iZZvqwgBhn+01fObqrAJ+SagxJ/21xNQJchtUOWIlxR
# kuUn9KkuDYrMO70a2ekHODcAbcuHAGI8wzw4saK1iPPhVTlFijHS+7VfIt/d/18p
# MLHHArLQQqe1Z0mTfuL4M4xCUKpebkH8rI3Fva62/6osaXLD0ymERzCCBTAwggQY
# oAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4X
# DTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx
# MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBD
# QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/DhGvZ3cH0wsx
# SRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2qvCchqXYJawO
# eSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrskacLCUvIUZ4qJ
# RdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/6XzLkqHlOzEc
# z+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE94zRICUj6whk
# PlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8np+mM6n9Gd8l
# k9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQD
# AgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0wazAkBggrBgEF
# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRw
# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu
# Y3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsME8GA1UdIARI
# MEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdp
# Y2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7KgqjpepxA8Bg
# +S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG
# 9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh134LYP3DPQ/E
# r4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63XX0R58zYUBor3
# nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPAJRHinBRHoXpo
# aK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC/i9yfhzXSUWW
# 6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG/AeB+ova+YJJ
# 92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBY0wggR1oAMCAQICEA6bGI75
# 0C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIG
# A1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAw
# MFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD
# ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGln
# aUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuE
# DcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNw
# wrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs0
# 6wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e
# 5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtV
# gkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85
# tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+S
# kjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1Yxw
# LEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzl
# DlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFr
# b7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATow
# ggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiu
# HA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQE
# AwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2
# hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290
# Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/
# Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNK
# ei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHr
# lnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4
# oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5A
# Y8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNN
# n3O3AamfV6peKOK5lDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJ
# KoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu
# YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQg
# VHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVow
# YzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQD
# EzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGlu
# ZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklR
# VcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54P
# Mx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupR
# PfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvo
# hGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV
# 5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYV
# VSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6i
# c/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/Ci
# PMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5
# K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oi
# qMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuld
# yF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAG
# AQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAW
# gBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww
# CgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDow
# OKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRS
# b290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkq
# hkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvH
# UF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0M
# CIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCK
# rOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rA
# J4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZ
# xhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScs
# PT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1M
# rfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXse
# GYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWY
# MbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYp
# hwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPww
# ggbAMIIEqKADAgECAhAMTWlyS5T6PCpKPSkHgD1aMA0GCSqGSIb3DQEBCwUAMGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0EwHhcNMjIwOTIxMDAwMDAwWhcNMzMxMTIxMjM1OTU5WjBGMQswCQYDVQQGEwJV
# UzERMA8GA1UEChMIRGlnaUNlcnQxJDAiBgNVBAMTG0RpZ2lDZXJ0IFRpbWVzdGFt
# cCAyMDIyIC0gMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM/spSY6
# xqnya7uNwQ2a26HoFIV0MxomrNAcVR4eNm28klUMYfSdCXc9FZYIL2tkpP0GgxbX
# kZI4HDEClvtysZc6Va8z7GGK6aYo25BjXL2JU+A6LYyHQq4mpOS7eHi5ehbhVsbA
# umRTuyoW51BIu4hpDIjG8b7gL307scpTjUCDHufLckkoHkyAHoVW54Xt8mG8qjoH
# ffarbuVm3eJc9S/tjdRNlYRo44DLannR0hCRRinrPibytIzNTLlmyLuqUDgN5YyU
# XRlav/V7QG5vFqianJVHhoV5PgxeZowaCiS+nKrSnLb3T254xCg/oxwPUAY3ugjZ
# Naa1Htp4WB056PhMkRCWfk3h3cKtpX74LRsf7CtGGKMZ9jn39cFPcS6JAxGiS7uY
# v/pP5Hs27wZE5FX/NurlfDHn88JSxOYWe1p+pSVz28BqmSEtY+VZ9U0vkB8nt9Kr
# FOU4ZodRCGv7U0M50GT6Vs/g9ArmFG1keLuY/ZTDcyHzL8IuINeBrNPxB9Thvdld
# S24xlCmL5kGkZZTAWOXlLimQprdhZPrZIGwYUWC6poEPCSVT8b876asHDmoHOWIZ
# ydaFfxPZjXnPYsXs4Xu5zGcTB5rBeO3GiMiwbjJ5xwtZg43G7vUsfHuOy2SJ8bHE
# uOdTXl9V0n0ZKVkDTvpd6kVzHIR+187i1Dp3AgMBAAGjggGLMIIBhzAOBgNVHQ8B
# Af8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAg
# BgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZ
# bU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFGKK3tBh/I8xFO2XC809KpQU31Kc
# MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAG
# CCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
# dC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggIBAFWqKhrzRvN4Vzcw/HXjT9aFI/H8+ZU5myXm93KK
# mMN31GT8Ffs2wklRLHiIY1UJRjkA/GnUypsp+6M/wMkAmxMdsJiJ3HjyzXyFzVOd
# r2LiYWajFCpFh0qYQitQ/Bu1nggwCfrkLdcJiXn5CeaIzn0buGqim8FTYAnoo7id
# 160fHLjsmEHw9g6A++T/350Qp+sAul9Kjxo6UrTqvwlJFTU2WZoPVNKyG39+Xgmt
# dlSKdG3K0gVnK3br/5iyJpU4GYhEFOUKWaJr5yI+RCHSPxzAm+18SLLYkgyRTzxm
# lK9dAlPrnuKe5NMfhgFknADC6Vp0dQ094XmIvxwBl8kZI4DXNlpflhaxYwzGRkA7
# zl011Fk+Q5oYrsPJy8P7mxNfarXH4PMFw1nfJ2Ir3kHJU7n/NBBn9iYymHv+XEKU
# gZSCnawKi8ZLFUrTmJBFYDOA4CPe+AOk9kVH5c64A0JH6EE2cXet/aLol3ROLtoe
# HYxayB6a1cLwxiKoT5u92ByaUcQvmvZfpyeXupYuhVfAYOd4Vn9q78KVmksRAsiC
# nMkaBXy6cbVOepls9Oie1FqYyJ+/jbsYXEP10Cro4mLueATbvdH7WwqocH7wl4R4
# 4wgDXUcsY6glOJcB0j862uXl9uab3H4szP8XTE0AotjWAQ64i+7m4HJViSwnGWH2
# dwGMMYIFXTCCBVkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD
# ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGln
# aUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQAwW7hiGwoWNf
# v96uEgTnbTANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgACh
# AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM
# BgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAjUO+FYDS5vUyM+Gc0VO0BsYMY
# PihNL0jMJrdpx6UUtDANBgkqhkiG9w0BAQEFAASCAQA+lNI5zvOK3SVILewtIRX+
# 8em2+AkhCo1GIwOYxmSAL3yYq4yKODoS62sAx1HECoyNzZT5uDNTlXzNakBEfIje
# bkhdRtREqHiWjaUo0W+oJOyd2nqlWyNjEKzrW+xB1U+33epaBkiJaUUFnDMgjUu5
# UtdRE0TLtai7+R1BaStYIiJREpZnSZ+XMx5iV3ZD6Rw/yzEVoAszXnOdiwMcCbG7
# +ZFzZX9pTlpo7KGz+fPbl0hQPHB9XZcFScfaOhgJEtb8QbCuMxr7Ld94D8IM4/DL
# dPznYcbdzza1aVW0TXZRNBqZAZALBapgtaPciIgHki4lc3ouI/oceVU2p9KvgbMM
# oYIDIDCCAxwGCSqGSIb3DQEJBjGCAw0wggMJAgEBMHcwYzELMAkGA1UEBhMCVVMx
# FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVz
# dGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQQIQDE1pckuU+jwq
# Sj0pB4A9WjANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0B
# BwEwHAYJKoZIhvcNAQkFMQ8XDTIyMTIxMzIxMTYwM1owLwYJKoZIhvcNAQkEMSIE
# IAbPQh6eBayBerOgGDTa6PdlMjYRYg1dFp6nPNymthFjMA0GCSqGSIb3DQEBAQUA
# BIICAMMK85yHUOPDKoCfeQ3t5Y+UfqJ5mwYN9a5ghwClAiXmJKR9YDvdQ8UN15zk
# E6z45jc+d1G5HdZIjCzgzKZ2x+SNuRMsozDHURmfmp2MUS4aCtct0PlfZrPGiQ2Y
# x5JW8w81mJgVDysgE0x1uqu+gUoP9kiKnUUs5VVvgg5TXpvDLBqOFT8A6ESi5f2s
# fVNdxOUNCLkIvDo4Id2u3TU4djhPS7NDhPouF4ShuoMgjsoCDxy85K0ozuv16C6v
# vfHZVfOcR7/F9CK6GKdRn7/EkGJz0KSAj16sqiOk6is9yS1PdRc3yqmom6lNEClJ
# Iz2TvZlaziF9PYgkcwR1NvS5vmJUPSKGcltEVgLgC6WVUAMBja+46BsC963BFMKk
# wnGwL14N7IxUPlDTOlXVLktvz7VQrtl0NxDZcGuyEfu9rZOGi7J9f3JUgi8SWkVB
# ZjpZhpqx1MLJNxllBFpQAcnw7TeazSOMU/XQrZwaAcTdff8FRI6MYLo+v9iulCu3
# ts6Kx2kM67VbhubWP1QjZGxp+A9qBTEHwYkuo6/38U4HDycimcRy0+NR3NcFuy/+
# dV5QRAEKJyX0NDho0m7lCi8HvTtuSXQWqvPI0Vk9aOXV+19+7E6sr3Wm+E1cWJmw
# Qeoli3namdJyZEcqatpfxJZ+DdpLoHIaPLRq0RYDDJH416+9
# SIG # End signature block