public/New-SystemInventoryReport.ps1

function New-SystemInventoryReport {
    <#
    .SYNOPSIS
    Generates comprehensive system inventory reports in Markdown and HTML formats
 
    .DESCRIPTION
    This function reads the SystemInventory.json file and generates professional system inventory
    reports in both Markdown and HTML formats. The reports include dynamic tables with proper
    formatting, color coding, and sortable/filterable content. Reports are timestamped and
    include comprehensive system information, Windows updates, installed software, capabilities,
    optional features, and Store apps.
 
    .OUTPUTS
    [System.Void]
    This function does not return objects but generates report files on disk
 
    .PARAMETER InventoryFilePath
    The path to the SystemInventory.json file containing the collected system data.
    Default: "C:\ProgramData\SystemInventory\SystemInventory.json"
 
    .PARAMETER ReportBaseFileName
    The base filename for the generated reports (without extension).
    Default: "SystemInventoryReport"
 
    .PARAMETER OutputPath
    The directory where the Markdown and HTML report files will be saved.
    Default: "C:\ProgramData\SystemInventory"
 
    .PARAMETER HTMLOnly
    Generate only HTML report, skipping Markdown generation.
 
    .PARAMETER MarkdownOnly
    Generate only Markdown report, skipping HTML generation.
 
    .EXAMPLE
    PS C:\> New-SystemInventoryReport
    Generates both Markdown and HTML reports using default paths and filenames
 
    .EXAMPLE
    PS C:\> New-SystemInventoryReport -InventoryFilePath "C:\Custom\SystemInventory.json" -OutputPath "C:\Reports"
    Generates reports from a custom inventory file and saves them to a specified output directory
 
    .EXAMPLE
    PS C:\> New-SystemInventoryReport -HTMLOnly -ReportBaseFileName "ServerInventory"
    Generates only an HTML report with a custom base filename
 
    .EXAMPLE
    PS C:\> New-SystemInventoryReport -MarkdownOnly
    Generates only a Markdown report, skipping HTML generation
 
    .NOTES
    Function : New-SystemInventoryReport
    Author : John Billekens
    CoAuthor : GitHub Copilot
    Copyright : Copyright (c) John Billekens Consultancy
    Version : 2025.1110.1415
    #>

    [CmdletBinding()]
    [OutputType([System.Void])]
    param(
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$InventoryFilePath = "C:\ProgramData\SystemInventory\SystemInventory.json",

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$ReportBaseFileName = "SystemInventoryReport",

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$OutputPath = "C:\ProgramData\SystemInventory",

        [Parameter(Mandatory = $false)]
        [switch]$HTMLOnly,

        [Parameter(Mandatory = $false)]
        [switch]$MarkdownOnly
    )

    begin {
        Write-Verbose "Starting $($MyInvocation.MyCommand)"
        $Script:LogFile = Join-Path -Path (Split-Path $InventoryFilePath -Parent) -ChildPath "$(([System.IO.FileInfo]$InventoryFilePath).BaseName).log"

        # Set report generation flags based on parameters
        $MarkDownReport = $true
        $HTMLReport = $true
        if ($HTMLOnly.IsPresent) {
            $MarkDownReport = $false
        }
        if ($MarkdownOnly.IsPresent) {
            $HTMLReport = $false
        }
    }

    process {
        try {
            Write-Log "Starting report generation..."
        Write-Log "JSON Path: $InventoryFilePath"
        Write-Log "Output Path: $OutputPath"

        # Verify JSON file exists
        if (-not (Test-Path $InventoryFilePath)) {
            Write-Log "ERROR: SystemInventory.json not found at: $InventoryFilePath"
            throw "SystemInventory.json not found at: $InventoryFilePath"
        }

        # Load JSON data
        Write-Log "Loading SystemInventory.json..."
        $inventory = Get-Content $InventoryFilePath -Raw -Encoding UTF8 | ConvertFrom-Json
        $formattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

        $computerName = if ($inventory.ComputerName) { $inventory.ComputerName } else { "Unknown" }

        Write-Log "Report Date: $formattedDate"
        Write-Log "Computer Name: $computerName"

        if ($MarkDownReport) {
            $mdFileName = "$($ReportBaseFileName).md"
            $mdPath = Join-Path $OutputPath $mdFileName
            # ===== GENERATE MARKDOWN REPORT =====
            Write-Log "Generating Markdown report..."

            $markdown = @"
# System Inventory - $formattedDate
**Golden Master: $computerName**
 
---
 
"@


            # 1 System Information Section (keep as-is, custom formatting)
            if ($inventory.SystemInfo) {
                $markdown += "## System Information`n`n"
                $markdown += Format-SystemInfoMarkdown -SystemInfo $inventory.SystemInfo
                $markdown += "---`n`n"
            }

            # 2+ Dynamic sections based on AvailableItems and metadata
            $sortedItems = Get-SortedReportItems -InventoryData $inventory

            foreach ($itemName in $sortedItems) {
                Write-Log "Processing Markdown section: $itemName"

                # Get the data for this item
                $itemData = @($inventory.$itemName)

                # Generate dynamic table
                $section = New-DynamicMarkdownTable -ItemName $itemName -ItemData $itemData -InventoryData $inventory

                if (-not [string]::IsNullOrWhiteSpace($section)) {
                    $markdown += $section
                }
            }

            # Save Markdown file with proper UTF-8 encoding
            if ($PSVersionTable.PSVersion.Major -ge 6) {
                $markdown | Set-Content -Path $mdPath -Encoding utf8NoBOM
            } else {
                # PowerShell 5.1 - Use StreamWriter for more reliable UTF-8 without BOM
                try {
                    $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
                    $streamWriter = [System.IO.StreamWriter]::new($mdPath, $false, $utf8NoBom)
                    $streamWriter.Write($markdown)
                    $streamWriter.Close()
                } catch {
                    # Fallback to File.WriteAllText if StreamWriter fails
                    [System.IO.File]::WriteAllText($mdPath, $markdown, [System.Text.UTF8Encoding]::new($false))
                } finally {
                    if ($streamWriter) { $streamWriter.Dispose() }
                }
            }
            Write-Log "Markdown report saved: $mdPath"
        }
        if ($HTMLReport) {
            $htmlFileName = "$($ReportBaseFileName).html"
            $htmlPath = Join-Path $OutputPath $htmlFileName
            # ===== GENERATE HTML REPORT =====
            Write-Log "Generating HTML report..."

            $htmlContent = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>System Inventory - $computerName</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
 
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            background: #f5f5f5;
            padding: 20px;
        }
 
        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: white;
            padding: 40px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            border-radius: 8px;
        }
 
        header {
            border-bottom: 3px solid #0066cc;
            padding-bottom: 20px;
            margin-bottom: 30px;
        }
 
        h1 {
            color: #0066cc;
            font-size: 2.2em;
            margin-bottom: 10px;
        }
 
        .subtitle {
            color: #666;
            font-size: 1.2em;
            font-weight: 500;
        }
 
        h2 {
            color: #0066cc;
            font-size: 1.8em;
            margin-top: 40px;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #e0e0e0;
        }
 
        h3 {
            color: #333;
            font-size: 1.3em;
            margin-top: 25px;
            margin-bottom: 15px;
        }
 
        .section {
            margin-bottom: 30px;
        }
 
        .search-container {
            margin: 20px 0;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 5px;
        }
 
        .search-box {
            width: 100%;
            padding: 10px 15px;
            font-size: 14px;
            border: 1px solid #ddd;
            border-radius: 4px;
            outline: none;
        }
 
        .search-box:focus {
            border-color: #0066cc;
            box-shadow: 0 0 0 3px rgba(0,102,204,0.1);
        }
 
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 20px 0;
            background: white;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }
 
        .info-table {
            max-width: 800px;
        }
 
        .info-table th {
            background: #f8f9fa;
            text-align: left;
            width: 30%;
            font-weight: 600;
        }
 
        .data-table thead {
            background: #0066cc;
            color: white;
        }
 
        th, td {
            padding: 12px 15px;
            border: 1px solid #e0e0e0;
            text-align: left;
        }
 
        .data-table th {
            cursor: pointer;
            user-select: none;
            position: relative;
            padding-right: 30px;
        }
 
        .data-table th:hover {
            background: #0052a3;
        }
 
        .data-table th::after {
            content: '\25B2\25BC';
            position: absolute;
            right: 10px;
            opacity: 0.5;
            font-size: 0.7em;
            letter-spacing: -0.3em;
        }
 
        .data-table th.sort-asc::after {
            content: '\25B2';
            opacity: 1;
        }
 
        .data-table th.sort-desc::after {
            content: '\25BC';
            opacity: 1;
        }
 
        tbody tr:nth-child(even) {
            background: #f8f9fa;
        }
 
        tbody tr:hover {
            background: #e3f2fd;
        }
 
        .result-succeeded {
            background-color: #d4edda !important;
            color: #155724;
            font-weight: 500;
        }
 
        .result-failed {
            background-color: #fff3cd !important;
            color: #856404;
            font-weight: 500;
        }
 
        .stats {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 5px;
            margin: 15px 0;
            font-weight: 500;
        }
 
        @media print {
            body {
                background: white;
                padding: 0;
            }
 
            .container {
                box-shadow: none;
                padding: 20px;
            }
 
            .search-container {
                display: none;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>System Inventory - $formattedDate</h1>
            <div class="subtitle">Golden Master: $computerName</div>
        </header>
 
"@


            # System Information Section (keep as-is, custom formatting)
            if ($inventory.SystemInfo) {
                $htmlContent += "<h2>System Information</h2>`n"
                $htmlContent += Format-SystemInfoHtml -SystemInfo $inventory.SystemInfo
            }

            # Dynamic sections based on AvailableItems and metadata
            $sortedItems = Get-SortedReportItems -InventoryData $inventory

            foreach ($itemName in $sortedItems) {
                Write-Log "Processing HTML section: $itemName"

                # Get the data for this item
                $itemData = @($inventory.$itemName)

                # Generate dynamic table
                $section = New-DynamicHtmlTable -ItemName $itemName -ItemData $itemData -InventoryData $inventory

                if (-not [string]::IsNullOrWhiteSpace($section)) {
                    $htmlContent += $section
                }
            }

            # JavaScript for sorting and filtering
            $htmlContent += @"
    </div>
 
    <script>
        function sortTable(tableId, columnIndex) {
            const table = document.getElementById(tableId);
            const tbody = table.querySelector('tbody');
            const rows = Array.from(tbody.querySelectorAll('tr'));
            const th = table.querySelectorAll('th')[columnIndex];
 
            // Determine sort direction
            const isAsc = th.classList.contains('sort-asc');
 
            // Remove all sort classes
            table.querySelectorAll('th').forEach(header => {
                header.classList.remove('sort-asc', 'sort-desc');
            });
 
            // Add appropriate sort class
            if (isAsc) {
                th.classList.add('sort-desc');
            } else {
                th.classList.add('sort-asc');
            }
 
            // Sort rows
            rows.sort((a, b) => {
                const aValue = a.cells[columnIndex].textContent.trim();
                const bValue = b.cells[columnIndex].textContent.trim();
 
                // Try to parse as number or date
                const aNum = parseFloat(aValue);
                const bNum = parseFloat(bValue);
 
                if (!isNaN(aNum) && !isNaN(bNum)) {
                    return isAsc ? bNum - aNum : aNum - bNum;
                }
 
                // String comparison
                return isAsc ? bValue.localeCompare(aValue) : aValue.localeCompare(bValue);
            });
 
            // Rebuild tbody
            rows.forEach(row => tbody.appendChild(row));
        }
 
        function filterTable(tableId, searchId) {
            const searchTerm = document.getElementById(searchId).value.toLowerCase();
            const table = document.getElementById(tableId);
            const rows = table.querySelectorAll('tbody tr');
 
            rows.forEach(row => {
                const text = row.textContent.toLowerCase();
                row.style.display = text.includes(searchTerm) ? '' : 'none';
            });
        }
    </script>
</body>
</html>
"@


            # Save HTML file with proper UTF-8 encoding
            if ($PSVersionTable.PSVersion.Major -ge 6) {
                $htmlContent | Set-Content -Path $htmlPath -Encoding utf8NoBOM
            } else {
                # PowerShell 5.1 - Use StreamWriter for more reliable UTF-8 without BOM
                try {
                    $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
                    $streamWriter = [System.IO.StreamWriter]::new($htmlPath, $false, $utf8NoBom)
                    $streamWriter.Write($htmlContent)
                    $streamWriter.Close()
                } catch {
                    # Fallback to File.WriteAllText if StreamWriter fails
                    [System.IO.File]::WriteAllText($htmlPath, $htmlContent, [System.Text.UTF8Encoding]::new($false))
                } finally {
                    if ($streamWriter) { $streamWriter.Dispose() }
                }
            }
            Write-Log "HTML report saved: $htmlPath"
        }

        Write-Log "Report generation completed successfully"
        Write-Log "Generated files:"
        if ($MarkDownReport) {
            Write-Log " - Markdown: $mdPath"
        }
        if ($HTMLReport) {
            Write-Log " - HTML: $htmlPath"
        }

        } catch [System.Management.Automation.ItemNotFoundException] {
            Write-Error "File not found: $($_.Exception.Message)"
            throw
        } catch {
            Write-Error "An error occurred: $($_.Exception.Message)"
            Write-Log "Error during report generation: $($_.Exception.Message)" -Level "ERROR"
            Write-Log "Important Error details:"
            Write-Log "$($_ | Get-ExceptionDetails -AsText)"
            throw
        } finally {
            $Script:LogFile = $null
        }
    }

    end {
        Write-Verbose "Completed $($MyInvocation.MyCommand)"
    }
}

# SIG # Begin signature block
# MIImdwYJKoZIhvcNAQcCoIImaDCCJmQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB4ZM+RpOQ1VGbE
# W7LrHHK6RWScwtaQTxIeQh7eTsyuKKCCIAowggYUMIID/KADAgECAhB6I67aU2mW
# D5HIPlz0x+M/MA0GCSqGSIb3DQEBDAUAMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQK
# Ew9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIFRpbWUg
# U3RhbXBpbmcgUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5
# WjBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSwwKgYD
# VQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIzNjCCAaIwDQYJ
# KoZIhvcNAQEBBQADggGPADCCAYoCggGBAM2Y2ENBq26CK+z2M34mNOSJjNPvIhKA
# VD7vJq+MDoGD46IiM+b83+3ecLvBhStSVjeYXIjfa3ajoW3cS3ElcJzkyZlBnwDE
# JuHlzpbN4kMH2qRBVrjrGJgSlzzUqcGQBaCxpectRGhhnOSwcjPMI3G0hedv2eNm
# GiUbD12OeORN0ADzdpsQ4dDi6M4YhoGE9cbY11XxM2AVZn0GiOUC9+XE0wI7CQKf
# OUfigLDn7i/WeyxZ43XLj5GVo7LDBExSLnh+va8WxTlA+uBvq1KO8RSHUQLgzb1g
# bL9Ihgzxmkdp2ZWNuLc+XyEmJNbD2OIIq/fWlwBp6KNL19zpHsODLIsgZ+WZ1AzC
# s1HEK6VWrxmnKyJJg2Lv23DlEdZlQSGdF+z+Gyn9/CRezKe7WNyxRf4e4bwUtrYE
# 2F5Q+05yDD68clwnweckKtxRaF0VzN/w76kOLIaFVhf5sMM/caEZLtOYqYadtn03
# 4ykSFaZuIBU9uCSrKRKTPJhWvXk4CllgrwIDAQABo4IBXDCCAVgwHwYDVR0jBBgw
# FoAU9ndq3T/9ARP/FqFsggIv0Ao9FCUwHQYDVR0OBBYEFF9Y7UwxeqJhQo1SgLqz
# YZcZojKbMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0gADBMBgNVHR8ERTBDMEGg
# P6A9hjtodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3Rh
# bXBpbmdSb290UjQ2LmNybDB8BggrBgEFBQcBAQRwMG4wRwYIKwYBBQUHMAKGO2h0
# dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ1Jv
# b3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAN
# BgkqhkiG9w0BAQwFAAOCAgEAEtd7IK0ONVgMnoEdJVj9TC1ndK/HYiYh9lVUacah
# RoZ2W2hfiEOyQExnHk1jkvpIJzAMxmEc6ZvIyHI5UkPCbXKspioYMdbOnBWQUn73
# 3qMooBfIghpR/klUqNxx6/fDXqY0hSU1OSkkSivt51UlmJElUICZYBodzD3M/SFj
# eCP59anwxs6hwj1mfvzG+b1coYGnqsSz2wSKr+nDO+Db8qNcTbJZRAiSazr7KyUJ
# Go1c+MScGfG5QHV+bps8BX5Oyv9Ct36Y4Il6ajTqV2ifikkVtB3RNBUgwu/mSiSU
# ice/Jp/q8BMk/gN8+0rNIE+QqU63JoVMCMPY2752LmESsRVVoypJVt8/N3qQ1c6F
# ibbcRabo3azZkcIdWGVSAdoLgAIxEKBeNh9AQO1gQrnh1TA8ldXuJzPSuALOz1Uj
# b0PCyNVkWk7hkhVHfcvBfI8NtgWQupiaAeNHe0pWSGH2opXZYKYG4Lbukg7HpNi/
# KqJhue2Keak6qH9A8CeEOB7Eob0Zf+fU+CCQaL0cJqlmnx9HCDxF+3BLbUufrV64
# EbTI40zqegPZdA+sXCmbcZy6okx/SjwsusWRItFA3DE8MORZeFb6BmzBtqKJ7l93
# 9bbKBy2jvxcJI98Va95Q5JnlKor3m0E7xpMeYRriWklUPsetMSf2NvUQa/E5vVye
# fQIwggZFMIIELaADAgECAhAIMk+dt9qRb2Pk8qM8Xl1RMA0GCSqGSIb3DQEBCwUA
# MFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMu
# QS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNpZ25pbmcgMjAyMSBDQTAeFw0yNDA0
# MDQxNDA0MjRaFw0yNzA0MDQxNDA0MjNaMGsxCzAJBgNVBAYTAk5MMRIwEAYDVQQH
# DAlTY2hpam5kZWwxIzAhBgNVBAoMGkpvaG4gQmlsbGVrZW5zIENvbnN1bHRhbmN5
# MSMwIQYDVQQDDBpKb2huIEJpbGxla2VucyBDb25zdWx0YW5jeTCCAaIwDQYJKoZI
# hvcNAQEBBQADggGPADCCAYoCggGBAMslntDbSQwHZXwFhmibivbnd0Qfn6sqe/6f
# os3pKzKxEsR907RkDMet2x6RRg3eJkiIr3TFPwqBooyXXgK3zxxpyhGOcuIqyM9J
# 28DVf4kUyZHsjGO/8HFjrr3K1hABNUszP0o7H3o6J31eqV1UmCXYhQlNoW9FOmRC
# 1amlquBmh7w4EKYEytqdmdOBavAD5Xq4vLPxNP6kyA+B2YTtk/xM27TghtbwFGKn
# u9Vwnm7dFcpLxans4ONt2OxDQOMA5NwgcUv/YTpjhq9qoz6ivG55NRJGNvUXsM3w
# 2o7dR6Xh4MuEGrTSrOWGg2A5EcLH1XqQtkF5cZnAPM8W/9HUp8ggornWnFVQ9/6M
# ga+ermy5wy5XrmQpN+x3u6tit7xlHk1Hc+4XY4a4ie3BPXG2PhJhmZAn4ebNSBwN
# Hh8z7WTT9X9OFERepGSytZVeEP7hgyptSLcuhpwWeR4QdBb7dV++4p3PsAUQVHFp
# wkSbrRTv4EiJ0Lcz9P1HPGFoHiFAQQIDAQABo4IBeDCCAXQwDAYDVR0TAQH/BAIw
# ADA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY2NzY2EyMDIxLmNybC5jZXJ0dW0u
# cGwvY2NzY2EyMDIxLmNybDBzBggrBgEFBQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0
# dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0dW0uY29tMDUGCCsGAQUFBzAChilodHRw
# Oi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNlcjAfBgNVHSMEGDAW
# gBTddF1MANt7n6B0yrFu9zzAMsBwzTAdBgNVHQ4EFgQUO6KtBpOBgmrlANVAnyiQ
# C6W6lJwwSwYDVR0gBEQwQjAIBgZngQwBBAEwNgYLKoRoAYb2dwIFAQQwJzAlBggr
# BgEFBQcCARYZaHR0cHM6Ly93d3cuY2VydHVtLnBsL0NQUzATBgNVHSUEDDAKBggr
# BgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAEQsN8wg
# PMdWVkwHPPTN+jKpdns5AKVFjcn00psf2NGVVgWWNQBIQc9lEuTBWb54IK6Ga3hx
# QRZfnPNo5HGl73YLmFgdFQrFzZ1lnaMdIcyh8LTWv6+XNWfoyCM9wCp4zMIDPOs8
# LKSMQqA/wRgqiACWnOS4a6fyd5GUIAm4CuaptpFYr90l4Dn/wAdXOdY32UhgzmSu
# xpUbhD8gVJUaBNVmQaRqeU8y49MxiVrUKJXde1BCrtR9awXbqembc7Nqvmi60tYK
# lD27hlpKtj6eGPjkht0hHEsgzU0Fxw7ZJghYG2wXfpF2ziN893ak9Mi/1dmCNmor
# GOnybKYfT6ff6YTCDDNkod4egcMZdOSv+/Qv+HAeIgEvrxE9QsGlzTwbRtbm6gwY
# YcVBs/SsVUdBn/TSB35MMxRhHE5iC3aUTkDbceo/XP3uFhVL4g2JZHpFfCSu2TQr
# rzRn2sn07jfMvzeHArCOJgBW1gPqR3WrJ4hUxL06Rbg1gs9tU5HGGz9KNQMfQFQ7
# 0Wz7UIhezGcFcRfkIfSkMmQYYpsc7rfzj+z0ThfDVzzJr2dMOFsMlfj1T6l22GBq
# 9XQx0A4lcc5Fl9pRxbOuHHWFqIBD/BCEhwniOCySzqENd2N+oz8znKooSISStnkN
# aYXt6xblJF2dx9Dn89FK7d1IquNxOwt0tI5dMIIGYjCCBMqgAwIBAgIRAKQpO24e
# 3denNAiHrXpOtyQwDQYJKoZIhvcNAQEMBQAwVTELMAkGA1UEBhMCR0IxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGlt
# ZSBTdGFtcGluZyBDQSBSMzYwHhcNMjUwMzI3MDAwMDAwWhcNMzYwMzIxMjM1OTU5
# WjByMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOV2VzdCBZb3Jrc2hpcmUxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEwMC4GA1UEAxMnU2VjdGlnbyBQdWJsaWMgVGlt
# ZSBTdGFtcGluZyBTaWduZXIgUjM2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEA04SV9G6kU3jyPRBLeBIHPNyUgVNnYayfsGOyYEXrn3+SkDYTLs1crcw/
# ol2swE1TzB2aR/5JIjKNf75QBha2Ddj+4NEPKDxHEd4dEn7RTWMcTIfm492TW22I
# 8LfH+A7Ehz0/safc6BbsNBzjHTt7FngNfhfJoYOrkugSaT8F0IzUh6VUwoHdYDpi
# ln9dh0n0m545d5A5tJD92iFAIbKHQWGbCQNYplqpAFasHBn77OqW37P9BhOASdmj
# p3IijYiFdcA0WQIe60vzvrk0HG+iVcwVZjz+t5OcXGTcxqOAzk1frDNZ1aw8nFhG
# EvG0ktJQknnJZE3D40GofV7O8WzgaAnZmoUn4PCpvH36vD4XaAF2CjiPsJWiY/j2
# xLsJuqx3JtuI4akH0MmGzlBUylhXvdNVXcjAuIEcEQKtOBR9lU4wXQpISrbOT8ux
# +96GzBq8TdbhoFcmYaOBZKlwPP7pOp5Mzx/UMhyBA93PQhiCdPfIVOCINsUY4U23
# p4KJ3F1HqP3H6Slw3lHACnLilGETXRg5X/Fp8G8qlG5Y+M49ZEGUp2bneRLZoyHT
# yynHvFISpefhBCV0KdRZHPcuSL5OAGWnBjAlRtHvsMBrI3AAA0Tu1oGvPa/4yeei
# Ayu+9y3SLC98gDVbySnXnkujjhIh+oaatsk/oyf5R2vcxHahajMCAwEAAaOCAY4w
# ggGKMB8GA1UdIwQYMBaAFF9Y7UwxeqJhQo1SgLqzYZcZojKbMB0GA1UdDgQWBBSI
# YYyhKjdkgShgoZsx0Iz9LALOTzAOBgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIw
# ADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDBKBgNVHSAEQzBBMDUGDCsGAQQBsjEB
# AgEDCDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29tL0NQUzAIBgZn
# gQwBBAIwSgYDVR0fBEMwQTA/oD2gO4Y5aHR0cDovL2NybC5zZWN0aWdvLmNvbS9T
# ZWN0aWdvUHVibGljVGltZVN0YW1waW5nQ0FSMzYuY3JsMHoGCCsGAQUFBwEBBG4w
# bDBFBggrBgEFBQcwAoY5aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVi
# bGljVGltZVN0YW1waW5nQ0FSMzYuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz
# cC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAYEAAoE+pIZyUSH5ZakuPVKK
# 4eWbzEsTRJOEjbIu6r7vmzXXLpJx4FyGmcqnFZoa1dzx3JrUCrdG5b//LfAxOGy9
# Ph9JtrYChJaVHrusDh9NgYwiGDOhyyJ2zRy3+kdqhwtUlLCdNjFjakTSE+hkC9F5
# ty1uxOoQ2ZkfI5WM4WXA3ZHcNHB4V42zi7Jk3ktEnkSdViVxM6rduXW0jmmiu71Z
# pBFZDh7Kdens+PQXPgMqvzodgQJEkxaION5XRCoBxAwWwiMm2thPDuZTzWp/gUFz
# i7izCmEt4pE3Kf0MOt3ccgwn4Kl2FIcQaV55nkjv1gODcHcD9+ZVjYZoyKTVWb4V
# qMQy/j8Q3aaYd/jOQ66Fhk3NWbg2tYl5jhQCuIsE55Vg4N0DUbEWvXJxtxQQaVR5
# xzhEI+BjJKzh3TQ026JxHhr2fuJ0mV68AluFr9qshgwS5SpN5FFtaSEnAwqZv3IS
# +mlG50rK7W3qXbWwi4hmpylUfygtYLEdLQukNEX1jiOKMIIGgjCCBGqgAwIBAgIQ
# NsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYD
# VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBS
# U0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjEwMzIyMDAwMDAwWhcNMzgw
# MTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1p
# dGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFJvb3Qg
# UjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiJ3YuUVnnR3d6Lkm
# gZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTkdizICFNeINCSJS+lV1ipnW5ihkQy
# C0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfhpdXJ2aHHsPHggGsCi7uE0awqKggE
# /LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmUCO2GAXsePHi+/JUNAax3kpqstbl3
# vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tNK/WABKf+Gnoi4cmisS7oSimgHUI0
# Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHflm/FDDrOJ3rWqauUP8hsokDoI7D/
# yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXKrop06m7NUNHdlTDEMovXAIDGAvYy
# nPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6aEtE9vtiTMzz/o2dYfdP0KWZwZIX
# bYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1R5vj3NgevsAsvxsAnI8Oa5s2oy25
# qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGOJEoqzEIbW3q0b2iPuWLA911cRxgY
# 5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9lo3l7jk27MAe145GWxK4O3m3gEFEI
# kv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOCARYwggESMB8GA1UdIwQYMBaAFFN5
# v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBT2d2rdP/0BE/8WoWyCAi/QCj0U
# JTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zATBgNVHSUEDDAKBggr
# BgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0
# cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25B
# dXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDov
# L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEADr5lQe1oRLjl
# ocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39rAARaAdAxUeiX1ktLJ3+lgxtoLQhn
# 5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8hjsDLRhsyeIiJsms9yAWnvdYOdEM
# q1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb1SxVf7nkNtUjPfcxuFtrQdRMRi/f
# InV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa6HAJyz9gvEOcF1VWXG8OMeM7Vy7B
# s6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwDiGinCwKPyFO7ApcmVJOtlw9FVJxw
# /mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1NiurMp4pmAWjR+Pb/SIduPnmFzbSN/
# G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn6G/UYOy8k60mKcmaAZsEVkhOFuoj
# 4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0a7SBIkiRzfPfS9T+JesylbHa1LtR
# V9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl14suVFBmbzrt5V5cQPnwtd3UOTpS
# 9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43ET7HHFHeRPRYrMBKjkb8/IN7Po0d0
# hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8wgga5MIIEoaADAgECAhEAmaOACiZVO2Wr
# 3G6EprPqOTANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoT
# GVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0
# aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0
# d29yayBDQSAyMB4XDTIxMDUxOTA1MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkG
# A1UEBhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIG
# A1UEAxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAnSPPBDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTm
# flN4mSAfgLKTvggv24/rWiVGzGxT9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbv
# IKck6+hI1shsylP4JyLvmxwLHtSworV9wmjhNd627h27a8RdrT1PH9ud0IF+njvM
# k2xqbNTIPsnWtw3E7DmDoUmDQiYi/ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzu
# C8KNqfcYf7Z4/iZgkBJ+UFNDcc6zokZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD
# 2bSlbfsq7BiqljjaCun+RJgTgFRCtsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QO
# ZgYqVWQ4x6cM7/G0yswg1ElLlJj6NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkc
# SkAExzd4prHwYjUXTeZIlVXqj+eaYqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDY
# O962WZayx7ACFf5ydJpoEowSP07YaBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq
# 1UiYPIAHlezf4muJqxqIns/kqld6JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBS
# ND36E/ENVv8urPS0yOnpG4tIoBGxVCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JS
# qPECAwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA
# 23ufoHTKsW73PMAywHDNMB8GA1UdIwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79
# MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAn
# MCWgI6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUF
# BwEBBGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNv
# bTAyBggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNh
# Mi5jZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93
# d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q
# 7EP89DttyZqGYn2XRDhJkL6P+/T0IPZyxfxiXumYlARMgwRzLRUStJl490L94C9L
# GF3vjzzH8Jq3iR74BRlkO18J3zIdmCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLU
# UAsO0eqeLNhLVsgw6/zOfImNlARKn1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXd
# d3x2mbJCKKtl2s42g9KUJHEIiLni9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rN
# LqyA3ahe8WlxVWkxyrQLjH8ItI17RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMc
# ZNhpn8zJ+6MTyE6YoEBSRVrs0zFFIHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vU
# BfSouCReZwSLo8WdrDlPXtR0gicDnytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTp
# V2Na3PR7nxYVlPu1JPoRZCbH86gc96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGg
# K2W1eEJfo2qyrBNPvF7wuAyQfiFXLwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2yw
# zOr+tKyEVAotnyU9vyEVOaIYMk3IeBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602
# NCmvO1nm+/80nLy5r0AZvCQxaQ4xggXDMIIFvwIBATBqMFYxCzAJBgNVBAYTAlBM
# MSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0Nl
# cnR1bSBDb2RlIFNpZ25pbmcgMjAyMSBDQQIQCDJPnbfakW9j5PKjPF5dUTANBglg
# hkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3
# DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEV
# MC8GCSqGSIb3DQEJBDEiBCCexACxa621D8oyM/MznQnSndMtwzKDXysOV4S6i9vf
# +zANBgkqhkiG9w0BAQEFAASCAYBkmCUBGWMMn7EpidN3wOxfx17iXBviO+Eq6fJh
# CouRLwopBHCgDxKR7Y5gyrrl3Wx5O+aJFgkiz/48D6H70YGP6MzX/16ov37SDOZg
# 3ZEADcazwEK4pRqsYBrb3K2R8rmkv1C4wqiOt2Z+EhzhAFDGE8l+YA4dQNKEVAMe
# ZV9qQ3Edg+UpYe/0sX8F461tzuLWnAHV4rtVujoYVGjUc6C2mvhO2kqG8517ccen
# FR6Aps6IhBnt6SsGOYqX+KupJCCHXmVGn1op3GfGGHM2nElKsJ38rivfLeUZpg0Y
# u+JBuWYM/M4EKSruzsBgclEE5Srp/WKMjY11clnDULlBTEHINLFEtvEjkG7hN0Wd
# nItuAv9HJNLAHcLUB6FVrLLGWQ3NWcUaXbISiGlCpqFX2J8i2eLV8yHQCMQ447tP
# 36RANQRLJSE5MRy7joHjNo/Osg+dhHVDGhMg5GBzG3T1cHXqLfIt49MJPA1FHEVl
# u9bESZM52jRgPcWWRiiEUgN9j92hggMjMIIDHwYJKoZIhvcNAQkGMYIDEDCCAwwC
# AQEwajBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSww
# KgYDVQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIzNgIRAKQp
# O24e3denNAiHrXpOtyQwDQYJYIZIAWUDBAICBQCgeTAYBgkqhkiG9w0BCQMxCwYJ
# KoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNjAzMTEwODUxMzBaMD8GCSqGSIb3
# DQEJBDEyBDAqo6q9t7Mvrs8pxQX8yqrg59L5LKRrrgOWe/3UH/V34SNOhSD62Gb4
# cXRu+4iL0IQwDQYJKoZIhvcNAQEBBQAEggIAMkukwlzRE/pGx+gspX2ybTcET+9k
# WV4YcToMtfGt63WUPO9fVFNl6f7yxYeXhYnGBWp4PVaUCoZrKqQRGC9/xbEG5GvC
# OaXQwmOdkCluNhgQ5qJqhI8ASVZmFnU4eennOudt7l66HXZYYvBcvytR21Nem1+m
# ndH/sUEhJSAYPd/KLlkV0BBCzMJEuURBogIAKRjXQnx6W3eZodS6aYDUSpWhaNsD
# nrZ4lQbWL12CNSegGlEFyoMc9mwr5SnDqOXkcR/agBMQAFaxoxNxAozWRBqt+z1F
# 18BNfHcIc6hX6MzkNVOlfl2f8M0RC9Rtjlqh3oiW3yBK2obb+bE3WnBGVYlBn/V0
# vi7bGErG2O1Y2j05SFtZhDhVoUZOEUabncLhahXdDO492jA2a2TjOvzTZYp2o0b5
# vDAg9WiQ9YCzQfqdQfjM4gafYjdP8UGCAfbVmcu2Vk3dp1Ws5/sd50HsiatscU71
# 6LEjH/uR6L/qlEjKpjNtOUaYy9Zb5TEJVygQJfnwIsW4n/w4gas1t70mqRAQOZbp
# ITMEbaV8iB7UaipOnPMO3WVeE7iwOw1euiG9OjgxlE78eqgQwjIQ+kl/zmv4yT3b
# IgyTVv6DzEl2+xshipilKoUNQA128hvYA8MhNG507zL4md24p62FgmE18ULV3cxi
# 0/x3rUYwyzJvevA=
# SIG # End signature block