Privates/FileHandlers.psm1

# FileHandlers.psm1

# supplementary function
function Test-ExcelMacros {
    param (
        [string]$FilePath
    )

    $result = $false # Macros check
    $excel = New-Object -ComObject Excel.Application
    $excel.DisplayAlerts = $false
    $excel.Visible = $false

    try {
        $workbook = $excel.Workbooks.Open($FilePath, $false, $true)

        if ($workbook.HasVBProject) {
            $result = $true
        }
    } catch {
        $result = $_
    } finally {
        if ($workbook) { $workbook.Close($false) }
        $excel.Quit()
        [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null
        [GC]::Collect()
        [GC]::WaitForPendingFinalizers()
    }
    return $result
}
function Check-SysvolAcl {
    param (
        [string]$FilePath,
        [string[]]$TrustGroups,
        [string[]]$PermissionTrust
    )

    $results = @()
    try {
        $acl = Get-Acl -Path $FilePath #-ErrorAction Stop
    } catch {
        #Write-Warning "Impossible de lire les ACL de : $fichier"
        return
    }

    $acl.Access | Where-Object {
    $_.IsInherited -eq $false -and
    $Trustgroups -notcontains $_.IdentityReference.Value -and
    $_.FileSystemRights -notin $PermissionTrust
} | ForEach-Object {
        $msg = "$($_.IdentityReference.Value) has '$($_.FileSystemRights)'"
        $results += [pscustomobject]@{
            FilePath = $FilePath
            Pattern  = 'WrongACL'
            Reason   = $msg
        }
    
    }
    return $results
}


#region FonctionDetect
function Get-OthersContent {
    param (
        [string]$filepath,
        [string[]]$patterns
    )  
    $results = @()    
    try {
        $findmatch = Select-String -Path $filepath -Pattern $patterns -AllMatches
        foreach ($match in $findmatch) {

            $patternMatch = $match.pattern -replace '\\b',''
            [string]$word= $match.line.Replace($patternMatch,'')

            if (-not([string]::IsNullOrEmpty(($word -replace '^[\s=:"]+').Trim()))){
            switch ($patternMatch) {
            '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' { $patternMatch = 'IPv4' }
            '[a-fA-F0-9]{32}' { $patternMatch = 'MD5' }
            '[a-fA-F0-9]{40}' { $patternMatch = 'SHA-1' }
            '[a-fA-F0-9]{64}' { $patternMatch = 'SHA-256' }
            '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}' { $patternMatch = 'UPN' }
            '/user:\S+\\\S+' { $patternMatch = 'Net Credentials' }
            default {
            switch -Wildcard ($patternMatch) {
            'net *' { $patternMatch = 'Commande Net User' }
            default { $patternMatch = $patternMatch }
             }
             }             
             }

            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = $patternMatch
                Reason   = $match.Line
            }
            $results += $result
            }            
     }
    } catch {
        $result = [PSCustomObject]@{
            FilePath = $filepath
            Pattern  = 'error'
            Reason     = $_.Exception.Message
        }
        $results = $result
    }    
    return $results
}
function Get-DefaultGPOContent {
    param (  
        [string]$filepath,
        [string[]]$patterns
    )
    
    $PatternGPO = @(
    'MinimumPasswordAge',
    'MaximumPasswordAge',
    'MinimumPasswordLength',
    'PasswordComplexity',
    'PasswordHistorySize',
    'RequireLogonToChangePassword',
    'ClearTextPassword',
    'RefusePasswordChange',
    'EnablePlainTextPassword',
    'disablepasswordchange',
    'maximumpasswordage',
    'LimitBlankPasswordUse'
)

    $results = @()    
    try {
        $findmatch = Select-String -Path $filepath -Pattern $patterns -AllMatches
        foreach ($match in $findmatch) {

            $patternMatch = $match.pattern -replace '\\b',''
            [string]$word= $match.line.Replace($patternMatch,'')

            $firstToken = (($match.Line -split '\\')[-1] -split '=' | Select-Object -First 1).trim()
            if ($PatternGPO -contains $firstToken) {
            $value = ($match.Line -split '=', 2)[1].Trim()
            if ($value) {
            $valueClean = $value -replace '[^\d-]', ''
            if (-not [int]::TryParse($valueClean, [ref]$null)) {
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = "WrongParameters"
                Reason   = $match.Line
            }
            $results += $result
            } 
            }           
            }            
            elseif (-not([string]::IsNullOrEmpty(($word -replace '^[\s=:"]+').Trim()))){
                        switch ($patternMatch) {
            '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' { $patternMatch = 'IPv4' }
            '[a-fA-F0-9]{32}' { $patternMatch = 'MD5' }
            '[a-fA-F0-9]{40}' { $patternMatch = 'SHA-1' }
            '[a-fA-F0-9]{64}' { $patternMatch = 'SHA-256' }
            '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}' { $patternMatch = 'UPN' }
            default {
            switch -Wildcard ($patternMatch) {
            'net *' { $patternMatch = 'Commande Net User' }
            default { $patternMatch = $patternMatch }
             }
             }             
             }

            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = $patternMatch
                Reason   = $match.Line
            }
            $results += $result
            }          
     }
    } 
    catch {
        $result = [PSCustomObject]@{
            FilePath = $filepath
            Pattern  = 'error'
            Reason     = $_.Exception.Message
        }
        $results = $result
    }    
    return $results
}
function Get-DocxContent {
    param (
        [string]$filepath,
        [string[]]$patterns
    )
    $results = @()
    Import-Module PSWriteOffice
    try {
    $document = Get-OfficeWord -FilePath $filepath -ReadOnly
       $n = 0
    foreach ($pattern in $patterns) {
        $pattern = $pattern -replace '\\b', ''
        $findtext = $document.Find($pattern) | Select-Object text -First 2
        if ($findtext) {
        
        foreach ($text in $findtext.text) 
        {
        if ($n -le '1') {
        $n++
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = $pattern
                Reason   = $text
            }
            $results += $result
            } 
        elseif ($n -gt '1') { 
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = $pattern
                Reason   = "at least " + $n + " characters found"
            }
            $results += $result
         break }
        }
    }
    }
    Close-OfficeWord -Document $document
    } 
    catch {   $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = 'error'
                Reason     = $_
            }
            $results = $result
    }
    $results
    }
function Get-XlsxContent {
    param (
        [string]$filepath,
        [string[]]$patterns
    )
    $results = @()
    Import-Module PSWriteOffice
    try {
    $excel = Get-OfficeExcel -FilePath $filepath 
    $n = 0
    foreach ($pattern in $patterns) {
        $pattern = $pattern -replace '\\b', ''
        [string]$findtext = $excel.Search($pattern, [System.Globalization.CompareOptions]::IgnoreCase, $false) | Select-Object value -First 2
        if ($findtext) {                    
            if ($n -le '2') {
            foreach ($text in $findtext) {
            $n++
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = $pattern
                Reason    = $text
            }
            $results += $result
        }
        } else { 
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = $pattern
                Reason    = "at least " + $n + " characters found"
            }
            $results += $result
         break }
            }
    }
    $excel.Dispose()
    } 
    catch {
    $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = 'error'
                Reason     = $_
            }
            $results = $result
    }
    $results
}
function Get-DocContent {
    param (
        [string]$filepath,
        [string[]]$patterns,
        [string]$wordinstalled
    )
    $results = @()
    if ($wordinstalled -eq $false) {
        $result = [PSCustomObject]@{
            FilePath = $filepath
            Pattern  = 'requires_check'
            Reason     = 'Word is not installed'
        }
        $results += $result
    } else {
        $wordApp = New-Object -ComObject Word.Application
        $wordApp.Visible = $false
        try {
            $document = $wordApp.Documents.Open($filepath, [ref]$null, [ref]$true)
            $n = 0
            foreach ($pattern in $patterns) {
                $pattern = $pattern -replace '\\b', ''
                $find = $document.Content.Find | Select-Object -First 1
                $find.Text = $pattern
                $find.Forward = $true
                $find.Wrap = 1  # wdFindContinue
                $find.Execute() | Out-Null
                if ($find.Found) {
                    if ($n -le '2') {
                        $n++
                        $result = [PSCustomObject]@{
                            FilePath = $filepath
                            Pattern  = $pattern
                            Reason     = "at least " + $n + " characters found"
                        }
                        $results = $result
                    } else { break }
                }
            }
            $document.Close([ref]$false)
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($document) | Out-Null
        } catch {
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = 'error'
                Reason     = $_.Exception.Message
            }
            $results = $result
        } finally {
            $wordApp.Quit()
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wordApp) | Out-Null
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }
    }
    $results
}
function Get-XlsContent {
    param (
        [string]$filePath,
        [string[]]$patterns,
        [string]$excelInstalled
    )
    $results = @()
    
    if ($excelInstalled -eq $false) {
        $result = [PSCustomObject]@{
            FilePath = $filePath
            Pattern  = 'requires_check'
            Reason   = 'Excel is not installed'
        }
    return $result
    } else {
        
        $value = Test-ExcelMacros -FilePath $filePath 

        if ($value -eq $true ) {

        $result = [PSCustomObject]@{
                                FilePath = $filePath
                                Pattern  = "Macros detected"
                                Reason   = "Files contenant macros, need manualy check"
                            }
        return $result
        }

        $excelApp = New-Object -ComObject Excel.Application
        $excelApp.Visible = $false
        try {
            $workbook = $excelApp.Workbooks.Open($filePath, [ref]$null, [ref]$true)
            $n = 0
            foreach ($pattern in $patterns) {
                $pattern = $pattern -replace '\\b', ''
                foreach ($sheet in $workbook.Sheets) {
                    $cells = $sheet.Cells.Find($pattern)
                    if ($null -ne $cells) {
                        if ($n -le '2') {
                            $n++
                            $result = [PSCustomObject]@{
                                FilePath = $filePath
                                Pattern  = $pattern
                                Reason   = "at least " + $n + " characters found"
                            }
                            $results = $result
                        } else { break }
                    }
                }
            }
            $workbook.Close([ref]$false)
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null

        } catch {
            $result = [PSCustomObject]@{
                FilePath = $filePath
                Pattern  = 'error'
                Reason     = $_.Exception.Message
            }
            $results = $result
        } finally {
            $excelApp.Quit()
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelApp) | Out-Null
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }
    }
    $results
}
function Get-PPTContent {
    param (
        [string]$filepath,
        [string[]]$patterns,
        [string]$wordinstalled
    )
    $results = @()
    if ($wordinstalled -eq $false) {
        $result = [PSCustomObject]@{
            FilePath = $filepath
            Pattern  = 'requires_check'
            Reason     = 'Office is not installed'
        }
        $results += $result
    } 
    else {      
        try {
            $MSPPT = New-Object -ComObject powerpoint.application
            $PRES = $MSPPT.Presentations.Open($filepath, $true, $true, $false)

            $n = 0

            foreach($Slide in $PRES.Slides) {
            foreach ($Shape in $Slide.Shapes) {

            if ($Shape.HasTextFrame -eq "-1") {
             $text = $Shape.TextFrame.TextRange.Text            

             foreach ($pattern in $patterns) {
             
             $pattern = $pattern -replace '\\b', ''

             if ($text -match $pattern) {
             
             if ($n -le '2') {        
                 
             $n++
             
             $result = [PSCustomObject]@{
                            FilePath = $filepath
                            Pattern  = $pattern
                            Reason     = "at least " + $n + " characters found"
                        }
                        $results = $result
                        }  else { break }
            }                       

            }
            }
            }
            }

            $MSPPT.PresentationClose
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($MSPPT) | Out-Null
        } catch {
            $result = [PSCustomObject]@{
                FilePath = $filepath
                Pattern  = 'error'
                Reason     = $_.Exception.Message
            }
            $results = $result
        } finally {
            $MSPPT.Quit()
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($MSPPT) | Out-Null
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }
    }
    $results
}
function Get-OdsContent {
    param (
        [string]$filePath,
        [string[]]$patterns
    )
    $results = @()


    # Open ODS file
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $zip = [System.IO.Compression.ZipFile]::OpenRead($filePath)

    # Extract contenant
    $contentXmlEntry = $zip.Entries | Where-Object { $_.FullName -eq "content.xml" }
    $reader = [System.IO.StreamReader]::new($contentXmlEntry.Open())
    $contentXml = $reader.ReadToEnd()
    $reader.Close()
    $zip.Dispose()

    # Analys file XML
    [xml]$xmlContent = $contentXml

    # Read text XML
    $textContent = $xmlContent.'document-content'.InnerText

    $n = 0
    foreach ($pattern in $patterns) {
        $pattern = $pattern -replace '\\b', ''
        if ($textContent -match $pattern) {
            if ($n -le '2') {
                $n++
                $result = [PSCustomObject]@{
                    FilePath = $filePath
                    Pattern  = $pattern
                    Reason     = "at least " + $n + " characters found"
                }
                $results = $result
            }
        }
    }
    $results
}
function Get-Pdfcontentold {
        param (
        [string]$filepath,
        [string[]]$patterns
        )

            $results = @()
            Import-Module PSWritePDF
            try {
            $text = (Convert-PDFToText -FilePath $filepath) -join "`n"
            $n = 0
            foreach ($pattern in $passwordPatterns) {
                if ($text -match $pattern) {
                if ($n -le '2') {
                    $n++
                    $result = [PSCustomObject]@{
                        FilePath = $filepath
                        Pattern  = $pattern
                        Reason     = "at least " + $n + " characters found"
                    }
                    $results = $result
            }   else { break }
                }
            }
            } 
            catch {
            $result = [PSCustomObject]@{
                        FilePath = $filepath
                        Pattern  = 'error'
                        Reason     = $_.Exception.Message
                    }
                    $results = $result
            }
            $results
}
function Get-Pdfcontent {
        param (
        [string]$filepath,
        [string[]]$patterns
        )

            $results = @()
            Import-Module PSWritePDF
            try {
            $text = (Convert-PDFToText -FilePath $filepath) -join "`n"
            $n = 0
            foreach ($pattern in $patterns) {
                $cleanPattern = $pattern -replace '\\b', ''
                if ($text -match $pattern) {
                if ($n -le '2') {
                    $n++
                    $result = [PSCustomObject]@{
                        FilePath = $filepath
                        Pattern  = $cleanPattern
                        Reason     = "at least " + $n + " characters found"
                    }
                    $results += $result
            }   else { break }
                }
            }
            } 
            catch {
            $result = [PSCustomObject]@{
                        FilePath = $filepath
                        Pattern  = 'error'
                        Reason     = $_.Exception.Message
                    }
                    $results = $result
            }
            $results
}
function Get-OthersxmlContent {
    param (
        [string]$filepath,
        [string[]]$patterns,
        [int]$MaxContextLength = 40  # Number of characters to display after the word
    )  

    $results = @()    
    $maxMatchesPerLine = 2  # Limit to 2 occurrences per pattern per line

    try {
        # Read the file line by line
        $lines = Get-Content -Path $filepath

        foreach ($line in $lines) {
            foreach ($pattern in $patterns) {
                # Find all matches for the current pattern (limited to 2)
                $valueMatches = [regex]::Matches($line, $pattern) | Select-Object -First $maxMatchesPerLine | ForEach-Object { $_.Value }

                # Identify the type of detected pattern
                $patternMatch = switch ($pattern) {
                    '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' { 'IPv4' }
                    '\b[a-fA-F0-9]{32}\b' { 'MD5' }
                    '\b[a-fA-F0-9]{40}\b' { 'SHA-1' }
                    '\b[a-fA-F0-9]{64}\b' { 'SHA-256' }
                    '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' { 'UPN' }
                    '\b[Pp]assword\s*[:=]\s*\S+' { 'Password' }
                    default { $pattern }
                }

                # Store each found match
                foreach ($valueMatch in $valueMatches) {
                    if (-not [string]::IsNullOrEmpty($valueMatch)) {
                        
                        # Find the exact position of the word in the line
                        $startIndex = $line.IndexOf($valueMatch)
                        $endIndex = [Math]::Min($startIndex + $MaxContextLength, $line.Length)

                        # Extract the found word + 40 characters after
                        $reason = $line.Substring($startIndex, $endIndex - $startIndex).Trim()

                        # Add the result
                        $results += [PSCustomObject]@{
                            FilePath = $filepath
                            Pattern  = $patternMatch
                            Reason   = $reason  # Found word + 40 characters after
                        }
                    }
                }
            }
        }
    }
    catch {
        $results += [PSCustomObject]@{
            FilePath = $filepath
            Pattern  = 'error'
            Reason   = $_.Exception.Message
        }
    }

    return $results
}
function Get-Xmlcontent {
        param (
        [string]$filepath,
        [string[]]$patterns
        )
            $results = @()
            $xmlContent = Get-Content -Path $filepath -Raw
            if ($xmlContent -match 'cpassword') {
                $xml = [xml]$xmlContent
                # Check for cpassword
                $cpasswords = @()
                if ($xml.Groups.User) {
                    $cpasswords = $xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
                } elseif ($xml.NTServices.NTService) {
                    $cpasswords = $xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
                } elseif ($xml.ScheduledTasks.Task) {
                    $cpasswords = $xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
                } elseif ($xml.DataSources.DataSource) {
                    $cpasswords = $xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
                } elseif ($xml.Printers.SharedPrinter) {
                    $cpasswords = $xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
                } elseif ($xml.Drives.Drive) {
                    $cpasswords = $xml | Select-Xml "/Drives/Drive/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
                }
                if ($cpasswords) {
                $result = [PSCustomObject]@{
                    FilePath  = $filepath
                    Pattern  = 'cpassword'
                    Reason     = $cpasswords
                }
                $results += $result
                }
            } 
            elseif ($xmlContent -match 'DefaultUserName') {
               
                $xml = [xml]$xmlContent
                # Check for AutoLogon
                $userName = ""
                $password = ""
                foreach ($registry in $xml.RegistrySettings.Registry) {
                    if ($registry.Properties.name -eq "DefaultUserName") {
                        $userName = $registry.Properties.value
                    }
                    if ($registry.Properties.name -eq "DefaultPassword") {
                        $password = $registry.Properties.value
                    }
                }
                $result  = [PSCustomObject]@{
                    FilePath = $filepath
                    Pattern  = 'AutoLogon'
                    Reason     = "$userName : $password"
                }
                $results += $result
            } 
            else { $results= Get-OthersxmlContent -filepath $filepath -patterns $patterns }
            $results
}
function Get-Xlsmcontent {
   param ( 
            [string]$filepath
        )             
             $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Macros detected '
                    Reason   = 'Files contenant macros, need check'
                }
             
        return $result
        }
function Get-Executablescontent {
        
        param (
        [string]$filepath
        )

        $results = @()
            if ($filepath -notlike '*.jar') { 
            $signature = Get-AuthenticodeSignature -FilePath $filepath
            if ($signature.Status -ne 'Valid') {
                $result = [PSCustomObject]@{
                    FilePath = $filepath
                    Pattern  = "NotSigned"
                    Reason     = 'File is Not Signed'
                }
                $results = $result
            }
            } else {
        $isSigned = $false
        try {
        $zip = [System.IO.Compression.ZipFile]::OpenRead($filepath)
        foreach ($entry in $zip.Entries) {
            if ($entry.FullName -like "META-INF/*.SF") {
                $isSigned = $true
                break
            }
        }
        $zip.Dispose()
    } catch {
        $result = [PSCustomObject]@{
                        FilePath = $filepath
                        Pattern  = 'error'
                        Reason     = $_.Exception.Message
                    }
                    $results = $result
    }

    if (!$isSigned) {
         $result = [PSCustomObject]@{
                    FilePath = $filepath
                    Pattern  = "NotSigned"
                    Reason     = "Jar file not signed"
                }
                $results = $result
    }
            }
        $results
}
function Get-Zipprotectedbypass {
        param (
        [string]$filepath,
        [string]$zipinstalled
        )
    $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.')
    if ($extension -eq "zip" ) {
    try {
        $zip = [System.IO.Compression.ZipFile]::OpenRead($filepath)

        foreach ($entry in $zip.Entries) {
            $stream = $entry.Open()
            [byte[]]$buffer = New-Object byte[] 10
            $stream.Read($buffer, 0, $buffer.Length) | Out-Null
            $stream.Close()
            break  
        }

        $zip.Dispose()
        return $null
    }
    catch {
         if ($_.Exception.Message -match "(Read|Block|Password|Encrypted)") {
                    
          $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Zip protected'
                    Reason   = 'File protected by password'
                }
                    
            return $result
        } else {
            Write-Host "Erreur inattendue : $_"
            $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Zip check error'
                    Reason   = 'Error to read zip : $_'
                }
           return $result
        }
    }
    } elseif ($zipinstalled) {
    
    $sevenZipPath = $zipinstalled + "7z.exe"
    $output = & "$sevenZipPath" t "$filepath" -pBadPasswordConf 2>&1
    if ($output -match "Wrong password") {
            $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Zip protected'
                    Reason   = 'File protected by password'
                }                    
    return $result
    } 
    }

}
function Get-Requiredcheckcontent {
        param ( 
            [string]$filepath
        )             
             $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'check required'
                    Reason   = 'Binary does not match'
                }
             
        return $result
        }
function Get-CertifContent {
    param (
        [string]$filePath
    )

    $results = @()
    try {
        # Try to load certificat with class .NET X509Certificate2
        $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
        $cert.Import($filePath)        
        if (!$cert.Thumbprint) { $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Certificate Empty'
                    Reason   = 'Certificate without Thumbprint'
                }
      $results = $result }
        if ($cert.PrivateKey) { $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Certificate private key'
                    Reason     = 'certificate with exportable private key'
                }
      $results += $result }
        $cert.Dispose()
    }
    catch {
      $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Protected Certificate'
                    Reason     = ($_.Exception.Message).split(":").split("`n")[1].Trim()
                }
      $results = $result       
    }
    return $results
}
function Get-P7bCertContent {
    param (
        [string]$filePath
    )
    $results = @()
    try {
        # Create certificate collection contenant P7B ou P7C
        $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
        $certCollection.Import($filePath)

        # browse all certificate present in P7B/P7C
    foreach ($cert in $certCollection) {         
       if (!$cert.Thumbprint) { 
                    $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Certificate Empty'
                    Reason     = 'Certificate without Thumbprint'
                }
       $results += $result
       }
       if ($cert.PrivateKey) { 
       $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Certificate private key'
                    Reason     = 'certificate with exportable private key'
                }
       $results += $result
      }
      $cert.Dispose()
      }
      }
    catch {
      $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Protected Certificate'
                    Reason     = $_.Exception.Message
      }
      $results = $result
    }
    return $results
}
function Get-HiddenFilesInImage {
    param (
        [string]$filePath
    )
        
        $fileInfo = Get-Item $filePath
        $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)  

        # Skip file more than 4 Mo
        if ($fileSizeMB -gt 4) {
        $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Large size'
                    Reason   = "File ignored: (size: $fileSizeMB MB)"
                }
      return $result
        }

    try {
        # read binary and convert file to Hexa
        $fileBytes = [System.IO.File]::ReadAllBytes($filePath)
        $fileHex   = [BitConverter]::ToString($fileBytes) -replace '-'

        $magicNumbers = [ordered]@{
            "MSI" = "D0CF11E0A1B11AE1" # MSI or office
            "RAR" = "526172211A0700";  # RAR file
            "ZIP" = "504B0304";   # ZIP file
            "7z"  = "377ABCAF271C"; # 7z file
            "mimikatz" = "6D696D696B61747A"
            "printf"   = "7072696E7466"
            "invoke-" = "696E766F6B652D"
            ".Net.WebClient" = "2E4E65742E576562436C69656E74"
            "EXE" = "4D5A";       # EXE file (MZ header)
        }

        foreach ($key in $magicNumbers.Keys) {
            $magicNumber = $magicNumbers[$key]
            $currentIndex = 0

            if ($fileHex -match $magicNumber) {

            if ($key -eq "EXE") {
                while ($fileHex.IndexOf($magicNumber, $currentIndex) -ne -1) {
                    $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex)
                    # Extract the part after the Magic Number (limited to 400 bytes after)
                    $remainingHex = $fileHex.Substring($startIndex + $magicNumber.Length, [Math]::Min(200 * 2, $fileHex.Length - ($startIndex + $magicNumber.Length)))

                    # Check for the presence of the special string "0000004000" in this range
                    if ($remainingHex -match "0000004000") {
                    $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Suspicious Image'
                    Reason   = "EXE file with '0000004000' string detected"
                }
                return $result
                    }

                    # Continue the search from the next index
                    $currentIndex = $startIndex + $magicNumber.Length
                }
            }
            elseif ($key -eq "ZIP") {

    # Find the index where the ZIP file starts
    $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex) / 2  # Divide by 2 because each hexadecimal represents 2 characters

    # Extract the ZIP bytes starting from this index
    $zipBytes = $fileBytes[$startIndex..($fileBytes.Length - 1)]

    $tempDir = [System.IO.Path]::GetTempPath()
    $fileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($filePath)
    $tempZipPath = [System.IO.Path]::Combine($tempDir, "$fileNameWithoutExtension.zip")
    [System.IO.File]::WriteAllBytes($tempZipPath, $zipBytes)

    # Read the ZIP file and list the files
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    $zip = [System.IO.Compression.ZipFile]::OpenRead($tempZipPath)

    # List the files inside the ZIP
    $fileNames = $zip.Entries | Select-Object -ExpandProperty FullName
    $fileNames | ForEach-Object { Write-Host "File in ZIP: $_" }

    $zip.Dispose()

    # Delete the temporary file
    Remove-Item $tempZipPath

    # Return the result
    $result = [PSCustomObject]@{
        FilePath =  $filePath
        Pattern  = 'Suspicious Image'
        Reason     = "ZIP detected in pictures. Containing: $($fileNames -join ', ')"
    }
    return $result
}
            else {
                $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Suspicious Image'
                    Reason     = "File $key detected in the image"
                }
                return $result
                }
            }
        }
    }
    catch {
        $result = [PSCustomObject]@{
                 FilePath =  $filepath
                 Pattern  = 'Error'
                 Reason     = "Details : $_"
                }
       return $result
    }
}
function Get-HiddenFilesSpecificInImage {
    param (
        [string]$filePath
    )

    $fileInfo = Get-Item $filePath
    $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)  

    # Skip file more than 4 Mo
    if ($fileSizeMB -gt 4) {
        $result = [PSCustomObject]@{
                    FilePath =  $filePath
                    Pattern  = 'Large size'
                    Reason   = "File ignored: (size: $fileSizeMB MB)"
                }
        return $result
    }

    function Extractzip-fromfile {
    # Extract ZIP and list files inside it
                    $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex) / 2  # Divide by 2 for hex chars
                    $zipBytes = $fileBytes[$startIndex..($fileBytes.Length - 1)]

                    $tempDir = [System.IO.Path]::GetTempPath()
                    $fileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($filePath)
                    $tempZipPath = [System.IO.Path]::Combine($tempDir, "$fileNameWithoutExtension.zip")
                    [System.IO.File]::WriteAllBytes($tempZipPath, $zipBytes)

                    # Read the ZIP file and list the files inside it
                    Add-Type -AssemblyName System.IO.Compression.FileSystem
                    $zip = [System.IO.Compression.ZipFile]::OpenRead($tempZipPath)
                    $fileNames = $zip.Entries | Select-Object -ExpandProperty FullName
                    $fileNames | ForEach-Object { Write-Host "File in ZIP: $_" }

                    $zip.Dispose()
                    Remove-Item $tempZipPath

                    $result = [PSCustomObject]@{
                        FilePath =  $filePath
                        Pattern  = 'Suspicious Image'
                        Reason   = "ZIP detected in pictures. Containing: $($fileNames -join ', ')"
                    }
                    return $result
    }

    try {
        # Read binary and convert file to Hex
        $fileBytes = [System.IO.File]::ReadAllBytes($filePath)
        $fileHex = [BitConverter]::ToString($fileBytes) -replace '-'
        $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.').ToLower()

        # Magic numbers for different file types
        $magicNumbers = [ordered]@{
            "MSI" = "D0CF11E0A1B11AE1" 
            "RAR" = "526172211A0700"  
            "ZIP" = "504B0304"
            "7z"  = "377ABCAF271C"
            "EXE" = "4D5A"
        }
       
        switch ($extension) {
        {$_ -in "jpg","jpeg"}  { $endoffile = "FFD9"}
        'png'   { $endoffile = "0000000049454E44AE426082"}
        'gif'   { $endoffile = "3B"}
        }
        foreach ($key in $magicNumbers.Keys) {
            $magicNumber = $magicNumbers[$key]
            $currentIndex = 0

            $truecheck = $endoffile + $magicNumber

            if (($fileHex -match $truecheck) -and ($fileHex.Substring($fileHex.Length -$endoffile.Length, $endoffile.Length) -ne $endoffile)) {

            if ($key -eq 'zip') { 
            return Extractzip-fromfile
            } else {
            $result = [PSCustomObject]@{
                                FilePath =  $filePath
                                Pattern  = 'Suspicious Image'
                                Reason   = "$key file found in image with unexpected binary ending"
                            }
            return $result
            }
            }
            }        
        foreach ($key in $magicNumbers.Keys) {
            $magicNumber = $magicNumbers[$key]
            $currentIndex = 0

            if ($fileHex -match $magicNumber) {
                if ($key -eq "EXE") {
                    while ($fileHex.IndexOf($magicNumber, $currentIndex) -ne -1) {
                        $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex)
                        # Extract the part after the Magic Number (up to 200 bytes)
                        $remainingHex = $fileHex.Substring($startIndex + $magicNumber.Length, [Math]::Min(200 * 2, $fileHex.Length - ($startIndex + $magicNumber.Length)))

                        # Check if the string "0000004000" appears in the remaining data
             
                        if ($remainingHex -match "0000004000") {
                            $result = [PSCustomObject]@{
                                FilePath =  $filePath
                                Pattern  = 'Suspicious Image'
                                Reason   = "EXE file with '0000004000' string detected"
                            }
                            return $result
                        }

                        # Continue searching from the next index
                        $currentIndex = $startIndex + $magicNumber.Length
                    }

                }
                elseif ($key -eq "ZIP") {
                return Extractzip-fromfile
                }
                else {
                    $result = [PSCustomObject]@{
                        FilePath =  $filePath
                        Pattern  = 'Suspicious Image'
                        Reason   = "File $key detected in the image"
                    }
                    return $result
                }
            }
        }
        if ($fileHex.Substring($fileHex.Length -$endoffile.Length, $endoffile.Length) -ne $endoffile) {

       $Keywords = [ordered]@{
            "EXE file"  = "4558452066696C65" 
            "printf"    = "7072696E7466"
            "fopen"     = "666F70656E"
            "malloc"    = "6D616C6C6F63"
            "strcpy"    = "737472637079"
            "system"    = "73797374656D"
            "socket"    = "736F636B6574"
            "class"     = "636C617373"
        }

        # Search other motifs in the last 2000 characters
        $searchHex = $fileHex.Substring([Math]::Max(0, $fileHex.Length - 2000))
        foreach ($word in $Keywords.Keys) {
        if ($searchHex -match $Keywords[$word]) {
        
                  $result = [PSCustomObject]@{
                        FilePath =  $filePath
                        Pattern  = 'Suspicious Image'
                        Reason   = "Image probably modified, found argument : $word"
                    }
        return $result

        }
        }
        $result = [PSCustomObject]@{
                        FilePath =  $filePath
                        Pattern  = 'Suspicious Image'
                        Reason   = "Image probably modified, not correctyl end binary match"
                    }
                    return $result
        }        
        }
    catch {
        $result = [PSCustomObject]@{
                 FilePath =  $filePath
                 Pattern  = 'Error'
                 Reason   = "Details: $_"
                }
        return $result
    }
}
function Get-checkfilesize {
    param (
        [string]$filePath
    )

    $fileInfo = Get-Item $filePath
    $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2) 

    $result = [PSCustomObject]@{
                    FilePath =  $filepath
                    Pattern  = 'Large size'
                    Reason   = "Size is so much, file ignored: (size: $fileSizeMB MB)"
                }             
    return $result 
}
#endregion FonctionDetect

# Add other functions for different file types...
function Get-CompressedFileType {    
    param (
        [string]$filePath,
        [object[]]$detectedType
    )


    # More check fot DOCX, XLSX, ODT, ODS ou JAR PPTX zip
    if ($detectedType -contains "docx" -or $detectedType -contains "jar") {
    try {
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        $zip = [System.IO.Compression.ZipFile]::OpenRead($filePath)

        $fileNames = $zip.Entries | Select-Object -ExpandProperty FullName

        if ($fileNames -contains "word/document.xml") {
            $detectedType = "docx"
        }
        elseif ($fileNames -contains "xl/vbaProject.bin") {
          $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.') 
            if ($extension -in "xlsm","xlam") {
            $detectedType = $extension 
            }
        }
        elseif ($fileNames -contains "ppt/presentation.xml") {
            $detectedType = "pptx"
        }
        elseif ($fileNames -contains "visio/document.xml") {
            $detectedType = "vsdx"
        }
        elseif ($fileNames -contains "xl/workbook.xml") {
            $detectedType = "xlsx"
        } 
        elseif ($fileNames -contains "content.xml") {
            $mimetypeEntry = $zip.Entries | Where-Object { $_.FullName -eq "mimetype" }
            if ($mimetypeEntry -ne $null) {
                $reader = [System.IO.StreamReader]::new($mimetypeEntry.Open())
                $mimetype = $reader.ReadToEnd()
                $reader.Close()

                switch ($mimetype) {
                    "application/vnd.oasis.opendocument.spreadsheet" {
                        $detectedType = "ods"
                    }
                    "application/vnd.oasis.opendocument.text" {
                        $detectedType = "odt"
                    }
                    "application/vnd.oasis.opendocument.presentation" {
                        $detectedType = "odp"
                    }
                    "application/vnd.oasis.opendocument.text-template" {
                        $detectedType = "ott"
                    }
                }
            }
        } 
        elseif ($fileNames -contains "META-INF/MANIFEST.MF") {
            $detectedType = "jar"
        } 
        else   { $detectedType = "others" }
        $zip.Dispose()
     } 
    catch {
        $detectedType = "requires_check"
         }
    }
    elseif ($detectedType -contains "doc") {
        
        $a = [System.IO.File]::ReadAllBytes($filePath)
        $content = [System.Text.Encoding]::ASCII.GetString($a)

        if ($content.Contains("Word.Document")) {
            $detectedType = "doc"
        } elseif ($content.Contains("MSI") -or $content.Contains("Installer")) {
            $detectedType = "msi"
        } elseif ($content.Contains("Excel") ) {
            $detectedType = "xls"
        } elseif ($content.Contains("PowerPoint")) {
            $detectedType = "ppt"
        } elseif ($content.Contains("Microsoft Visio")) {
            $detectedType = "vsd"
        } else {
            #check for db files
            $offsetBytes = [System.IO.File]::ReadAllBytes($filePath)[1024..1050]
            $offsetAscii = [System.Text.Encoding]::ASCII.GetString($offsetBytes).Trim()
            $filteredAscii = ($offsetAscii -split '').Where{ $_ -match '[A-Za-z ]' } -join ''

            if ($filteredAscii -replace '\s+', ' ' -eq "Root Entry") {
            $detectedType = "db"
            } 
            else {
            $detectedType = "others"
            }
        }
     }
    return $detectedType
}
function Get-FileType {
    param (
        [string]$filePath,
        [int]$Maxfilesize,
        [int]$MaxBinarysize,
        [object]$jsonContent
    )

    $detectedType = "others"
    $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.') 
    $fileHeaderHex = [System.IO.File]::ReadAllBytes($filePath)[0..20] | ForEach-Object { "{0:X2}" -f $_ }
    $fileHeaderHex = ($fileHeaderHex -join '').Trim()
    
    if ($fileHeaderHex.Length -eq 0) {
        return "empty"
    }

    $matchFound = $false
    foreach ($entry in $jsonContent.magic_numbers) {
        if ($matchFound) { break }

        foreach ($expectedMagic in $entry.magic) {
            if ($matchFound) { break }

            if ($expectedMagic.Length -le $fileHeaderHex.Length) {
                $difference = Compare-Object -ReferenceObject $expectedMagic -DifferenceObject ($fileHeaderHex.Substring(0, $expectedMagic.Length)) -SyncWindow 0

                if ($difference.Count -eq 0) {
                    if ($entry.offset) {
                    foreach ($offsetItem in $entry.offset) {
                        $offsetPosition = $expectedOffsetValue = $null              
                        $offsetPosition = $offsetItem[0]
                        $expectedOffsetValue = $offsetItem[1]                                                
                        $lastpostition = $offsetPosition + ($expectedOffsetValue.Length/2)-1

                        $CustomfileHeaderHex = [System.IO.File]::ReadAllBytes($filePath)[$offsetPosition..$lastpostition] | ForEach-Object { "{0:X2}" -f $_ }
                        $specificBytes = ($CustomfileHeaderHex -join '').Trim()               

                        if ($specificBytes -eq $expectedOffsetValue) {
                            if ($extension -notin $entry.extensions) {
                                $detectedType = 'requires_check'
                            } else {
                                $detectedType = $extension
                            }
                            $matchFound = $true
                            break
                        }
                       }                       
                    }
                    else {
                        if ($extension -notin $entry.extensions) {
                            $detectedType = 'requires_check'
                        } 
                        else {
                        if ($entry.extensions -contains "docx" -or $entry.extensions -contains "doc") {
                            $detectedType = Get-CompressedFileType -filePath $filePath -detectedType $entry.extensions
                        }
                        if ($detectedType -eq "others" -and $extension -in $entry.extensions) {
                         $detectedType = $extension
                         } elseif ($detectedType -ne $extension -and $detectedType -ne "others") {
                         $detectedType = 'requires_check'
                         }
                    }
                        $matchFound = $true
                        break
                    }
                }
            }
        }
    }

    if ($detectedType -eq 'others') {
    if ($extension -in $jsonContent.magic_numbers.extensions) {
        $detectedType = 'requires_check'
    }
    elseif ($filePath -like '*\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf') {
    $detectedType = "DefaultGPO"
    } 
    else  {    
        $firstLine = Get-Content -Path $filePath -TotalCount 1
        if ($firstLine -match '^<\?xml') {
            $detectedType = "xml"
        }
    }  
    }

    # Check the file size to avoid slowing down analyze files larger than 10 MB, along with installation file extensions over 50 MB
    if ($detectedType -ne 'requires_check' ) {
    $fileInfo = Get-Item $filePath
    $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2)
    switch ($extension)  {
        {$_ -in "exe","msi","dll","msu","cab","zip","rar"} {
        if ($fileSizeMB -gt $MaxBinarysize) { return "bigsize" }
        }
        default {  
        if ($fileSizeMB -gt $Maxfilesize) { return "bigsize" }
        }
        }
    }

    return $detectedType
}

#region Codehtml
#endregion Codehtml
# SIG # Begin signature block
# MIItjAYJKoZIhvcNAQcCoIItfTCCLXkCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDd94ffdy/FQYKj
# 7Qeh8B2PWlsPzjVSw+UxqNyi53NM66CCEtUwggXJMIIEsaADAgECAhAbtY8lKt8j
# AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
# ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
# dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l
# dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG
# A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl
# BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb
# Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC
# 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va
# qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ
# 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet
# /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe
# GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV
# WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl
# s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb
# MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc
# noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl
# jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA
# AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8
# +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud
# DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w
# bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v
# c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np
# dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG
# CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM
# BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1
# BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR
# mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl
# lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs
# NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv
# n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZHMIIEL6ADAgECAhA12OBytW+cTayv
# VHUpRhwLMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB
# c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp
# Z25pbmcgMjAyMSBDQTAeFw0yNTExMTYxMTAwMTlaFw0yNjExMTYxMTAwMThaMG0x
# CzAJBgNVBAYTAkZSMQ8wDQYDVQQHDAZUb3Vsb24xHjAcBgNVBAoMFU9wZW4gU291
# cmNlIERldmVsb3BlcjEtMCsGA1UEAwwkT3BlbiBTb3VyY2UgRGV2ZWxvcGVyLCBE
# QUtIQU1BIE1FSERJMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAp6Ku
# m/VmkWCqAaF/3zHh9f1FuJYY2ozbXOu7mo1/Q8i1c0fE0TXpkZXLY2GZbfpj9BmH
# AAFM0IhOsPR2vdxq3jOUJUb9TICneFor6YaPpySsXR3WSE7X42kgpkkmPELovm1Y
# hwSzhJ4a+E+NWL/MU8h5JpmGVlqPJ02/ZTlMj5kcpIQtq8hoQMcUEDkGFt9IcamE
# 1yN4IHkBA5nm4jJPaos0IuS77t805992JSGWhxBxWARH+2vyltv8Rmq1pZV1lE6n
# JgrWT7Ichjw2X/A+OP68ooTzQwCIpzXb4UuUcwHEfrmP3HGMQJoj//SNC4QPMao+
# 3Z8zbevl73E3d6Kfvra1S+pWM2Ze5YCsIqAd98GUHgi5E6GiG8FQq/+d6msL7l8B
# UASCqXlcAKIjRNMHp8BrUaaW6HS9Kpc+3O3t/LUmK6X3FFiW8QsWoh4K+7YSpopa
# CQbNXmEI4xftctwBOJrEU2oqRnYiwchfjqBNlrGwVGPK1rmM0iTt5KiLTus7AgMB
# AAGjggF4MIIBdDAMBgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6
# Ly9jY3NjYTIwMjEuY3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUF
# BwEBBGcwZTAsBggrBgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1
# bS5jb20wNQYIKwYBBQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9j
# Y3NjYTIwMjEuY2VyMB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0G
# A1UdDgQWBBSXTmfHi9BD9GDRwk5/doNtKHBXYzBLBgNVHSAERDBCMAgGBmeBDAEE
# ATA2BgsqhGgBhvZ3AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0
# dW0ucGwvQ1BTMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDAN
# BgkqhkiG9w0BAQsFAAOCAgEAe+khGqwUUkFYuFRsrvenX2/a+PIt2Tu9d3VoW6Or
# MX3YLpe7S2CgFkXwEi2Siq5KiD1labP9jsh/3G1ZQwwlnPv8dB7ocl/nOrQ9OZex
# GVE1r7IO6VYVa5F7XuJ/KadKLEbQSs1BpBVhESo1ZYr6w9NCLuO9q2Sh3H5MktET
# D6sB+g1TFOYMdwYl8eAawgI2kGPe3dRQSoumP0mHkm3x5SIwRCW+08md5uyzCIui
# 85WmcNPtM1QCqjkSpfdFGYPsnf/BO9NATpZkqFxhXwa9+PqseX+mofCIL49guCXG
# kU4RpeRHcUie14oYkxvBw7VUO4MT6wYbS2C3j2nyoAV4XqqNMfrhZIBJG5haj2RB
# V46bMJ+DsW6hxlm3lIlCaJT2pLbbk79OP+Bk0HIdC9mAbKzcqaZpBpn4+ljrcx7/
# X7OHv4XTCCDWwlZbaogy4Wci6TiSjjfpfXK5N/eJTEEh2w4qoYTTrR61ptkVnTUT
# vGRfPnVtS/3aOm2v4UahtOc/ygcL0A/J85r1e6CEeOaTm9eJbHoNdwNIYaZ81VlX
# /V/MoJgFCtioYOKiTf2Rdq7XrEEHLU2YGwCqJyKYz9tz10yXBcMW6/+gX+PGqAYz
# eKg5jbKLdi9lVrKspQUXAPHdcl6VJMXy799J0lbsQeJNgBVy6HWxOWvdLBGX3hPE
# 3aYwgga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6EprPqOTANBgkqhkiG9w0BAQwF
# ADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVz
# IFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEk
# MCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMB4XDTIxMDUxOTA1
# MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFz
# c2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2ln
# bmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnSPP
# BDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4mSAfgLKTvggv24/rWiVGzGxT
# 9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck6+hI1shsylP4JyLvmxwLHtSw
# orV9wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xqbNTIPsnWtw3E7DmDoUmDQiYi
# /ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KNqfcYf7Z4/iZgkBJ+UFNDcc6z
# okZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSlbfsq7BiqljjaCun+RJgTgFRC
# tsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYqVWQ4x6cM7/G0yswg1ElLlJj6
# NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAExzd4prHwYjUXTeZIlVXqj+ea
# YqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962WZayx7ACFf5ydJpoEowSP07Y
# aBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiYPIAHlezf4muJqxqIns/kqld6
# JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36E/ENVv8urPS0yOnpG4tIoBGx
# VCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPECAwEAAaOCAVUwggFRMA8GA1Ud
# EwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23ufoHTKsW73PMAywHDNMB8GA1Ud
# IwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjATBgNV
# HSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmNl
# cnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEBBGAwXjAoBggrBgEFBQcwAYYc
# aHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAyBggrBgEFBQcwAoYmaHR0cDov
# L3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5jZXIwOQYDVR0gBDIwMDAuBgRV
# HSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkq
# hkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP89DttyZqGYn2XRDhJkL6P+/T0
# IPZyxfxiXumYlARMgwRzLRUStJl490L94C9LGF3vjzzH8Jq3iR74BRlkO18J3zId
# mCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO0eqeLNhLVsgw6/zOfImNlARK
# n1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2mbJCKKtl2s42g9KUJHEIiLni
# 9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA3ahe8WlxVWkxyrQLjH8ItI17
# RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhpn8zJ+6MTyE6YoEBSRVrs0zFF
# IHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSouCReZwSLo8WdrDlPXtR0gicD
# nytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na3PR7nxYVlPu1JPoRZCbH86gc
# 96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1eEJfo2qyrBNPvF7wuAyQfiFX
# LwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+tKyEVAotnyU9vyEVOaIYMk3I
# eBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmvO1nm+/80nLy5r0AZvCQxaQ4x
# ghoNMIIaCQIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0
# YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNpZ25pbmcgMjAy
# MSBDQQIQNdjgcrVvnE2sr1R1KUYcCzANBglghkgBZQMEAgEFAKB8MBAGCisGAQQB
# gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAqMWJLQUAe3hjOOSd/
# k+fWEFejJDxY5rI9dmQoOm368TANBgkqhkiG9w0BAQEFAASCAYBOC2jqi44xnSyj
# 2931W9Rwsg+W8UM0qlxvNZx2Bmbsr/GEWzLsQcDqvi/0qBtY9PftTS1KoiYPhd74
# JnmSwgeGInKUaqmOPEdOidCjY/g0fKqRrswUI/zKc5UVxEvqle6iTH9ahJ8CT9U4
# YkdnCuOvqLim9cJFRd/qP+ULHyRenjOjzOftV9jHuH4wQkdJeyIjhX//qxVmXz/k
# wL/zmrngpEwgKr2PoNW0uZZOXsHyLTck2NwF60Hb2853LvxI2YEk4zqgIL+/JGeG
# Wc3JnSH3G9luyqCOU3nBr34iX2B4SLDV3nXdxEpj73HpKzRNFR/ywOeRZDtHC+d2
# k9/zrDTecab1NNBqMTAlOLaKnbrhzyeWwpMb1VF5E6UIi9s6iLfxjRN6pOx9yOwY
# 2X27WNy0ULLmJwtc8cbAwwEa2RBUA+XWjiFaqRolauU2Cxy9CNdmP5CwhpfAAatI
# E2PLYDAQ3yr6a9zL4IEgc5smiW/v/FYinL0bmNeCRDEx4kFiIuGhghd2MIIXcgYK
# KwYBBAGCNwMDATGCF2IwghdeBgkqhkiG9w0BBwKgghdPMIIXSwIBAzEPMA0GCWCG
# SAFlAwQCAQUAMHcGCyqGSIb3DQEJEAEEoGgEZjBkAgEBBglghkgBhv1sBwEwMTAN
# BglghkgBZQMEAgEFAAQgcZYAUQOPtpqppbNp1s5oHXJtbXvKP8aVhsHJ5z79rSkC
# EDvjp7f5N1kBJ0EAsVP1xpUYDzIwMjYwOTI5MjEwNDA2WqCCEzowggbtMIIE1aAD
# AgECAhAIT9wzT35FTtvDD4/5khg1MA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYT
# AlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQg
# VHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEw
# HhcNMjYwODA1MDAwMDAwWhcNMzcxMTA0MjM1OTU5WjBjMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFNIQTI1
# NiBSU0E0MDk2IFRpbWVzdGFtcCBSZXNwb25kZXIgMjAyNiAxMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAtnum8sn+zUr41JtMZbP9OMYw+HwJDpG5xkIu
# /lqcfNYmMX81YmsUiHLbh9ykpeWBGKTLhYBrAN9Tdg/QEzG32XcObmgIblnr0CoQ
# 3WSAeDZ6nH6X6VkFyYkJw3QBJREwvm4UhLzSxmwPA7cFKRTEOMsmEEj6qJk/dqLE
# AL+oQYuOwE2UuiX1Vnul8YReIyWd4kgLn9gq6LNXM0UplkR6jL/QHxmb6fMoGBJY
# bnaUI7XD6cKDpekK2SVMld4iDbzeHDtOaaxldH5IxuNusQ69nd8/ZXEiB5Hbxj3R
# lK13cX1W4DlFXKdv/CEhM8Cj1vvlmvhNroyPdRGbbpBlgyf8Wdu5N6ByhFwURn0U
# 6ozlPoxN22v+fviUhP+6DR547OZnpBMWDfei1f5sVGwiiW/KQTWOK97g+4RJpPzP
# NV4VYMAwO2jM2Aty2QYPVmOQTJm0msuXnJrSbl2gf9JylpkJlWXqk1Q4LJsxz+TE
# LoQCZIljbgvTJgoPU2R12ydv8i1UqL/adelA0y7U9Pmmtbze9Xx3rtajC5SzQd1j
# gfwAwsa90v9YcSPdmeoyoBBA/27cCL237l5DTYYPDLQ4ON3OLTGWnvRb6jDrf/T7
# 5gMRfUzSLCBQfBusm9+mSWRlC/Df6S/e9Q8i13CuhzOT2Jx+V/nlbXM4QoBwlUAh
# elwwJT0CAwEAAaOCAZUwggGRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBTJY4ow
# LtRK+26U8+bjQH717M3iMB8GA1UdIwQYMBaAFO9vU0rp5AZ8esrikFb2L9RJ7MtO
# MA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCBlQYIKwYB
# BQUHAQEEgYgwgYUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
# bTBdBggrBgEFBQcwAoZRaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0VHJ1c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIwMjVDQTEuY3J0
# MF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy
# bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL
# BQADggIBAI3FOmEenVIK35msCYB+fShAsWvSYvLBItoNdAgQ2jIqrGsVsluXMJU/
# +mRebBc52s6lbKAvOVPXaizmKkMLLflEEKDZQx4CkS2t8aHPjkXha3hYZ010htFa
# 3dhNgmalH5vuWvh3tTCf4frTS7gPtGc4Z/xaPhQ2AB1mR8eEe/WbH0RWHvVIl6Vw
# Q3+g5FKNfN2N/DWJkf13w2H+2GfqEfbd35Ww8CvoYBjLNIDTadcPWdgsjsiOaK/7
# EsKJgLjUNIVgvcaFOLLQ/GlrA+0ZHJoFUbOr5SJN8zykPspXIXlpDJY/gqFUZRRO
# eab9GVgmhbdOJcD/63RhxPahFUGbckRONqMe6DYAv6/mOG0pWd3cPStsdcS7buj5
# DyniwRY8yooMH6ptx5vpP/pZzBPBeZD2U4IsthyxB5Jaa8qrOkB5z160TXiM5ADM
# spZ0TfD9MJoq0tFpFPssKRFhWeEDYPvcUuN7U7lvcdHl4ezQ3NT/7Ffs1sR1yh/L
# RbdZ3B3Vc6q2WmD8mDC0p9kzl2o73iVtS946IkEj7FkRsZGww1teYxERROC745xr
# tjvcw9ZyyUjHZWGRIpJeMNsPquCDf0fkyHtB+J4AiNZqCQk23rxh+KbpyMTNVKIt
# J5l92Svl20U9NbqMBOVYl1h54NEYLJq1/xHWFKPNK903zJZA9P2DMIIGtDCCBJyg
# AwIBAgIQDcesVwX/IZkuQEMiDDpJhjANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG
# EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
# cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjUw
# NTA3MDAwMDAwWhcNMzgwMTE0MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# VGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAtHgx0wqYQXK+PEbAHKx126NGaHS0URedTa2N
# DZS1mZaDLFTtQ2oRjzUXMmxCqvkbsDpz4aH+qbxeLho8I6jY3xL1IusLopuW2qft
# JYJaDNs1+JH7Z+QdSKWM06qchUP+AbdJgMQB3h2DZ0Mal5kYp77jYMVQXSZH++0t
# rj6Ao+xh/AS7sQRuQL37QXbDhAktVJMQbzIBHYJBYgzWIjk8eDrYhXDEpKk7RdoX
# 0M980EpLtlrNyHw0Xm+nt5pnYJU3Gmq6bNMI1I7Gb5IBZK4ivbVCiZv7PNBYqHEp
# NVWC2ZQ8BbfnFRQVESYOszFI2Wv82wnJRfN20VRS3hpLgIR4hjzL0hpoYGk81coW
# J+KdPvMvaB0WkE/2qHxJ0ucS638ZxqU14lDnki7CcoKCz6eum5A19WZQHkqUJfdk
# DjHkccpL6uoG8pbF0LJAQQZxst7VvwDDjAmSFTUms+wV/FbWBqi7fTJnjq3hj0Xb
# Qcd8hjj/q8d6ylgxCZSKi17yVp2NL+cnT6Toy+rN+nM8M7LnLqCrO2JP3oW//1sf
# uZDKiDEb1AQ8es9Xr/u6bDTnYCTKIsDq1BtmXUqEG1NqzJKS4kOmxkYp2WyODi7v
# QTCBZtVFJfVZ3j7OgWmnhFr4yUozZtqgPrHRVHhGNKlYzyjlroPxul+bgIspzOwb
# tmsgY1MCAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYE
# FO9vU0rp5AZ8esrikFb2L9RJ7MtOMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/n
# upiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB3Bggr
# BgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv
# bTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2Ny
# bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0g
# BBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQAX
# zvsWgBz+Bz0RdnEwvb4LyLU0pn/N0IfFiBowf0/Dm1wGc/Do7oVMY2mhXZXjDNJQ
# a8j00DNqhCT3t+s8G0iP5kvN2n7Jd2E4/iEIUBO41P5F448rSYJ59Ib61eoalhnd
# 6ywFLerycvZTAz40y8S4F3/a+Z1jEMK/DMm/axFSgoR8n6c3nuZB9BfBwAQYK9FH
# aoq2e26MHvVY9gCDA/JYsq7pGdogP8HRtrYfctSLANEBfHU16r3J05qX3kId+ZOc
# zgj5kjatVB+NdADVZKON/gnZruMvNYY2o1f4MXRJDMdTSlOLh0HCn2cQLwQCqjFb
# qrXuvTPSegOOzr4EWj7PtspIHBldNE2K9i697cvaiIo2p61Ed2p8xMJb82Yosn0z
# 4y25xUbI7GIN/TpVfHIqQ6Ku/qjTY6hc3hsXMrS+U0yy+GWqAXam4ToWd2UQ1KYT
# 70kZjE4YtL8Pbzg0c1ugMZyZZd/BdHLiRu7hAWE6bTEm4XYRkA6Tl4KSFLFk43es
# aUeqGkH/wyW4N7OigizwJWeukcyIPbAvjSabnf7+Pu0VrFgoiovRDiyx3zEdmcif
# /sYQsfch28bZeUz2rtY/9TCA6TD8dC3JE3rYkrhLULy7Dc90G6e8BlqmyIjlgp2+
# VqsS9/wQD7yFylIz0scmbKvFoW2jNrbM1pD2T7m3XDCCBY0wggR1oAMCAQICEA6b
# GI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEk
# MCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAw
# MDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMY
# RGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
# MIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2u
# exuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKv
# aJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/gh
# YZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOt
# yU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCR
# cKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8
# oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m
# 1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y
# 1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkl
# iWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/E
# IFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOC
# ATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/n
# upiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB
# /wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
# LmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqg
# OKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS
# b290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEA
# cKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU
# 9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0Sb
# QyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1Rmpp
# VLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxY
# oA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0Vys
# GMNNn3O3AamfV6peKOK5lDGCA3wwggN4AgEBMH0waTELMAkGA1UEBhMCVVMxFzAV
# BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVk
# IEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMQIQCE/cM09+
# RU7bww+P+ZIYNTANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0BCQMxDQYLKoZI
# hvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDkyOTIxMDQwNlowKwYLKoZIhvcN
# AQkQAgwxHDAaMBgwFgQUUdmr2gNJc9hPQmaspIJI5rNpxDkwLwYJKoZIhvcNAQkE
# MSIEILIOfpSqDKjTtCZpz8nVTgsQfQE2PJNBZRnlQZNXZZ5MMDcGCyqGSIb3DQEJ
# EAIvMSgwJjAkMCIEIC2gnaf0Ex+f5y22xebpyWVnVa8EPx6nQswNISDhQev8MA0G
# CSqGSIb3DQEBAQUABIICADCghAyZeePUevICoy4AGB0PDTSQGdu1GhP5tsm9pxd+
# /6HMfJnHa2z8I0lejYXI51evM+Jui+lzn9aIejnNN0Ft29Xu7FmjJ6e3/jZuhp85
# zNSkFhor5x4oruAV3tbOvAuPPimLQnSmbJnlgTnoLRYEwe/ePljORNkmR0ceBNx/
# 2CXJgXnTaNtj9cZDqZK+rvde5wx3BgQHj13zdDdmFINfBYaRK5WIflHWERESmezI
# M85UNwlUTQVJU2F7xEACax1DfGEG60mSMxSjtPxtHHCIH4QLc0b1eWwDW2b4wK+f
# R1cHVmmQv2eiUxm8sQSeGnwb+P+tyDspoxXLRmjBhvJD9+D89eQBtqHls4fICx6F
# Q7Qyrl45wZ7fYeCagWfgOz615A4AaVGpu2HCtVw/6hbt9R7ASfIYE7rt/T03DDTN
# mwMC6zTaHVE8AcN0pCFgt53SAUprktcoaIgiNRtdhoA1k5oSBQEe2xaIei3YhPjd
# zx7SDnehbItD9iHInk+eTfl1tpprEnPlSDl/mJANaISWRd5gV2PUIJqr9x15Gxi9
# U99cZqx1YECaDPdxRv/TciVcqeViseyoASD11tPg1gZyvINTd1FPEWgN61+7We17
# UMXCQSFUWTOJwAsoNehWpIV9rbj7OAk5mxEantCTqYbVioXeiZ0EouND17alN5/o
# SIG # End signature block