Functions/Backup-CertificateServicesDatabase.ps1

<#
 
.SYNOPSIS
    Back up the CA server database and additional configuration information.
 
.PARAMETER LocalPath
    The local file path to store backup files. The folder is created if it does not exist and its permissions are restricted to SYSTEM and Administrators.
 
.PARAMETER RemotePath
    The remote file path to store the backup file archive. Archives are timestamped (cabackup_<hostname>_<yyyyMMdd-HHmmss>.zip) and older archives are removed according to the RetentionCount parameter.
 
.PARAMETER IncludePrivateKey
    Indicates that private keys should be included in the backup.
 
.PARAMETER Password
    A SecureString password used to protect exported private keys. Required for non-interactive (scheduled) use with the IncludePrivateKey parameter. If omitted in an interactive session, the operator is prompted.
 
.PARAMETER RetentionCount
    The number of archive files to retain in the remote path. Older archives beyond this count are deleted. The default value is 14.
 
.EXAMPLE
    Backup-CertificateServicesDatabase
 
    Running this PowerShell command will back up the local certificate services database and configuration files to the default location C:\CaBackup.
 
.EXAMPLE
    Backup-CertificateServicesDatabase -IncludePrivateKey
 
    Running this PowerShell command will back up the local certificate services database and configuration files to the default location C:\CaBackup and include private keys in the backup. The operator is prompted for a password to protect the exported keys.
 
.EXAMPLE
    Backup-CertificateServicesDatabase -IncludePrivateKey -Password (Import-CliXml -Path C:\Secure\backup-password.xml).Password
 
    Running this PowerShell command performs a backup including private keys using a password previously stored with Export-CliXml. Use this pattern for scheduled (non-interactive) backups.
 
.EXAMPLE
    Backup-CertificateServicesDatabase -LocalPath 'C:\Temp\CaBackup' -RemotePath '\\fs1.corp.example.net\pki\backup\'
 
    Running this PowerShell command will back up the local certificate services database and configuration files to C:\Temp\CaBackup and copy a compressed, timestamped archive (.zip file) to a remote file server.
 
.DESCRIPTION
    Use this PowerShell script to perform regular certificate services database and configuration backup. Can be scheduled with a scheduled PowerShell job or scheduled task and performed on a regular schedule. When scheduling backups that include private keys, supply the Password parameter so no interactive prompt is required.
 
    The database backup is written to a staging folder and verified before the previous backup is replaced, so a failed backup never destroys the last known good backup. All critical failures throw terminating errors so scheduled tasks and monitoring can detect them.
 
    This function depends on the private ADCSTools module functions Test-IsElevated and Get-Sid and is not intended to run standalone outside the module.
 
    Ensure the remote path is a share restricted to CA administrators and backup operators. Archives may contain exported private keys (password protected) and should be treated as sensitive material.
 
.INPUTS
    None. This function does not accept pipeline input.
 
.OUTPUTS
    PSCustomObject. Returns an object describing the completed backup (computer name, backup path, archive path, CA certificate thumbprint, and timestamp).
 
.LINK
    https://github.com/richardhicks/adcstools/blob/main/Functions/Backup-CertificateServicesDatabase.ps1
 
.LINK
    https://www.richardhicks.com/
 
.NOTES
    Version: 3.0
    Creation Date: January 20, 2020
    Last Updated: July 26, 2026
    Author: Richard Hicks
    Organization: Richard M. Hicks Consulting, Inc.
    Contact: rich@richardhicks.com
    Website: https://www.richardhicks.com/
 
#>


Function Backup-CertificateServicesDatabase {

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([PSCustomObject])]

    Param (

        [ValidateNotNullOrEmpty()]
        [string]$LocalPath = (Join-Path -Path $env:systemdrive -ChildPath 'CaBackup'),
        [ValidateNotNullOrEmpty()]
        [string]$RemotePath,
        [switch]$IncludePrivateKey,
        [securestring]$Password,
        [ValidateRange(1, 365)]
        [int]$RetentionCount = 14

    )

    # Require administrative privileges
    If (-Not (Test-IsElevated)) {

        Throw 'This command requires an elevated PowerShell session. Run PowerShell as an administrator and try again.'

    }

    # Variables
    $Hostname = $env:computername
    $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
    $BackupDBPath = Join-Path -Path $LocalPath -ChildPath 'Database'
    $StagingPath = Join-Path -Path $LocalPath -ChildPath 'Database_staging'

    # Confirm the overall operation (supports -WhatIf/-Confirm)
    If (-Not $PSCmdlet.ShouldProcess($Hostname, 'Back up certificate services database and configuration')) {

        Return

    }

    # Validate password when including private keys
    If ($IncludePrivateKey) {

        If ($PSBoundParameters.ContainsKey('Password')) {

            # Reject empty passwords supplied via parameter
            If ($Password.Length -eq 0) {

                Throw 'The password supplied with the Password parameter must not be empty.'

            }

        }

        Else {

            # A password prompt is not possible in a non-interactive session
            $NonInteractive = (-Not [Environment]::UserInteractive) -or ([Environment]::GetCommandLineArgs() -match '^-NonI')

            If ($NonInteractive) {

                Throw 'The Password parameter is required when using IncludePrivateKey in a non-interactive session.'

            }

            # Prompt user for password and validate
            Do {

                # Prompt user for password
                $Password = Read-Host 'Enter a password to protect the exported private key(s)' -AsSecureString
                $Password2 = Read-Host 'Confirm password' -AsSecureString

                # Reject empty passwords
                If ($Password.Length -eq 0) {

                    Write-Warning 'The password must not be empty. Please try again.'
                    $Match = $false
                    Continue

                }

                # Compare passwords case-sensitively without retaining plaintext copies
                $Bstr1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
                $Bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password2)

                Try {

                    $Match = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($Bstr1) -ceq [Runtime.InteropServices.Marshal]::PtrToStringBSTR($Bstr2)

                }

                Finally {

                    # Zero and free the unmanaged copies of the password
                    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr1)
                    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr2)

                }

                # Check if passwords match
                If (-Not $Match) {

                    Write-Warning 'Passwords do not match. Please try again.'

                }

            }

            # Repeat until passwords match
            While (-Not $Match)

        }

    }

    # Create the local backup folder and restrict its permissions before any sensitive data is written
    If (-Not (Test-Path $LocalPath)) {

        Write-Verbose "Creating local backup folder $LocalPath..."
        New-Item -Path $LocalPath -ItemType Directory | Out-Null

    }

    Write-Verbose 'Restricting backup folder permissions to SYSTEM and Administrators...'
    $Acl = Get-Acl -Path $LocalPath
    $Acl.SetAccessRuleProtection($true, $false)

    ForEach ($Identity in 'NT AUTHORITY\SYSTEM', 'BUILTIN\Administrators') {

        $Acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($Identity, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))

    }

    Set-Acl -Path $LocalPath -AclObject $Acl

    # Validate the remote path early so a bad target fails before the backup runs
    If ($RemotePath) {

        # Check for existing archive folder. Create if not found.
        Write-Verbose "Checking for existing archive folder `"$RemotePath`"."
        If (-Not (Test-Path $RemotePath)) {

            Try {

                Write-Verbose 'Archive path not found. Creating folder...'
                New-Item -Path $RemotePath -ItemType Directory -ErrorAction Stop | Out-Null

            }

            Catch {

                Throw "Unable to create remote archive folder `"$RemotePath`". $($_.Exception.Message)"

            }

        }

    }

    # Remove artifacts from previous runs so stale data is never archived
    Write-Verbose 'Removing artifacts from previous backups...'
    $StaleArtifacts = @('*.reg', '*.p12', 'csp.txt', 'templates.txt', 'dblocation.txt', 'sid.txt', 'cacert.txt', 'CAPolicy.inf')

    ForEach ($Artifact in $StaleArtifacts) {

        Remove-Item -Path (Join-Path -Path $LocalPath -ChildPath $Artifact) -Force -ErrorAction SilentlyContinue

    }

    If (Test-Path $StagingPath) {

        Remove-Item $StagingPath -Recurse -Force

    }

    # Backup the CA database to a staging folder so the previous backup is preserved if this backup fails
    Write-Verbose "Backing up the CA database on $Hostname..."

    Try {

        Backup-CARoleService -Path $StagingPath -DatabaseOnly -ErrorAction Stop

    }

    Catch {

        Throw "CA database backup failed. $($_.Exception.Message)"

    }

    # Verify the staged backup contains the database before replacing the previous backup
    $StagedDatabase = Join-Path -Path $StagingPath -ChildPath 'DataBase'

    If (-Not (Get-ChildItem -Path $StagedDatabase -Filter '*.edb' -ErrorAction SilentlyContinue)) {

        Throw "CA database backup completed but no database (.edb) file was found in $StagedDatabase."

    }

    # Replace the previous backup with the verified staged backup
    If (Test-Path $BackupDBPath) {

        Write-Verbose 'Deleting previous backup...'
        Remove-Item $BackupDBPath -Recurse -Force

    }

    Move-Item -Path $StagedDatabase -Destination $BackupDBPath
    Remove-Item $StagingPath -Recurse -Force

    # Backup CA certificate(s) and private key(s)
    If ($IncludePrivateKey) {

        Try {

            Backup-CARoleService -Path $LocalPath -Password $Password -KeyOnly -Force -ErrorAction Stop

        }

        Catch {

            Throw "CA private key backup failed. $($_.Exception.Message)"

        }

    }

    # Export CA configuration registry entries
    Write-Verbose 'Exporting CA registry entries...'
    $RegFile = Join-Path -Path $LocalPath -ChildPath "$Hostname.reg"
    reg.exe export 'HKLM\System\CurrentControlSet\Services\CertSvc\Configuration' $RegFile /y | Out-Null

    If ($LASTEXITCODE -ne 0) {

        Throw "CA registry export failed with exit code $LASTEXITCODE."

    }

    # Copy CAPolicy.inf
    If (Test-Path "$env:systemroot\CAPolicy.inf") {

        Write-Verbose 'Backing up CAPolicy.inf...'
        Copy-Item "$env:systemroot\CAPolicy.inf" $LocalPath

    }

    # Record existing CSP algorithm and key length
    Write-Verbose 'Recording existing CSP algorithm and key length...'
    certutil.exe -getreg 'CA\CSP\*' | Out-File -FilePath (Join-Path -Path $LocalPath -ChildPath 'csp.txt') -Encoding utf8

    If ($LASTEXITCODE -ne 0) {

        Write-Warning "certutil.exe -getreg returned exit code $LASTEXITCODE. CSP information may be incomplete."

    }

    # Record published templates
    Write-Verbose 'Recording published templates...'
    certutil.exe -catemplates | Out-File -FilePath (Join-Path -Path $LocalPath -ChildPath 'templates.txt') -Encoding utf8

    If ($LASTEXITCODE -ne 0) {

        Write-Warning "certutil.exe -catemplates returned exit code $LASTEXITCODE. Template information may be incomplete."

    }

    # Record CA database and log file locations
    Write-Verbose 'Recording CA database and log file locations...'
    $CertSvcConfigPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration'
    $DbLocationFile = Join-Path -Path $LocalPath -ChildPath 'dblocation.txt'
    $DbNames = @('DBDirectory', 'DBLogDirectory', 'DBSystemDirectory', 'DBTempDirectory')

    $DbOutput = ForEach ($Name in $DbNames) {

        $Value = (Get-ItemProperty -Path $CertSvcConfigPath -Name $Name -ErrorAction SilentlyContinue).$Name
        "$Name = $Value"

    }

    $DbOutput | Out-File -FilePath $DbLocationFile -Encoding utf8

    # Record machine SID if domain-joined (useful for server migrations)
    If ((Get-CimInstance -ClassName Win32_ComputerSystem).PartOfDomain) {

        Write-Verbose 'Recording machine SID...'

        Try {

            Get-Sid -SidType Machine -ErrorAction Stop | Out-File -FilePath (Join-Path -Path $LocalPath -ChildPath 'sid.txt') -Encoding utf8

        }

        Catch {

            Write-Warning "Unable to record machine SID. $($_.Exception.Message)"

        }

    }

    # Extract CA name
    $Subject = (Get-ItemProperty -Path $CertSvcConfigPath).Active

    # Search local computer certificate store for newest CA signing certificate
    $Escaped = [regex]::Escape($Subject)
    $CaCert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -match "CN=$Escaped(,|$)" -and $_.HasPrivateKey } | Sort-Object -Property NotBefore -Descending | Select-Object -First 1

    # Record current CA signing certificate thumbprint (useful for server migrations)
    If ($null -eq $CaCert) {

        Write-Warning "No certificate matching CA name `"$Subject`" was found in the local machine store. The CA certificate thumbprint was not recorded."

    }

    Else {

        $CaCert.Thumbprint | Out-File -FilePath (Join-Path -Path $LocalPath -ChildPath 'cacert.txt') -Encoding utf8

    }

    # Create archive file in remote location
    $ArchivePath = $null

    If ($RemotePath) {

        # Use a timestamped archive name so a failed run never overwrites the last known good archive
        $ArchivePath = Join-Path -Path $RemotePath -ChildPath "cabackup_${Hostname}_$Timestamp.zip"

        Write-Verbose "Creating archive file $ArchivePath..."

        Try {

            # Use ZipFile directly to avoid the Compress-Archive file size limitation in Windows PowerShell 5.1
            Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop

            If (Test-Path $ArchivePath) {

                Remove-Item $ArchivePath -Force

            }

            [System.IO.Compression.ZipFile]::CreateFromDirectory($LocalPath, $ArchivePath, [System.IO.Compression.CompressionLevel]::Optimal, $true)

        }

        Catch {

            Throw "Unable to create archive file $ArchivePath. $($_.Exception.Message)"

        }

        # Remove archives beyond the retention count
        Write-Verbose "Removing archives beyond the retention count of $RetentionCount..."
        Get-ChildItem -Path $RemotePath -Filter "cabackup_${Hostname}_*.zip" | Sort-Object -Property LastWriteTime -Descending | Select-Object -Skip $RetentionCount | Remove-Item -Force

    }

    # Return a result object describing the completed backup
    [PSCustomObject]@{

        ComputerName       = $Hostname
        BackupPath         = $LocalPath
        ArchivePath        = $ArchivePath
        CaCertThumbprint   = $CaCert.Thumbprint
        IncludedPrivateKey = [bool]$IncludePrivateKey
        Timestamp          = $Timestamp

    }

}

# SIG # Begin signature block
# MIIk6wYJKoZIhvcNAQcCoIIk3DCCJNgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBX049aRFYUm7bo
# eoJsJrK7XCKUKespBs80e0Y98qMvyqCCH6YwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggW0MIIDnKADAgECAhAOxitIKuZQm69NGxw+uiH/MA0GCSqG
# SIb3DQEBDAUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j
# LjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNB
# NDA5NiBTSEEzODQgMjAyMSBDQTEwHhcNMjYwNTE2MDAwMDAwWhcNMjcwODE3MjM1
# OTU5WjCBhjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV
# BAcTDU1pc3Npb24gVmllam8xJDAiBgNVBAoTG1JpY2hhcmQgTS4gSGlja3MgQ29u
# c3VsdGluZzEkMCIGA1UEAxMbUmljaGFyZCBNLiBIaWNrcyBDb25zdWx0aW5nMFkw
# EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOooTPiege6mCA4AriPO+Xh3mymiiZ+3k
# kn31uJifB2ojzzfY7VkAVKhgj+rcVBnofnj2b8OhvAJ4YaQ2Iwuc6aOCAgMwggH/
# MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBQJvGhl
# Ahwi6UKROatrFKBmPLmd5TA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUF
# BwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgeA
# MBMGA1UdJQQMMAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5n
# UlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz
# ODQyMDIxQ0ExLmNybDCBlAYIKwYBBQUHAQEEgYcwgYQwJAYIKwYBBQUHMAGGGGh0
# dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcwAoZQaHR0cDovL2NhY2Vy
# dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0
# MDk2U0hBMzg0MjAyMUNBMS5jcnQwCQYDVR0TBAIwADANBgkqhkiG9w0BAQwFAAOC
# AgEAbaKnnRcJAMHjuWSc2PG/QhJ0jj4hQVwJIbddYDJNxPmD0cxuuorSiR9gX2nl
# ajqNI9N7Kl+FB3oheRTGh/wp4JgZMpCq0qS0zGJ/N6Js+HmVtbkFaPyYxJMXbIWq
# p9zKkoXtSXkpR6nGZnzYkn3EBcRlu4R6hIJHzM/C2PUztH/Hd4fGIryyD69iHvKx
# zotYdlHHY6+X1ACaQnuCz3TLxs3/CDKhPUXesKcISnXHmm4uCwyVdtGyl7wPuZVk
# +rfCIOeWn+XG5J7L8xwhXCPSJ5fKJ5m8/H5cICLR0I7hI4SUiybE1nG5CZ1hKhbW
# abSfNer1dHH/vSYi80YGXCej/88vZeCGQ9/rrjugsg0yN7WCPqNKjEMTYGWkrt37
# lp4cJqULS+alUbL6x1HBdoBStDE2CFmPivL7cCCtnudqCA6b3XB416/FlRo8t4Lw
# Dc2ty+RDKirWM84Zj3ANTVs5fi43rxClBQwngGdqi5TjriKHGTkEKYRIFTViy6Ie
# JDIboOkCFJU5vM7Curvh4rQnw+aM4CyjwnDwnzwcKQVZC3Iy1T4h/FvmpSgu5ouM
# wjdzaR3cSh4OPDRrfBl1YIOoZEOHcshCaHDC46t8+UyAf70BMlrB7Nj84ORTuKTi
# IlU062VzGeREc1KHJqp/S3/NtArpVUVQEgibRxQ99KJCOV8wggawMIIEmKADAgEC
# AhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0Mjkw
# MDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E
# aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2Rl
# IFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDVtC9C0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYw
# n6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43i
# CH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1
# hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd
# 6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObar
# YBLj6Na59zHh3K3kGKDYwSNHR7OhD26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18eb
# MlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYo
# X7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDz
# d5Ea/ttQokbIYViY9XwCFjyDKK05huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8S
# kXbev1jLchApQfDVxW0mdmgRQRNYmtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZ
# YIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxW
# EQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg
# 67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4c
# D08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUF
# BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG
# CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTAT
# MAcGBWeBDAEDMAgGBmeBDAEEATANBgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6P
# vDqZ01bgAhql+Eg08yy25nRm95RysQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V
# 1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+
# 3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcn
# P/2Q0XaG3RywYFzzDaju4ImhvTnhOE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgU
# kpn13c5UbdldAhQfQDN8A+KVssIhdXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6Q
# B7BDf5WIIIJw8MzK7/0pNVwfiThV9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3
# kuZOX956rEnPLqR0kq3bPKSchh/jwVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKL
# QcBIhEuWTatEQOON8BUozu3xGFYHKi8QxAwIZDwzj64ojDzLj4gLDb879M4ee47v
# tevLt/B3E+bnKD+sEq6lLyJsQfmCXBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0
# qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0
# YW6/aOImYIbqyK+p/pQd52MbOoZWeE4wgga0MIIEnKADAgECAhANx6xXBf8hmS5A
# QyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT
# GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAx
# MTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j
# LjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNB
# NDA5NiBTSEEyNTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
# AoICAQC0eDHTCphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcy
# bEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzT
# qpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftB
# dsOECS1UkxBvMgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3
# mmdglTcaarps0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6z
# MUjZa/zbCclF83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS
# 5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBB
# BnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqL
# XvJWnY0v5ydPpOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7ps
# NOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeE
# WvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCC
# AVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv
# 1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/
# BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0
# LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjAL
# BglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvI
# tTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/m
# S83afsl3YTj+IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgX
# f9r5nWMQwr8Myb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liy
# rukZ2iA/wdG2th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+
# Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2
# ykgcGV00TYr2Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipD
# oq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6Ax
# nJll38F0cuJG7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAl
# Z66RzIg9sC+NJpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1
# MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZs
# q8WhbaM2tszWkPZPubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDAN
# BgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
# IEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5n
# IFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkw
# MzIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVz
# cG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBG
# rC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwB
# SOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/
# 4QhguSssp3qome7MrxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3
# K3E0zz09ldQ//nBZZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROU
# INDT98oksouTMYFOnHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3
# w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46Yce
# NA0LfNsnqcnpJeItK/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d
# 2sC26/PQPdP51ho1zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8x
# ymLnjCbSLZ49kPmk8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+
# AliL7ojTdS5PWPsWeupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2b
# Qhpp0IoKRR7YufAkprxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNV
# HRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSME
# GDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0l
# AQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzAB
# hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9j
# YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGlu
# Z1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBp
# bmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIw
# CwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESe
# Y0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FU
# FqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7Y
# MTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0zi
# TN6R3ygQBHMUBaB5bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/
# QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlq
# AcuEVT0cKsb+zJNEsuEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3
# Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roan
# cJIFcbojBcxlRcGG0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/
# ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7
# IRE2lskKPIJgbaP5t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdC
# vHlshtjdNXOCIUjsarfNZzGCBJswggSXAgEBMH0waTELMAkGA1UEBhMCVVMxFzAV
# BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVk
# IEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQDsYrSCrm
# UJuvTRscProh/zANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKAC
# gAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsx
# DjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAffV48hWG9cAZ0Fh2B7xuh
# 3CvsKbDLaBsod/8POP4wADALBgcqhkjOPQIBBQAERjBEAiAQZYBKo/DShxTiC1Ij
# EKi5vyW1vxQQqOmbrsGIzUEz1gIgOmAm1taOaKIEdyjh29wBGss+d/2/aShvTUQA
# HjyV9WKhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYDVQQG
# EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0
# IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex
# AhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkD
# MQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNzI3MDQwMjUyWjAvBgkq
# hkiG9w0BCQQxIgQgeuIapyL2wjf8v15VIgBt1K1Y1rdm43+Jq659/Z1Q7FAwDQYJ
# KoZIhvcNAQEBBQAEggIAhp69TgRMCD+ez95i6ToyIrC7H5jeNDEJ5lBOaxoJ7GPO
# Otwk5jwLmdOPsloIGRiax83F+8Gv7XSMM/hpYNiEkEtovl0atfC5E7jgkGdlWQf3
# E68oQCfpJoiC47MpI4BK/hipuz1ysQ9MqXD38cWYvyaZlrJ0qY183FL4vOrb+Ftr
# 5XUSOoNRzj1uXWg6e+Ssx+ecjGzsao0uT1fZkWNq6VmyaENFHlEm5OySGDj9MxYp
# 5Tyes0ogp50zS95xrGguQRedzKFEmOoukfsna0Amfz7jUf3dBEHJISLIjWG5F86Z
# a07sW1bQhkougyzH5LZ9ARgKLc7Rfab5S1WMNiknXZkoAhopdDqFsof8DzBGLPbl
# YpSNPq1YVNXaGK5d75JhRKqD9EkU5pwIbDFP4UCZ/jalMJEDMNvLsvhyvVb5hbNe
# 1l12D5bpXClDbALV2QFZPs5HOxwILzMOKx8PrcGMgrbYZO9g+O7AsbjsTYefuyxO
# cngsrRlr6k4gCX9gEWJn93N6fczx8GW9hGWDm5m0naFwv0NP91HFWnZyeG5eixok
# ky76aVIllYsqIt0X4Bs1QfimWiqT7AaSQo9efCIcZLZJJBxSCMRwlOfV4DRvTWd5
# jsR7Rb1W76gqJsBV6t/G77mw0/PrTUB/fJbwICjtvJDU1UMpajxYEH6cHdzfYNg=
# SIG # End signature block