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 |