Start-PsFCIV.ps1

function Start-PsFCIV {
<#
.ExternalHelp PsFCIV.Help.xml
#>

[CmdletBinding(DefaultParameterSetName = '__xml')]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [IO.DirectoryInfo]$Path,
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = '__xml')]
        [string]$XML,
        [string]$Include = "*",
        [string[]]$Exclude,
        [Parameter(ParameterSetName = '__xml')]
        [ValidateSet("Rename", "Delete")]
        [string]$Action,
        [Parameter(ParameterSetName = '__xml')]
        [ValidateSet("Bad", "Locked", "Missed", "New", "Ok", "Unknown", "All")]
        [String[]]$Show,
        [ValidateSet("MD5", "SHA1", "SHA256", "SHA384", "SHA512")]
        [AllowEmptyCollection()]
        [String[]]$HashAlgorithm = "SHA1",
        [switch]$Recurse,
        [Parameter(ParameterSetName = '__xml')]
        [switch]$Strict,
        [Parameter(ParameterSetName = '__xml')]
        [switch]$Rebuild,
        [Parameter(ParameterSetName = '__online')]
        [switch]$Online
    )

    #region Prepare environment
    # configure preferences
    if ($PSBoundParameters.Verbose) {$VerbosePreference = "continue"; $v = $true}
    if ($PSBoundParameters.Debug) {$DebugPreference = "continue"; $d = $true}
    
    # add DB file to exclusion list
    if (!$Online) {
        $XML = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($XML)
        $Exclude += $XML
    }
    
    # preserving current path
    if (!(Test-Path -LiteralPath (Resolve-Path $Path))) {
        throw "Specified directory path not found."
    }
    
    # creating statistics variable with properties. Each property will contain file names (and paths) with corresponding status.
    $script:stats = New-Object PsFCIV.Support.StatTable
    $script:statcount = New-Object PsFCIV.Support.IntStatTable

    # mode: New, Check, Rebuild, FCIV
    $mode = "Check"
    #endregion
    
    # internal function to calculate resulting statistics and show if if necessary.
    function __showStats {
    # if -Show parameter is presented we display selected groups (Total, New, Ok, Bad, Missed, Unknown)
        if ($show) {
            if ($Show.Contains("All")) {
                $stats | __showGridView "Bad", "Locked", "Missed", "New", "Ok", "Unknown" $statcount.Total
            } else {
                $stats | Select-Object $show | __showGridView $show $statcount.Total
            }
        }
        # script work in numbers
        if ($v -or $d) {
            Write-Host ----------------------------------- -ForegroundColor Green
            if ($Rebuild) {
                Write-Host "Total entries processed :" $statcount.Total -ForegroundColor Cyan
                Write-Host "Total removed unused entries :" $statcount.Deleted -ForegroundColor Yellow
                Write-Host "Total new added files :" $statcount.New -ForegroundColor Green
                Write-Host "Total locked files :" $statcount.Locked -ForegroundColor Yellow
            } else {
                Write-Host "Total files processed :" $statcount.Total -ForegroundColor Cyan
                if (("New", "Rebuild") -contains $mode) {
                    Write-Host "Total new added files :" $statcount.New -ForegroundColor Green
                }
                Write-Host "Total good files :" $statcount.Ok -ForegroundColor Green
                Write-Host "Total bad files :" $statcount.Bad -ForegroundColor Red
                Write-Host "Total unknown status files :" $statcount.Unknown -ForegroundColor Yellow
                Write-Host "Total missing files :" $statcount.Missed -ForegroundColor Yellow
                Write-Host "Total locked files :" $statcount.Locked -ForegroundColor Yellow
            }
            Write-Host ----------------------------------- -ForegroundColor Green
        }
        __finalize
        $statcount
    }
    
    # internal function to update statistic counters.
    function __addStatCounter ($filename, $status) {
        $script:statcount.$status++
        $script:statcount.Total++
        if ($Show -and ($Show.Contains($status) -or $Show.Contains("All"))) {
            $stats.$status.Add($filename)
        }
    }
    if ($Online) {
        Write-Debug "Online mode ON"
        dirx -Path (Join-Path $Path *) -Filter $Include -Exclude $Exclude $Recurse -Force | ForEach-Object {
            Write-Verbose "Perform file '$($_.fullName)' checking."
            $file = Get-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue
            if (__testFileLock $file) {return}
            __newFileEntry $file -hex
        }
        return
    }

    <#
    in this part we perform XML file update by removing entries for non-exist files and
    adding new entries for files that are not in the database.
    #>

    if ($Rebuild) {
        Write-Debug "Rebuild mode ON"
        $mode = "Rebuild"
        if (Test-Path -LiteralPath $xml) {
            $old = __readXml $xml
        } else {
            __finalize $oldpath
            throw "Unable to find XML file. Please, run the command without '-Rebuild' switch."
        }
        $new = New-Object PsFCIV.Support.FcivRootNode
        $interm = New-Object PsFCIV.Support.FcivRootNode
        # use foreach-object instead of where-object to keep original types.
        Write-Verbose "Perform DB file cleanup from non-existent items."
        $old.Entries | ForEach-Object {
            if ((Test-Path -LiteralPath $_.Name)) {
                if ($_.Name -eq $xml) {
                    Write-Debug "File '$($_.Name)' is DB file. Removed."
                    $statcount.Deleted++
                } else {
                    [void]$interm.Entries.Add($_)
                }
            } else {
                Write-Debug "File '$($_.Name)' does not exist. Removed."
                $statcount.Deleted++
            }
        }
        
        $statcount.Total = $old.Entries.Count - $interm.Entries.Count
        dirx -Path (Join-Path $Path *) -Filter $Include -Exclude $Exclude $Recurse -Force | ForEach-Object {
            if ($_.FullName -eq $XML) {
                return
            }
            Write-Verbose "Perform file '$($_.FullName)' checking."
            $file = Get-Item -LiteralPath $_.FullName -Force
            if (__testFileLock $file) {return}
            $filename = $file.FullName -replace [regex]::Escape($($pwd.providerpath + "\"))
            if ($interm.Entries.Contains((New-Object PsFCIV.Support.FcivFileEntry $filename))) {
                Write-Verbose "File '$filename' already exist in XML database. Skipping."
                return
            } else {
                [void]$new.Entries.Add((__newFileEntry $file))
                Write-Verbose "File '$filename' is added."
                __addStatCounter $filename New
            }
        }
        $new.Entries | ForEach-Object {[void]$interm.Entries.Add($_)}
        __writeXml $interm
        __showStats
        return
    }
    
    # this part contains main routine
    $db = __readXml $xml
    # create flat list with indexer access. $db.Entries is of type of ISet<T> and doesn't have indexer
    $entries = $db.Entries | ForEach-Object {$_}
    <#
    check XML file format. If Timestamp property of the first element is null, then the file was generated by
    original FCIV.exe tool. In this case we transform existing XML to a new PsFCIV format by adding new
    properties. Each record is checked against hashes stored in the source XML file. If hash check fails,
    an item is removed from final XML.
    #>

    if ($entries.Count -gt 0 -and $entries[0].Size -eq 0) {
        if ($PSBoundParameters.ContainsKey("HashAlgorithm")) {
            $HashAlgorithm = $HashAlgorithm[0].ToUpper()
        } else {
            $HashAlgorithm = @()
        }
        Write-Debug "FCIV (compatibility) mode ON"
        $mode = "FCIV"
        if ($HashAlgorithm -and $HashAlgorithm -notcontains "sha1" -and $HashAlgorithm -notcontains "md5") {
            throw "Specified hash algorithm (or algorithms) is not supported. For native FCIV source, use MD5 and/or SHA1."
        }
        for ($index = 0; $index -lt $entries.Length; $index++) { # must be FOR loop, because indexer is used below
            $entry = $entries[$index]
            Write-Verbose "Perform file '$($entry.Name)' checking."
            $filename = $entry.Name
            # check if the path is absolute and matches current path. If the path is absolute and does not belong to
            # current path -- skip this entry.
            if ($filename.Contains(":") -and $filename -notmatch [regex]::Escape($pwd.ProviderPath)) {continue}
            # if source file name record contains absolute path, and belongs to the current pathe,
            # just strip base path. New XML format uses relative paths only.
            if ($filename.Contains(":")) {$filename = $filename -replace ([regex]::Escape($($pwd.ProviderPath + "\")))}
            # Test if the file exist. If the file does not exist, skip the current entry and process another record.
            if (!(Test-Path -LiteralPath $filename)) {
                Write-Verbose "File '$filename' not found. Skipping."
                __addStatCounter $filename Missed
                continue
            }
            # get file item and test if it is not locked by another application
            $file = Get-Item -LiteralPath $filename -Force -ErrorAction SilentlyContinue
            if (__testFileLock $file) {continue}
            # create new-style entry record that stores additional data: file length and last modification timestamp.
            $newentry = __newFileEntry $file -NoHash
            $newentry.Name = $filename
            # process current hash entries and copy required hash values to a new entry object.
            "SHA1", "MD5" | ForEach-Object {$newentry.$_ = $entry.$_}
            $entries[$index] = $newentry
            __checkfiles $newentry $file $Action $Strict
        }
        # clear DB entries and insert from backing collection. This step is necessary since ISet<T> doesn't have an
        # indexer and you cannot replace the item in set by index. Only remove->add is supported.
        $db.Entries.Clear()
        $entries | ForEach-Object {[void]$db.Entries.Add($_)}

        # we are done. Overwrite XML, display stats and exit.
        __writeXml $db
        # display statistics and exit right now.
        __showStats
    }
    # if XML file exist, proccess and check all records. XML file will not be modified.
    if ($entries.Count -gt 0) {
        Write-Debug "Native PsFCIV mode ON"
        $mode = "Check"
        # this part is executed only when we want to process certain file. Wildcards are not allowed.
        if ($Include -ne "*") {
            $db.Entries | Where-Object {$_.Name -like $Include} | ForEach-Object {
                Write-Verbose "Perform file '$($_.Name)' checking."
                $entry = $_
                # calculate the hash if the file exist.
                if (Test-Path -LiteralPath $entry.Name) {
                    # and check file integrity
                    $file = Get-Item -LiteralPath $entry.Name -Force -ErrorAction SilentlyContinue
                    __checkfiles $entry $file $Action $Strict
                } else {
                    # if there is no record for the file, skip it and display appropriate message
                    Write-Verbose "File '$filename' not found. Skipping."
                    __addStatCounter $entry.Name Missed
                }
            }
        } else {
            $db.Entries | ForEach-Object {
                <#
                to process files only in the current directory (without subfolders), we remove items
                that contain slashes from the process list and continue regular file checking.
                #>

                if (!$Recurse -and $_.Name -match "\\") {return}
                Write-Verbose "Perform file '$($_.Name)' checking."
                $entry = $_
                if (Test-Path -LiteralPath $entry.Name) {
                    $file = Get-Item -LiteralPath $entry.Name -Force -ErrorAction SilentlyContinue
                    __checkfiles $entry $file $Action $Strict
                } else {
                    Write-Verbose "File '$($entry.Name)' not found. Skipping."
                    __addStatCounter $entry.Name Missed
                }
            }
        }
    } else {
        # if there is no existing XML DB file, start from scratch and create a new one.
        Write-Debug "New XML mode ON"
        $mode = "New"
        dirx -Path (Join-Path $Path *) -Filter $Include -Exclude $Exclude $Recurse -Force | ForEach-Object {
             Write-Verbose "Perform file '$($_.fullName)' checking."
             $file = Get-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue
             if (__testFileLock $file) {return}
             $entry = __newFileEntry $file
             [void]$db.Entries.Add($entry)
             __addStatCounter $entry.Name New
        }
        __writeXml $db
    }
    __showStats
}
# SIG # Begin signature block
# MIIcnwYJKoZIhvcNAQcCoIIckDCCHIwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCQ6+jgqwrOnxe9
# 2KFTlTo6jkwQbQvImsMFZDvUuadSgKCCFn4wggT+MIID5qADAgECAhANQkrgvjqI
# /2BAIc4UAPDdMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwHhcN
# MjEwMTAxMDAwMDAwWhcNMzEwMTA2MDAwMDAwWjBIMQswCQYDVQQGEwJVUzEXMBUG
# A1UEChMORGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFt
# cCAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwuZhhGfFivUN
# CKRFymNrUdc6EUK9CnV1TZS0DFC1JhD+HchvkWsMlucaXEjvROW/m2HNFZFiWrj/
# ZwucY/02aoH6KfjdK3CF3gIY83htvH35x20JPb5qdofpir34hF0edsnkxnZ2OlPR
# 0dNaNo/Go+EvGzq3YdZz7E5tM4p8XUUtS7FQ5kE6N1aG3JMjjfdQJehk5t3Tjy9X
# tYcg6w6OLNUj2vRNeEbjA4MxKUpcDDGKSoyIxfcwWvkUrxVfbENJCf0mI1P2jWPo
# GqtbsR0wwptpgrTb/FZUvB+hh6u+elsKIC9LCcmVp42y+tZji06lchzun3oBc/gZ
# 1v4NSYS9AQIDAQABo4IBuDCCAbQwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQC
# MAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwQQYDVR0gBDowODA2BglghkgBhv1s
# BwEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMB8G
# A1UdIwQYMBaAFPS24SAd/imu0uRhpbKiJbLIFzVuMB0GA1UdDgQWBBQ2RIaOpLqw
# Zr68KC0dRDbd42p6vDBxBgNVHR8EajBoMDKgMKAuhixodHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLXRzLmNybDAyoDCgLoYsaHR0cDovL2NybDQu
# ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkw
# dzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME8GCCsGAQUF
# BzAChkNodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNz
# dXJlZElEVGltZXN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQBIHNy1
# 6ZojvOca5yAOjmdG/UJyUXQKI0ejq5LSJcRwWb4UoOUngaVNFBUZB3nw0QTDhtk7
# vf5EAmZN7WmkD/a4cM9i6PVRSnh5Nnont/PnUp+Tp+1DnnvntN1BIon7h6JGA078
# 9P63ZHdjXyNSaYOC+hpT7ZDMjaEXcw3082U5cEvznNZ6e9oMvD0y0BvL9WH8dQgA
# dryBDvjA4VzPxBFy5xtkSdgimnUVQvUtMjiB2vRgorq0Uvtc4GEkJU+y38kpqHND
# Udq9Y9YfW5v3LhtPEx33Sg1xfpe39D+E68Hjo0mh+s6nv1bPull2YYlffqe0jmd4
# +TaY4cso2luHpoovMIIFMTCCBBmgAwIBAgIQCqEl1tYyG35B5AXaNpfCFTANBgkq
# hkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB
# c3N1cmVkIElEIFJvb3QgQ0EwHhcNMTYwMTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAw
# WjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy
# ZWQgSUQgVGltZXN0YW1waW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAvdAy7kvNj3/dqbqCmcU5VChXtiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI
# 5Je/YyGQmL8TvFfTw+F+CNZqFAA49y4eO+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+
# wKL1oODeIj8O/36V+/OjuiI+GKwR5PCZA207hXwJ0+5dyJoLVOOoCXFr4M8iEA91
# z3FyTgqt30A6XLdR4aF5FMZNJCMwXbzsPGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmE
# UeaC50ZQ/ZQqLKfkdT66mA+Ef58xFNat1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9
# olMqT4UdxB08r8/arBD13ays6Vb/kwIDAQABo4IBzjCCAcowHQYDVR0OBBYEFPS2
# 4SAd/imu0uRhpbKiJbLIFzVuMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3z
# bcgPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDov
# L29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8E
# ejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1
# cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9
# bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
# MAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAQEAcZUS6VGHVmnN793afKpj
# erN4zwY3QITvS4S/ys8DAv3Fp8MOIEIsr3fzKx8MIVoqtwU0HWqumfgnoma/Capg
# 33akOpMP+LLR2HwZYuhegiUexLoceywh4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQ
# GF+JOGFNYkYkh2OMkVIsrymJ5Xgf1gsUpYDXEkdws3XVk4WTfraSZ/tTYYmo9WuW
# wPRYaQ18yAGxuSh1t5ljhSKMYcp5lH5Z/IwP42+1ASa2bKXuh1Eh5Fhgm7oMLStt
# osR+u8QlK0cCCHxJrhO24XxCQijGGFbPQTS2Zl22dHv1VjMiLyI2skuiSpXY9aaO
# UjCCBfUwggPdoAMCAQICEB2iSDBvmyYY0ILgln0z02owDQYJKoZIhvcNAQEMBQAw
# gYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5MRQwEgYDVQQHEwtK
# ZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMS4wLAYD
# VQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE4
# MTEwMjAwMDAwMFoXDTMwMTIzMTIzNTk1OVowfDELMAkGA1UEBhMCR0IxGzAZBgNV
# BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE
# ChMPU2VjdGlnbyBMaW1pdGVkMSQwIgYDVQQDExtTZWN0aWdvIFJTQSBDb2RlIFNp
# Z25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCGIo0yhXoY
# n0nwli9jCB4t3HyfFM/jJrYlZilAhlRGdDFixRDtsocnppnLlTDAVvWkdcapDlBi
# pVGREGrgS2Ku/fD4GKyn/+4uMyD6DBmJqGx7rQDDYaHcaWVtH24nlteXUYam9Cfl
# fGqLlR5bYNV+1xaSnAAvaPeX7Wpyvjg7Y96Pv25MQV0SIAhZ6DnNj9LWzwa0VwW2
# TqE+V2sfmLzEYtYbC43HZhtKn52BxHJAteJf7wtF/6POF6YtVbC3sLxUap28jVZT
# xvC6eVBJLPcDuf4vZTXyIuosB69G2flGHNyMfHEo8/6nxhTdVZFuihEN3wYklX0P
# p6F8OtqGNWHTAgMBAAGjggFkMIIBYDAfBgNVHSMEGDAWgBRTeb9aqitKz1SA4dib
# wJ3ysgNmyzAdBgNVHQ4EFgQUDuE6qFM6MdWKvsG7rWcaA4WtNA4wDgYDVR0PAQH/
# BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwMG
# CCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BBhj9o
# dHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQ2VydGlmaWNhdGlv
# bkF1dGhvcml0eS5jcmwwdgYIKwYBBQUHAQEEajBoMD8GCCsGAQUFBzAChjNodHRw
# Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVNFUlRydXN0UlNBQWRkVHJ1c3RDQS5jcnQw
# JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcN
# AQEMBQADggIBAE1jUO1HNEphpNveaiqMm/EAAB4dYns61zLC9rPgY7P7YQCImhtt
# EAcET7646ol4IusPRuzzRl5ARokS9At3WpwqQTr81vTr5/cVlTPDoYMot94v5JT3
# hTODLUpASL+awk9KsY8k9LOBN9O3ZLCmI2pZaFJCX/8E6+F0ZXkI9amT3mtxQJmW
# unjxucjiwwgWsatjWsgVgG10Xkp1fqW4w2y1z99KeYdcx0BNYzX2MNPPtQoOCwR/
# oEuuu6Ol0IQAkz5TXTSlADVpbL6fICUQDRn7UJBhvjmPeo5N9p8OHv4HURJmgyYZ
# SJXOSsnBf/M6BZv5b9+If8AjntIeQ3pFMcGcTanwWbJZGehqjSkEAnd8S0vNcL46
# slVaeD68u28DECV3FTSK+TbMQ5Lkuk/xYpMoJVcp+1EZx6ElQGqEV8aynbG8HAra
# fGd+fS7pKEwYfsR7MUFxmksp7As9V1DSyt39ngVR5UR43QHesXWYDVQk/fBO4+L4
# g71yuss9Ou7wXheSaG3IYfmm8SoKC6W59J7umDIFhZ7r+YMp08Ysfb06dy6LN0Kg
# aoLtO0qqlBCk4Q34F8W2WnkzGJLjtXX4oemOCiUe5B7xn1qHI/+fpFGe+zmAEc3b
# tcSnqIBv5VPU4OOiwtJbGvoyJi1qV3AcPKRYLqPzW0sH3DJZ84enGm1YMIIGSjCC
# BTKgAwIBAgIQF0FLo4fb8T/ESzcF/lyStzANBgkqhkiG9w0BAQsFADB8MQswCQYD
# VQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdT
# YWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxJDAiBgNVBAMTG1NlY3Rp
# Z28gUlNBIENvZGUgU2lnbmluZyBDQTAeFw0xOTA4MTMwMDAwMDBaFw0yMjA4MTIy
# MzU5NTlaMIGZMQswCQYDVQQGEwJVUzEOMAwGA1UEEQwFOTcyMTkxDzANBgNVBAgM
# Bk9yZWdvbjERMA8GA1UEBwwIUG9ydGxhbmQxHDAaBgNVBAkMEzE3MTAgU1cgTWls
# aXRhcnkgUmQxGzAZBgNVBAoMElBLSSBTb2x1dGlvbnMgSW5jLjEbMBkGA1UEAwwS
# UEtJIFNvbHV0aW9ucyBJbmMuMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
# AgEA0L1qj5TDs7BrGL6/kUXX54oYim9CcoEW7rVWygcXKi0nzKh4Ly2JGOYeCBFu
# ZCbSxMB0BfDbdGPllbqd0xaADbnxxqtrr6hYHTvV7dy2wehq9zs2QOgKQpLa6Hm1
# OapyU0yDrFpTUgin0hYWrTQrWOR5d7EcgUuNMXYADZIS14k7pVjTwSI3qS0A5rU/
# g1sHR9NFSZxrSPdbnnaG9TkiDwbQMm1vggMwye7paWU27F+rI0mJQ5iQMRPWnnZf
# O+EwUwWeFuwf0k9xHggDs+njFzWZGF4P24T4pXHVqy0D8a9a2Sl5nL959tow7Fjh
# W5Nb2R2Bzy0HhU9qZBCmhWYPdQuxo/OK/xw66bQIytO3AoD+Z5qzbQDr27fGDwp6
# 4PDETSvKxPhrryVeMVlWLIdBBDN5mLjACJ+TdREgAG4rLoNB1DgUlGMi8BeHk8PZ
# Zq1jtoknD2dzRuIQHtsSMM1i58ng4v0z2JtWpDGkvPRWb0P5oIPUIkXJIJwg6DtV
# FYI3JOq0PEOVZ3ojsfWzZDCyIRYg4IQy06W976tmL1GAmG2t2gg2CaBI80f6UhN7
# EIys0O+kTNjGCCxMtwSziOrpfOgP9tEb2C9K/93kOLwmzSCNqqnANlsuw+uh1EVf
# QCUBE5qtNVbSb2epUdw/ijQtz+MoInrzTveSE75ow4kpb/0CAwEAAaOCAagwggGk
# MB8GA1UdIwQYMBaAFA7hOqhTOjHVir7Bu61nGgOFrTQOMB0GA1UdDgQWBBR31gKB
# zWLrup1S1jqN5GdPRjOxUDAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAT
# BgNVHSUEDDAKBggrBgEFBQcDAzARBglghkgBhvhCAQEEBAMCBBAwQAYDVR0gBDkw
# NzA1BgwrBgEEAbIxAQIBAwIwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdv
# LmNvbS9DUFMwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5zZWN0aWdvLmNv
# bS9TZWN0aWdvUlNBQ29kZVNpZ25pbmdDQS5jcmwwcwYIKwYBBQUHAQEEZzBlMD4G
# CCsGAQUFBzAChjJodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29SU0FDb2Rl
# U2lnbmluZ0NBLmNydDAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Auc2VjdGlnby5j
# b20wIAYDVR0RBBkwF4EVaW5mb0Bwa2lzb2x1dGlvbnMuY29tMA0GCSqGSIb3DQEB
# CwUAA4IBAQBrghkGUdTVXoPL5q2OvBJi6Av6vK/NHV4Yfn6fNvDECLiHchqSdAEi
# 5bOhqZH6FFSBr39F3iOsFtcZOCSozsC2fNc/s/k1k6bE50U6XVR+A0i/kx8k0/Oy
# +fnhufH2uApYmWmYo8KvXG1+PYRGWEB/oAM59TIJxOdLCUGLUFqLrTCqoM+6PXNw
# NoPZcZ11WapWumXHXM2hfu+HISsDtX2mxFZBgh+VhjQvnyCmiwRRUwozpMlFGOd2
# JtGc7YIj2mVcMHPiPdxOeLd9cYzdS4FUicpJ4L6ZOy8lVhMeMGjBaiGHEwF2oPTE
# VVvKyhEoa1ZInASt0CiaMwKtjarqhhzHMYIFdzCCBXMCAQEwgZAwfDELMAkGA1UE
# BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2Fs
# Zm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSQwIgYDVQQDExtTZWN0aWdv
# IFJTQSBDb2RlIFNpZ25pbmcgQ0ECEBdBS6OH2/E/xEs3Bf5ckrcwDQYJYIZIAWUD
# BAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMx
# DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq
# hkiG9w0BCQQxIgQgkEl32sCS5s6RZ1UEuft+NiD4VoEmEZS1WDHRBNruEb4wDQYJ
# KoZIhvcNAQEBBQAEggIAdaPWF+Pq8UqJUq1r4fgV1x9YCZg1H7TPkZnubSzi+S7+
# 306rF0MDf3tBXUgaX3MJCHycULa5Bh07RgAerpbxUYj7sAwBWKWUrIqojAFOWTsu
# BPYHGjTa6r7PDbIoBm470S/Fk3AhjQy6+qqPXT2EbDO/yafZ48mCDPweQp1xFBBr
# cKvLogH9Cq/P6C8HZ1pwKv5YT9qf946saKdEydjWe2lCnBxQnN2I/3OXzt+RDBSY
# jqLXoZsiktG7WjfkufXCl9ScPh9p/TJ6ZVHhSuOY7sFp4qOg/dSE/iGxWukqwEP4
# gpfR59+2oz4AK0lJ38qr7a8TWenrzZYBblnXyUfNAKY8SWdeGBLxQsJwyuFI1eMw
# vuTcmUH4GCvKJOwqpWOKNyGBV1jEH6Q9nC0C1fZvkclVWSbUKxPUvIviZqjk4gjb
# cd33gOdP29boQ4hIXFuq9UG9rbhBxvj0QVak6/iaT6GtYCMUJPWJizZx/EYdi2Id
# VJjbdxIXXsjSsXDWw7UPLecbMYNsqB2nssRjM8CsRwaDq0GIXfW9FlePmivB93yl
# RwO2phuIrWC0EIssnG4b7LjrbJNWjMT9/wR3nDdOAdOeyPfehiuyITg1Wig4sNuW
# 7D0sJW/2bReytbQlg1YAuxdVLpzy7vhL7SGxojL7BCAMgWIFquaT6VDHbQcB6MSh
# ggIwMIICLAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMx
# FTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv
# bTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGlu
# ZyBDQQIQDUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3
# DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIyMDExMjA5MjkwMlow
# LwYJKoZIhvcNAQkEMSIEIKvC9781DYGs4jl83KrXSIydOi84inYqV6Yw9QD2jkll
# MA0GCSqGSIb3DQEBAQUABIIBABo0KKqwny/ujS7FVqZGCa9g6jBMUAPUeKvY/qAb
# RzaSbOEPfMn5XqAMbEK2gbnu3bcMjbYcaCHtjL5PTnf+JchKYHc2MksjgmG1Bamm
# LBz+I9Gvhc1WCMtin7mBOtAlf1pe+Q1RZniWVf0LxnoVYe79FlKk4F6ZNCaAhkDf
# fZSrsMB7dMn8h9jwl9kTrp52HoNG7PXeh/K5fieoWIZP1u1a8yUCVQ3Z6gDAHu1t
# IJ1i/QWhxsvaZ6ecJgzLy2tfk7gc0k6YobXz0R2EpmIGE45NWUObzsUS0P6IooXk
# kkb1vVieRdqAMZSEdZ/Re9LcSIhaXMgdaAedRZTyjSlxJfU=
# SIG # End signature block