Skip to content

Instantly share code, notes, and snippets.

@kagarlickij
Created June 29, 2018 11:30
Show Gist options
  • Save kagarlickij/65386622ea59d376b96809ed6ffae1ab to your computer and use it in GitHub Desktop.
Save kagarlickij/65386622ea59d376b96809ed6ffae1ab to your computer and use it in GitHub Desktop.
<#
.NOTES
===========================================================================
Created on: Apr 2016
Created by: Dmitriy Kagarlickij
Contact: [email protected]
Description: http://kagarlickij.com/download-restore-ms-sql-database-backup/
===========================================================================
.DESCRIPTION
This script should be executing with Administrator-level permissions
#>
#Requires -RunAsAdministrator
# Set variables
$Hostname = hostname
$errorsCounter = 0
$WorkDir = "C:\myDBtmp"
$LogSource = "DB restore script"
$LogFile = "$WorkDir\logs\mainLog.txt"
$WorkDisk = "DeviceID='C:'"
$WorkDiskFreeSpaceDesired = "10"
$EmailFrom = "[email protected]"
$EmailServer = "smtp.gmail.com"
$EmailServerPort = "587"
$EmailPass = Get-Content C:\myDBtmp\MailboxSecurePass.txt | ConvertTo-SecureString
$EmailCred = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $EmailFrom, $EmailPass
$RemoteHostAddr = "10.211.55.8"
$RemoteHostUser = "sftp-user"
$RemoteHostPswd = Get-Content C:\myDBtmp\SFTPpwd.txt | ConvertTo-SecureString
$RemoteArcheiveName = "myDBarcheive.7z"
$OldBackupFileName = "myDBOld.bak"
$TargetDatabaseName = "myDB"
$MainTable = "dbo.Main_File"
$TargetServerName = "ws2016tp4"
Set-Alias sz "$env:ProgramFiles\7-Zip\7z.exe"
$7zOutDir = "-o$WorkDir"
$7zPswd = "-pPassword"
$PermGroup = "WS2016TP4\dk"
$PermLevel = "db_datareader"
function addLogEntry ($Message, $EntryType) {
# Write to console
Write-Host "[$(Get-Date -UFormat "%b-%d %H:%M:%S %Z")] $Message"
# Write to text log
Add-Content -Value "[$(Get-Date -UFormat "%b-%d %H:%M:%S %Z")] $Message" -Path $LogFile -Encoding ASCII
# Write to eventlog
Write-EventLog -LogName Application -Source $LogSource -EntryType $EntryType -EventId 1 -Message $Message
}
function sendEmail ($Result) {
# Add records to logs
$Message = "sendEmail function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Set EmailTo
if ($Result -eq 'SUCCESS') {
$EmailTo = "[email protected]"
} else {
$EmailTo = "[email protected]", "[email protected]"
}
# Set EmailSubject
$EmailSubject = "$Result : $TargetDatabaseName restore $(Get-Date -UFormat "%b-%d %H:%M:%S %Z")"
# Send email
Send-MailMessage -From $EmailFrom -To $EmailTo -Subject $EmailSubject -Body $(Get-Content -Path $LogFile | Out-String) `
-SmtpServer $EmailServer -Port $EmailServerPort -UseSsl -Credential $EmailCred
}
function endMain {
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalMinutes ))
# Add records to logs
$Message = "DB restore script has been finished with status: ERROR because of last function. Previous functions have been executed in $functionExecutionTime min"
$EntryType = "Error"
addLogEntry $Message $EntryType
# End script execution
$Result = "ERROR"
sendEmail $Result
exit
}
function addLogEntrys {
# Create directory with logs if it's not present
if ((Test-Path -Path $WorkDir\logs) -ne 'True') {
New-Item -Type Directory -Path $WorkDir -Name logs
}
# Remove old mainLog if it's present
if ((Test-Path -Path $LogFile) -eq 'True') {
Remove-Item -Path $LogFile
}
# Create source in the Application log if it's not present
if ([system.diagnostics.eventlog]::SourceExists($LogSource) -ne 'True') {
New-EventLog -LogName Application -Source $LogSource
if ([system.diagnostics.eventlog]::SourceExists($LogSource) -ne 'True') {
$Message = "Source in the Application log has been created with error"
$EntryType = "Error"
} else {
$Message = "Source in the Application log has been created successfully"
$EntryType = "Information"
}
} else {
$Message = "Source in the Application log has been already present"
$EntryType = "Information"
}
# Add records to logs
addLogEntry $Message $EntryType
# Add records to logs (echo script start)
$Message = "DB restore script has been started on server $Hostname. Restoring $TargetDatabaseName database."
$EntryType = "Information"
addLogEntry $Message $EntryType
}
function clean {
# Fix function start time
$functionStartTime = (Get-Date)
# Add records to logs
$Message = "clean function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Delete remote archeive (7zip file)
if ((Test-Path -Path $WorkDir\$RemoteArcheiveName) -eq 'True') {
Remove-Item $WorkDir\$RemoteArcheiveName -Force
}
if(-not $?) {
$errorsCounter++
$Message = "Remote archeive file has been deleted with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Delete backup files (.bak)
Remove-Item $WorkDir\*.bak -Force
if(-not $?) {
$errorsCounter++
$Message = "Backup files have been deleted with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Check function results
if ( $errorsCounter -gt 0 ) {
$Message = "clean function has been finished with status: ERROR in $functionExecutionTime sec"
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
else {
$Message = "clean function has been finished with status: SUCCESS in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
}
function checkDiskSpace {
# Fix function start time
$functionStartTime = (Get-Date)
# Add records to logs
$Message = "checkDiskSpace function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Check free disk space
$WorkDiskFreeSpace = $(Get-WMIObject Win32_LogicalDisk -ComputerName $TargetServerName -Filter $WorkDisk | ForEach-Object {[math]::truncate($_.freespace / 1GB)})
if ($WorkDiskFreeSpace -le $WorkDiskFreeSpaceDesired) {
$errorsCounter++
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Check function results
if ( $errorsCounter -gt 0 ) {
$Message = "checkDiskSpace function has been finished with status: ERROR in $functionExecutionTime sec"
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
else {
$Message = "checkDiskSpace function has been finished with status: SUCCESS in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
}
function importModules {
# Fix function start time
$functionStartTime = (Get-Date)
# Add records to logs
$Message = "importModules function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Import SQLPS Module
Import-Module -Name "sqlps" -DisableNameChecking -WarningAction SilentlyContinue
if(-not $?) {
$errorsCounter++
$Message = "Module sqlps has been imported with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Import Open Source SSH PowerShell Module
Import-Module -Name "Posh-SSH"
if(-not $?) {
$errorsCounter++
$Message = "Module Posh-SSH has been imported with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Check 7-zip
Test-Path -Path "$env:ProgramFiles\7-Zip\7z.exe"
if(-not $?) {
$errorsCounter++
$Message = "7-Zip has been checked with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Check function results
if ( $errorsCounter -gt 0 ) {
$Message = "importModules function has been finished with status: ERROR in $functionExecutionTime sec"
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
else {
$Message = "importModules function has been finished with status: SUCCESS in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
}
function getNewBackup {
# Fix function start time
$functionStartTime = (Get-Date)
# Add records to logs
$Message = "getNewBackup function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Download backup file from SFTP
$SFTPCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $RemoteHostUser, $RemoteHostPswd
$SFTPSession = New-SFTPSession -ComputerName $RemoteHostAddr -Credential $SFTPCredentials -AcceptKey
Get-SFTPFile -SFTPSession $SFTPSession -RemoteFile $RemoteArcheiveName -LocalPath $WorkDir
# Unzip backup file
sz x $WorkDir\$RemoteArcheiveName $7zOutDir $7zPswd
# Determine backup file names
Set-Variable -Name NewBackupFileName -Value $(Get-ChildItem -Path $WorkDir | Where-Object {$_.Name -like "$TargetDatabaseName*"}).Name -Scope Global
# Check downloaded backup file
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases
Invoke-Sqlcmd "RESTORE VERIFYONLY FROM DISK = '$WorkDir\$NewBackupFileName'"
if(-not $?) {
$errorsCounter++
$Message = "New Backup File has been checked with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Check function results
if ( $errorsCounter -gt 0 ) {
$Message = "getNewBackup function has been finished with status: ERROR in $functionExecutionTime sec"
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
else {
$Message = "getNewBackup function has been finished with status: SUCCESS in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
}
function backupOldDatabase {
# Fix function start time
$functionStartTime = (Get-Date)
# Add records to logs
$Message = "backupOldDatabase function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Calculate old records quantity in the table
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases\$TargetDatabaseName
Set-Variable -Name MainFileCountOld -Value $(Invoke-Sqlcmd "SELECT * FROM $MainTable;" | Measure-Object).Count -Scope Global
# Backup old database
Backup-SqlDatabase -ServerInstance $TargetServerName -Database $TargetDatabaseName -BackupFile $WorkDir\$OldBackupFileName -CompressionOption On
# Check old database backup
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases\$TargetDatabaseName
Invoke-Sqlcmd "RESTORE VERIFYONLY FROM DISK = '$WorkDir\$OldBackupFileName'"
if(-not $?) {
$errorsCounter++
$Message = "New Backup File has been checked with error"
$EntryType = "Error"
addLogEntry $Message $EntryType
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Check function results
if ( $errorsCounter -gt 0 ) {
$Message = "backupOldDatabase function has been finished with status: ERROR in $functionExecutionTime sec"
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
else {
$Message = "backupOldDatabase function has been finished with status: SUCCESS in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
}
function restoreDatabase ($BackupFileName) {
# Fix function start time
$functionStartTime = (Get-Date)
# Determine database to restore
if ($BackupFileName -eq $NewBackupFileName) {
Set-Variable -Name db -Value "New" -Scope Global
} elseif ($BackupFileName -eq $OldBackupFileName) {
Set-Variable -Name db -Value "Old" -Scope Global
} else {
$Message = "Request for restore unknown database. Script has been stopped."
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
# Add records to logs
$Message = "restoreDatabase function has been started. Restoring $db database."
$EntryType = "Information"
addLogEntry $Message $EntryType
# Set database offline
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases\$TargetDatabaseName
Invoke-Sqlcmd "ALTER DATABASE $TargetDatabaseName SET OFFLINE WITH ROLLBACK IMMEDIATE"
# Restore backup to TargetServer/TargetDatabase
Restore-SqlDatabase -ServerInstance $TargetServerName -Database $TargetDatabaseName -BackupFile $WorkDir\$BackupFileName -ReplaceDatabase
# Set database online
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases\$TargetDatabaseName
Invoke-Sqlcmd "ALTER DATABASE $TargetDatabaseName SET ONLINE"
# Add permissions to DB
Invoke-Sqlcmd "USE $TargetDatabaseName CREATE USER [$PermGroup5] FOR LOGIN [$PermGroup5] EXEC sp_addrolemember N'$PermLevel', N'$PermGroup'"
# Check restored database
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases\$TargetDatabaseName
Invoke-Sqlcmd "SELECT * FROM $MainTable;" | Measure-Object
if(-not $?) {
$errorsCounter++
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Check function results
if ( $errorsCounter -gt 0 ) {
$Message = "restoreDatabase function ($db database restore) has been finished with status: ERROR in $functionExecutionTime sec"
$EntryType = "Error"
addLogEntry $Message $EntryType
endMain
}
else {
$Message = "restoreDatabase function ($db database restore) has been finished with status: SUCCESS in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
}
function deployNewDatabase {
# Fix function start time
$functionStartTime = (Get-Date)
# Add records to logs
$Message = "deployNewDatabase function has been started"
$EntryType = "Information"
addLogEntry $Message $EntryType
# Restore new database
restoreDatabase $NewBackupFileName
# Calculate new records quantity in the table
Set-Location SQLSERVER:\SQL\$TargetServerName\DEFAULT\Databases\$TargetDatabaseName
Set-Variable -Name MainFileCountNew -Value $(Invoke-Sqlcmd "SELECT * FROM $MainTable;" | Measure-Object).Count -Scope Global
# Compare records quantity in the table
if ($MainFileCountOld -gt $MainFileCountNew) {
$Message = "New Backup File has less records than Old. Old database restore has been initiated."
$EntryType = "Warning"
addLogEntry $Message $EntryType
# Restore old database
restoreDatabase $OldBackupFileName
} else {
$Message = "New Backup File has more records than Old"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalSeconds ))
# Add records to logs
$Message = "deployNewDatabase function has been finished in $functionExecutionTime sec"
$EntryType = "Information"
addLogEntry $Message $EntryType
}
function main {
# Fix function start time
$functionStartTime = (Get-Date)
addLogEntrys
clean
checkDiskSpace
importModules
getNewBackup
backupOldDatabase
deployNewDatabase
clean
# Calculate function execution time
$functionExecutionTime = ([math]::truncate( ((Get-Date) - $functionStartTime).TotalMinutes ))
# Add records to logs
$Message = "DB restore script has been finished with status: SUCCESS. $db version of database $TargetDatabaseName was restored. All functions have been executed in $functionExecutionTime min"
$EntryType = "Information"
addLogEntry $Message $EntryType
# End script execution
$Result = "SUCCESS"
sendEmail $Result
# Clean for local run
Set-Location -Path "C:\"
Remove-Variable -Name * -ErrorAction SilentlyContinue
}
# Execute main function
main
# SIG # Begin signature block
# MIITvgYJKoZIhvcNAQcCoIITrzCCE6sCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUCF2s6OllFgsduIPdP5pIp2L0
# ZEGggg5PMIIEFDCCAvygAwIBAgILBAAAAAABL07hUtcwDQYJKoZIhvcNAQEFBQAw
# VzELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNV
# BAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0
# MTMxMDAwMDBaFw0yODAxMjgxMjAwMDBaMFIxCzAJBgNVBAYTAkJFMRkwFwYDVQQK
# ExBHbG9iYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFRpbWVzdGFt
# cGluZyBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlO9l
# +LVXn6BTDTQG6wkft0cYasvwW+T/J6U00feJGr+esc0SQW5m1IGghYtkWkYvmaCN
# d7HivFzdItdqZ9C76Mp03otPDbBS5ZBb60cO8eefnAuQZT4XljBFcm05oRc2yrmg
# jBtPCBn2gTGtYRakYua0QJ7D/PuV9vu1LpWBmODvxevYAll4d/eq41JrUJEpxfz3
# zZNl0mBhIvIG+zLdFlH6Dv2KMPAXCae78wSuq5DnbN96qfTvxGInX2+ZbTh0qhGL
# 2t/HFEzphbLswn1KJo/nVrqm4M+SU4B09APsaLJgvIQgAIMboe60dAXBKY5i0Eex
# +vBTzBj5Ljv5cH60JQIDAQABo4HlMIHiMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMB
# Af8ECDAGAQH/AgEAMB0GA1UdDgQWBBRG2D7/3OO+/4Pm9IWbsN1q1hSpwTBHBgNV
# HSAEQDA+MDwGBFUdIAAwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFs
# c2lnbi5jb20vcmVwb3NpdG9yeS8wMwYDVR0fBCwwKjAooCagJIYiaHR0cDovL2Ny
# bC5nbG9iYWxzaWduLm5ldC9yb290LmNybDAfBgNVHSMEGDAWgBRge2YaRQ2XyolQ
# L30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEATl5WkB5GtNlJMfO7FzkoG8IW
# 3f1B3AkFBJtvsqKa1pkuQJkAVbXqP6UgdtOGNNQXzFU6x4Lu76i6vNgGnxVQ380W
# e1I6AtcZGv2v8Hhc4EvFGN86JB7arLipWAQCBzDbsBJe/jG+8ARI9PBw+DpeVoPP
# PfsNvPTF7ZedudTbpSeE4zibi6c1hkQgpDttpGoLoYP9KOva7yj2zIhd+wo7AKvg
# IeviLzVsD440RZfroveZMzV+y5qKu0VN5z+fwtmK+mWybsd+Zf/okuEsMaL3sCc2
# SI8mbzvuTXYfecPlf5Y1vC0OzAGwjn//UYCAp5LUs0RGZIyHTxZjBzFLY7Df8zCC
# BJ8wggOHoAMCAQICEhEhBqCB0z/YeuWCTMFrUglOAzANBgkqhkiG9w0BAQUFADBS
# MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEoMCYGA1UE
# AxMfR2xvYmFsU2lnbiBUaW1lc3RhbXBpbmcgQ0EgLSBHMjAeFw0xNTAyMDMwMDAw
# MDBaFw0yNjAzMDMwMDAwMDBaMGAxCzAJBgNVBAYTAlNHMR8wHQYDVQQKExZHTU8g
# R2xvYmFsU2lnbiBQdGUgTHRkMTAwLgYDVQQDEydHbG9iYWxTaWduIFRTQSBmb3Ig
# TVMgQXV0aGVudGljb2RlIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQCwF66i07YEMFYeWA+x7VWk1lTL2PZzOuxdXqsl/Tal+oTDYUDFRrVZUjtC
# oi5fE2IQqVvmc9aSJbF9I+MGs4c6DkPw1wCJU6IRMVIobl1AcjzyCXenSZKX1GyQ
# oHan/bjcs53yB2AsT1iYAGvTFVTg+t3/gCxfGKaY/9Sr7KFFWbIub2Jd4NkZrItX
# nKgmK9kXpRDSRwgacCwzi39ogCq1oV1r3Y0CAikDqnw3u7spTj1Tk7Om+o/SWJMV
# TLktq4CjoyX7r/cIZLB6RA9cENdfYTeqTmvT0lMlnYJz+iz5crCpGTkqUPqp0Dw6
# yuhb7/VfUfT5CtmXNd5qheYjBEKvAgMBAAGjggFfMIIBWzAOBgNVHQ8BAf8EBAMC
# B4AwTAYDVR0gBEUwQzBBBgkrBgEEAaAyAR4wNDAyBggrBgEFBQcCARYmaHR0cHM6
# Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wCQYDVR0TBAIwADAWBgNV
# HSUBAf8EDDAKBggrBgEFBQcDCDBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3Js
# Lmdsb2JhbHNpZ24uY29tL2dzL2dzdGltZXN0YW1waW5nZzIuY3JsMFQGCCsGAQUF
# BwEBBEgwRjBEBggrBgEFBQcwAoY4aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNv
# bS9jYWNlcnQvZ3N0aW1lc3RhbXBpbmdnMi5jcnQwHQYDVR0OBBYEFNSihEo4Whh/
# uk8wUL2d1XqH1gn3MB8GA1UdIwQYMBaAFEbYPv/c477/g+b0hZuw3WrWFKnBMA0G
# CSqGSIb3DQEBBQUAA4IBAQCAMtwHjRygnJ08Kug9IYtZoU1+zETOA75+qrzE5ntz
# u0vxiNqQTnU3KDhjudcrD1SpVs53OZcwc82b2dkFRRyNpLgDXU/ZHC6Y4OmI5uzX
# BX5WKnv3FlujrY+XJRKEG7JcY0oK0u8QVEeChDVpKJwM5B8UFiT6ddx0cm5OyuNq
# Q6/PfTZI0b3pBpEsL6bIcf3PvdidIZj8r9veIoyvp/N3753co3BLRBrweIUe8qWM
# ObXciBw37a0U9QcLJr2+bQJesbiwWGyFOg32/1onDMXeU+dUPFZMyU5MMPbyXPsa
# jMKCvq1ZkfYbTVV7z1sB3P16028jXDJHmwHzwVEURoqbMIIFkDCCBHigAwIBAgIQ
# QXaXpC9uno+9UjU6oprWsjANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJHQjEb
# MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRow
# GAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEjMCEGA1UEAxMaQ09NT0RPIFJTQSBD
# b2RlIFNpZ25pbmcgQ0EwHhcNMTUwNjIyMDAwMDAwWhcNMTYwNjE2MjM1OTU5WjCB
# 1jELMAkGA1UEBhMCQ1kxDTALBgNVBBEMBDIxMTIxCjAIBgNVBAgMAS0xEDAOBgNV
# BAcMB05pY29zaWExEjAQBgNVBAkMCTV0aCBGbG9vcjEsMCoGA1UECQwjVHJlcHBp
# ZGVzIFRvd2VyLCA5IEthZmthc291IFN0cmVldCwxJDAiBgNVBAoMG0xFQURDQVBJ
# VEFMIE1BUktFVFMgTElNSVRFRDEMMAoGA1UECwwDRGV2MSQwIgYDVQQDDBtMRUFE
# Q0FQSVRBTCBNQVJLRVRTIExJTUlURUQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
# ggEKAoIBAQC2/fKSGaIyR5wa0cmCcXJofszXk+auXtEnbnDSgmSrlEluqAehzkuS
# j42uEMBrrKfR9O1AW1vG9nMEsKEpOifnhkEfFPko69k8IGjFV3biklrvuBSb2b+2
# CijlJTRMKIKsazATp5fFjCIpZ6Xvpt6m+kOu+394b+ig8qOAv9T1q7vhKjEO7zmj
# qkXN9U1emat8vYyoGyPqiux69n6lLzPPd8XD/yR/bH1/OuJIKNx58VcsIYVpy0ki
# F3YsoBTo4/xjpgx792hu9QneXroQq8pPbZEzgeT5SLcxMwi0qzQSsYkGguI9LeuZ
# d6qZI/BoLUydA2rMk39jR3V+0tNGjztDAgMBAAGjggGwMIIBrDAfBgNVHSMEGDAW
# gBQpkWD/ik366/mmarjP+eZLvUnOEjAdBgNVHQ4EFgQUKEW1o+pUvBTUoQIm+qpH
# +iZG2zQwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYI
# KwYBBQUHAwMwEQYJYIZIAYb4QgEBBAQDAgQQMEYGA1UdIAQ/MD0wOwYMKwYBBAGy
# MQECAQMCMCswKQYIKwYBBQUHAgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5uZXQv
# Q1BTMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NP
# TU9ET1JTQUNvZGVTaWduaW5nQ0EuY3JsMHQGCCsGAQUFBwEBBGgwZjA+BggrBgEF
# BQcwAoYyaHR0cDovL2NydC5jb21vZG9jYS5jb20vQ09NT0RPUlNBQ29kZVNpZ25p
# bmdDQS5jcnQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTAh
# BgNVHREEGjAYgRZhZG1pbkBvcHRpb25zY2xpY2suY29tMA0GCSqGSIb3DQEBCwUA
# A4IBAQCI6dmaN3fsZlWXq+if67EZvHUYQCq3AomBwYrv+rQhMore24NJwWQLwUyy
# ZMFuX9ezd1XU/iASFG5q1/xGLykklTBSe2r4J2sG2nj2q4YUGa0Tel1wMmTJWz1A
# ubcr8OJ8HRpb2MY0jERm/zf2txFN3gf7H5CAU0YCaklvieDs8DEAJCZy9Ryi4JIR
# td+jKnn9Z59FgfFdE0ESlqKoTrqnf9i2RplrLIBLQwT92KtTYbZGiYmNXk6a4fR5
# HuwHsqXYHAoucP7fPKrYJRVHjwgNKb+i99QKcbnh6iEMnlhqcTnmWGaNlJ0CJD+t
# altnQtzrpskFXmeSYi9YguFxVRakMYIE2TCCBNUCAQEwgZEwfTELMAkGA1UEBhMC
# R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9y
# ZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxIzAhBgNVBAMTGkNPTU9ETyBS
# U0EgQ29kZSBTaWduaW5nIENBAhBBdpekL26ej71SNTqimtayMAkGBSsOAwIaBQCg
# eDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJ
# BDEWBBQH+HwpnpKjOyxYFDlCmxIuO8IsnjANBgkqhkiG9w0BAQEFAASCAQCMOEST
# j5fe380vHtYMN/XJH70M397P/Uujig7+kH82Gz8LkNGdL+OyyGz9z5qhyuQxwF8I
# E/4/1G9i6VhhWCZD+jWS5OEp6Oz0E8RSk/yBUYViDVfTLLhpxyy/zK5kQ8UW7J45
# iMf0t3ZEkVVX0wjNkCtvrplynht+z/1Cd2GDch9qHiAeOxXPopPNnfLT2CrXaO7I
# IQwQuFQHjO51rQeFg6qevTSuhxyOp8MZh7BRKfYFWZC+JfSE3oedd2vrd0O6I4fw
# nOn7Sb1Cwy0bA12sIioViFC5Wpq1hoLquw9kd7TVNGsjqmACb02nBNpvj6fQlJoK
# Vdqm5PG2nHmi3dqPoYICojCCAp4GCSqGSIb3DQEJBjGCAo8wggKLAgEBMGgwUjEL
# MAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExKDAmBgNVBAMT
# H0dsb2JhbFNpZ24gVGltZXN0YW1waW5nIENBIC0gRzICEhEhBqCB0z/YeuWCTMFr
# UglOAzAJBgUrDgMCGgUAoIH9MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ
# KoZIhvcNAQkFMQ8XDTE2MDMzMDA5NDcwMFowIwYJKoZIhvcNAQkEMRYEFC9QU2Ol
# M9s42mqJeRZndWAIicFWMIGdBgsqhkiG9w0BCRACDDGBjTCBijCBhzCBhAQUs2MI
# tNTN7U/PvWa5Vfrjv7EsKeYwbDBWpFQwUjELMAkGA1UEBhMCQkUxGTAXBgNVBAoT
# EEdsb2JhbFNpZ24gbnYtc2ExKDAmBgNVBAMTH0dsb2JhbFNpZ24gVGltZXN0YW1w
# aW5nIENBIC0gRzICEhEhBqCB0z/YeuWCTMFrUglOAzANBgkqhkiG9w0BAQEFAASC
# AQAfFxol1RWq9TkttkrNrFviJ+0ldwih26WiThZ41Nw3GT7XUFVR+TdfsXCXXmdK
# 72XoHnA3s+3gp7ef4nonCj+tkFtZzT5cGJAzNOZaGZigBFV6kHCMQUUz15mEFMAJ
# nnNkY7MKuxrQ49UqiIM8ISLIJKuJSsyBnhF9T91fBV61tN8NDPlJW2oo2rzqLtr6
# 4a4yC55rJdDWMPnoWqYasuF5/nTSzyiUaK4YjMA/63VcqohgG1+4Rr8Q2kUC7ZY8
# bI/xzvL+m64P3QDTIC+TQU1RBh4L3nzjdV4T2cKo+v6i06+q/dQrT/+HpUtGoHze
# GlstW3sZSuANdfvXljJG5iNi
# SIG # End signature block
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment