Framework/Managers/AzSKPDFExtension.ps1

Set-StrictMode -Version Latest

class AzSKPDFExtension
{
    static [void] GeneratePDF([string] $reportFolderPath, [PSObject] $organizationObject, [PSObject] $dataObject, [bool] $isLandscape)
    {
        # Get Context Info
        $executedBy = ([ContextHelper]::GetCurrentContext()).Account

        # Verify whether word is installed on machine

        If (test-path HKLM:SOFTWARE\Classes\Word.Application)
        {
            # Initialize word file
            try
            {
                $Word = New-Object -ComObject word.application
                $Word.Visible = $false;
                $AzSKReportDoc = $Word.Documents.Add();
                if($isLandscape)
                {
                    $AzSKReportDoc.PageSetup.Orientation = 1
                }
                else
                {
                    $AzSKReportDoc.PageSetup.Orientation = 0
                }

                $pdfPath = "$reportFolderPath\SecurityReport.pdf"
                $margin = 36 # 1.26 cm
                $AzSKReportDoc.PageSetup.LeftMargin = $margin
                $AzSKReportDoc.PageSetup.RightMargin = $margin
                #$AzSKReportDoc.PageSetup.TopMargin = $margin
                $AzSKReportDoc.PageSetup.BottomMargin = $margin

                $isOrganizationCore = $false

                $selection = $Word.Selection
                $selection.WholeStory
                $selection.Style = "No Spacing"

                # Region Front Page
                [AzSKPDFExtension]::WriteText($selection, 'DevSecOps Kit for Azure (AzSK)','Title', $true, $true, $false)
                [AzSKPDFExtension]::WriteText($selection, 'Security Report','TOC Heading', $true, $true, $false)
                $selection.InsertBreak(6)
                $selection.InsertBreak(6)
                $selection.InsertBreak(6)
                $selection.InsertBreak(6)
                $selection.InsertBreak(6)

                $TitleTableRange = $selection.Range();
                $AzSKReportDoc.Tables.Add($TitleTableRange,11,2) | Out-Null
                $AzSKTitleTable = $AzSKReportDoc.Tables.item(1)
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 1, 'Organization Name', $organizationObject.OrganizationName)
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 2, 'OrganizationId', $organizationObject.OrganizationId)
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 3, 'AzSK.ADO Version', $dataObject.MyCommand.Version.ToString())
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 4, 'Generated by', $dataObject.MyCommand.ModuleName.ToString())
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 5, 'Generated on', (get-date).ToUniversalTime().ToString("MMMM dd, yyyy HH:mm") + " (UTC)")
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 6, 'Requested by', $executedBy.Id.ToString() + " (" + $executedBy.Type.ToString() + ")")
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 7, 'Command Executed', $dataObject.Line.Trim())
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 8, 'Documentation', 'http://aka.ms/azskdocs')
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 9, 'FAQ', 'http://aka.ms/azskdocs/faq')
                [AzSKPDFExtension]::WriteHeaderTableCell($AzSKTitleTable, 10, 'Support DL', [ConfigurationManager]::GetAzSKConfigData().SupportDL)

                $AzSKTitleTable.Borders.OutsideLineStyle = 1
                $AzSKTitleTable.Style = 'Table Grid Light'
                $AzSKTitleTable.Borders.OutsideLineStyle = 1
                $AzSKTitleTable.Borders.InsideLineStyle = 0
                $AzSKTitleTable.Columns.AutoFit()

                $Word.Selection.Start= $AzSKReportDoc.Content.End

                $selection.InsertBreak(7)
                #end region

                # Region TOC
                [AzSKPDFExtension]::WriteText($selection, 'Contents','TOC Heading', $false, $true, $false)
                $range = $Selection.Range
                $toc = $AzSKReportDoc.TablesOfContents.Add($range)
                $selection.TypeParagraph()
                $selection.InsertBreak(7)

                # End region TOC

                # Region Headers/Footers

                #$Section = $AzSKReportDoc.Sections.Item(1)
                #$Header = $Section.Footers.Item(1)
                #$Header.Range.Text = (get-date).ToUniversalTime().ToString("HH:mm MMMM dd, yyyy") + "(UTC)"
                #$Header.Range.Font.Size = 9
                #$Header.Range.ParagraphFormat.Alignment = 2
                $AzSKReportDoc.Sections(1).Footers(1).PageNumbers.Add(2)

                # End region Headers/Footers

                #region -> Add the CSV report
                $selection.TypeText("Security Report Summary");
                $selection.Style = 'Heading 1'
                $selection.TypeParagraph()
                $selection.Style = 'No Spacing'
                $selection.InsertBreak(6)

                $ReportRange = $selection.Range();

                $reportCSVFilePath = @();
                $reportCSVFilePath += Get-ChildItem -Path $reportFolderPath -Filter "*.CSV" -Recurse
                if($reportCSVFilePath.Length -le 0)
                {
                    [AzSKPDFExtension]::WriteText($selection, 'Unable to find the required security report under the report folder.','No Spacing', $false, $true, $false)
                    [AzSKPDFExtension]::WriteText($selection, 'Or','No Spacing', $true, $true, $false)
                    [AzSKPDFExtension]::WriteText($selection, 'No controls have been found to evaluate for the organization.','No Spacing', $false, $true, $false)
                    #throw "Didn't find the required security report under the report folder.";
                }
                else
                {
                    $controls = Import-Csv -Path $reportCSVFilePath[0].FullName
                    $isAttestedResult = $false
                    if(($controls | Measure-Object).Count -gt 0)
                    {
                        $Number_Of_Controls = (($controls | Measure-Object).Count +1)
                        if($controls[0] | Get-Member -Name "AttestedSubStatus")
                        {
                            $isAttestedResult = $true
                        }

                        if($isAttestedResult)
                        {
                            $Number_Of_Columns = 7 # ControlID, Status, RG, ResourceName, Control Severity
                        }
                        else
                        {
                            $Number_Of_Columns = 6
                        }

                        $x = 2

                        $AzSKReportDoc.Tables.Add($ReportRange,$Number_Of_Controls,$Number_Of_Columns) | Out-Null
                        $AzSKReportTable = $AzSKReportDoc.Tables.item(2)

                        $AzSKReportTable.Cell(1,1).Range.Text = "ControlId"
                        $AzSKReportTable.Cell(1,2).Range.Text = "Status"
                        $AzSKReportTable.Cell(1,3).Range.Text = "ResourceGroup"
                        $AzSKReportTable.Cell(1,4).Range.Text = "Resource"
                        $AzSKReportTable.Cell(1,5).Range.Text = "Severity"
                        $AzSKReportTable.Cell(1,6).Range.Text = "Description"
                        if($isAttestedResult)
                        {
                            $AzSKReportTable.Cell(1,7).Range.Text = "Attestation Description"
                        }

                        Foreach($control in $controls)
                        {
                             $AzSKReportTable.Cell($x,1).Range.Text=$control.ControlId
                             $AzSKReportTable.Cell($x,2).Range.Text=$control.Status
                             if($control | Get-Member -Name "ResourceGroupName")
                             {
                                $AzSKReportTable.Cell($x,3).Range.Text=$control.ResourceGroupName
                                if(($control | Get-Member -Name "ChildResourceName") -and (-Not [string]::IsNullOrEmpty($control.ChildResourceName)))
                                {
                                    $AzSKReportTable.Cell($x,4).Range.Text=$control.ResourceName + "/" + $control.ChildResourceName
                                }
                                else
                                {
                                    $AzSKReportTable.Cell($x,4).Range.Text=$control.ResourceName
                                }
                             }
                             else
                             {
                                $isOrganizationCore = $true
                                $AzSKReportTable.Cell($x,3).Range.Text="Organization"
                                $AzSKReportTable.Cell($x,4).Range.Text="Organization"
                             }
                             $AzSKReportTable.Cell($x,5).Range.Text=$control.ControlSeverity
                             $AzSKReportTable.Cell($x,6).Range.Text=$control.Description
                             $AzSKReportTable.Cell($x,6).Range.Font.Size = 9

                             if($isAttestedResult -and ($control.AttestedSubStatus))
                             {
                                #$AzSKReportTable.Cell($x,7).Range.Text=$control.ActualStatus
                                $attstionDescription = "Attested Status: " + $control.AttestedSubStatus + "`vAttested By: " + $control.AttestedBy + "`vJustification: " + $control.AttesterJustification
                                $AzSKReportTable.Cell($x,7).Range.Text = $attstionDescription
                                $AzSKReportTable.Cell($x,7).Range.Font.Size = 9
                             }
                             $x++

                             #if(($control | Get-Member -Name "AttestedSubStatus") -and ($control.AttestedSubStatus))
                             #{
                                #$AzSKReportTable.Cell($x,2).Range.Text= "Actual Status : " + $control.ActualStatus

                                #$attstionDescription = "Attestation Description`vAttested Status: " + $control.AttestedSubStatus + "`vAttested By: " + $control.AttestedBy + "`vJustification: " + $control.AttesterJustification
                                #$AzSKReportTable.Cell($x,6).Range.Text = $attstionDescription
                                #$AzSKReportTable.Cell($x,6).Range.Font.Size = 9
                                #$x++;
                             # }
                        }

                        $AzSKReportTable.Style = 'Grid Table 4 - Accent 1'
                        $AzSKReportTable.Columns.Autofit()
                        $selection = $Word.Selection
                        $selection.WholeStory
                        $selection.Style = "No Spacing"
                        $wdStory = 6
                        $wdMove = 0

                        $ret = $selection.EndKey($wdStory, $wdMove)
                        $selection.TypeParagraph()
                        $selection.InsertBreak(7)
                    }
                

                    #end region

                    #region -> Adding PowerShell output

                    Get-ChildItem -Path $reportFolderPath -Directory | Where-Object {($_.Name -eq "etc")} | ForEach-Object {
                        $rootfolder = $_
                        [AzSKPDFExtension]::WriteText($selection, 'PowerShell Output','Heading 1', $false, $true, $false)

                        Get-ChildItem -Path $rootfolder.FullName -Recurse -Filter "PowerShellOutput.LOG" | ForEach-Object {
                            $logfilepath = $_
                            $log = Get-Content $logfilepath.FullName | Out-String
                            [AzSKPDFExtension]::WriteText($selection, $log,'No Spacing', $false, $true, $false)
                            $selection.TypeText("#################################################################");
                            $selection.TypeParagraph()
                        }
                    }

                    $selection.InsertBreak(7)

                    #end region -> Adding PowerShell output

                    #region -> Adding detailed logs

                    [AzSKPDFExtension]::WriteText($selection, 'Detailed Output','Heading 1', $false, $true, $false)
                    $selection.InsertBreak(6)

                    Get-ChildItem -Path $reportFolderPath -Directory | Where-Object {-not ($_.Name -eq "etc")} | ForEach-Object {
                        $rootfolder = $_

                        if($isOrganizationCore)
                        {
                            [AzSKPDFExtension]::WriteText($selection, 'Organization Name: '+ ($rootfolder.Name),'Heading 2', $false, $true, $false)
                        }
                        else
                        {
                            [AzSKPDFExtension]::WriteText($selection, 'Resource Group Name: ' + ($rootfolder.Name),'Heading 2', $false, $true, $false)
                        }
                        Get-ChildItem -Path $rootfolder.FullName -Recurse -Filter "*.LOG" | ForEach-Object {
                            $logfilepath = $_
                            [AzSKPDFExtension]::WriteText($selection, 'Resource Type: ' + ($logfilepath.BaseName),'Heading 3', $false, $true, $false)
                            $logs = Get-Content $logfilepath.FullName
                            ForEach($log in $logs)
                            {
                                [AzSKPDFExtension]::WriteText($selection, ($log | Out-String),'No Spacing', $false, $false, $false)
                            }

                            $selection.TypeParagraph()
                            $selection.InsertBreak(7)
                        }
                    }

                    #end region

                    # Update table of content
                    $toc.Update()
                }
            }
            catch
            {
                throw $_.Exception
            }
            finally
            {
                $wdExportFormatPDF = 17
                $wdDoNotSaveChanges = 0
                $AzSKReportDoc.ExportAsFixedFormat($pdfPath,$wdExportFormatPDF)
                $AzSKReportDoc.close([ref]$wdDoNotSaveChanges)
                $Word.Quit()
                if (test-path variable:AzSKReportDoc)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($AzSKReportDoc) | Out-Null
                }
                if (test-path variable:word)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null
                }
                if (test-path variable:range)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($range) | Out-Null
                }
                if (test-path variable:ReportRange)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ReportRange) | Out-Null
                }
                if (test-path variable:AzSKReportTable)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($AzSKReportTable) | Out-Null
                }
                if (test-path variable:TitleTableRange)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($TitleTableRange) | Out-Null
                }
                if (test-path variable:AzSKTitleTable)
                {
                    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($AzSKTitleTable) | Out-Null
                }

                Remove-Variable range
                [gc]::collect()
                [gc]::WaitForPendingFinalizers()
            }
        }
        else
        {
            throw ([SuppressedException]::new(("You must have Microsoft Word application installed on machine to generate PDF report."), [SuppressedExceptionType]::Generic))
        }
    }

    static [void] WriteText([PSObject] $selectionObj, [string] $textToWrite, [string] $style, [bool] $bold, [bool] $newParagraph, [bool] $newLine)
    {
        $selectionObj.TypeText($textToWrite);
        $selectionObj.Style = $style
        if($bold)
        {
            $selectionObj.Range.Font.Bold = 1
        }
        else
        {
            $selectionObj.Range.Font.Bold = 0
        }

        if($newParagraph)
        {
            $selectionObj.TypeParagraph()
        }
        if($newLine)
        {
            $selectionObj.TypeText("`v");
        }
        $selectionObj.WholeStory
        $selectionObj.Style = "No Spacing"
    }

    static [void] WriteHeaderTableCell([PSObject] $tableObj, [int] $row, [string] $title, [string] $value)
    {
        $tableObj.Cell($row,1).Range.Text = $title
        $tableObj.Cell($row,1).Range.Bold = 1
        $tableObj.Cell($row,2).Range.Text = $value
    }
} 

# SIG # Begin signature block
# MIIjmAYJKoZIhvcNAQcCoIIjiTCCI4UCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA9VpovVuIfOw+c
# U7H6uUsl8zj5W9S+rOToqQKhFf8HoKCCDYUwggYDMIID66ADAgECAhMzAAAB4HFz
# JMpcmPgZAAAAAAHgMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ2WhcNMjExMjAyMjEzMTQ2WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDRXpc9eiGRI/2BlmU7OMiQPTKpNlluodjT2rltPO/Gk47bH4gBShPMD4BX/4sg
# NvvBun6ZOG2dxUW30myWoUJJ0iRbTAv2JFzjSpVQvPE+D5vtmdu6WlOR2ahF4leF
# 5Vvk4lPg2ZFrqg5LNwT9gjwuYgmih+G2KwT8NMWusBhO649F4Ku6B6QgA+vZld5S
# G2XWIdvS0pmpmn/HFrV4eYTsl9HYgjn/bPsAlfWolLlEXYTaCljK7q7bQHDBrzlR
# ukyyryFpPOR9Wx1cxFJ6KBqg2jlJpzxjN3udNJPOqarnQIVgB8DUm3I5g2v5xTHK
# Ovz9ucN21467cYcIxjPC4UkDAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUVBWIZHrG4UIX3uX4142l+8GsPXAw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2MzAxMDAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AE5msNzmYzYbNgpnhya6YsrM+CIC8CXDu10nwzZtkgQciPOOqAYmFcWJCwD5VZzs
# qFwad8XIOrfCylWf4hzn09mD87yuazpuCstLSqfDLNd3740+254vEZqdGxOglAGU
# ih2IiF8S0GDwucpLGzt/OLXPFr/d4MWxPuX0L+HB5lA3Y/CJE673dHGQW2DELdqt
# ohtkhp+oWFn1hNDDZ3LP++HEZvA7sI/o/981Sh4kaGayOp6oEiQuGeCXyfrIC9KX
# eew0UlYX/NHVDqr4ykKkqpHtzbUbuo7qovUHPbYKcRGWrrEtBS5SPLFPumqsRtzb
# LgU9HqfRAN36bMsd2qynGyWBVFOM7NMs2lTCGM85Z/Fdzv/8tnYT36Cmbue+IM+6
# kS86j6Ztmx0VIFWbOvNsASPT6yrmYiecJiP6H0TrYXQK5B3jE8s53l+t61ab0Eul
# 7DAxNWX3lAiUlzKs3qZYQEK1LFvgbdTXtBRnHgBdABALK3RPrieIYqPln9sAmg3/
# zJZi4C/c2cWGF6WwK/w1Nzw08pj7jaaZZVBpCeDe+y7oM26QIXxracot7zJ21/TL
# 70biK36YybSUDkjhQPP/uxT0yebLNBKk7g8V98Wna2MsHWwk0sgqpkjIp02TrkVz
# 26tcF2rml2THRSDrwpBa4x9c8rM8Qomiyeh2tEJnsx2LMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWkwghVlAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAHgcXMkylyY+BkAAAAA
# AeAwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIOAV
# UkjOT/cqj5EeBLQLzne3Pd4Ltj8gikZ1TJkLBvwpMEQGCisGAQQBgjcCAQwxNjA0
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu
# Y29tIDANBgkqhkiG9w0BAQEFAASCAQC4fAZrHpIwF4lYikuAVI8Z50ozA69un1Rk
# YPJhdQ0tax4mVtVc1gH6iiU3Rvdweqf+r4gSLfEbS75is732K1rCGsBIShTWwS5Q
# i+aS1RLfGfjGScmtU3CEgxWKlTo9Qfpz3svT6zC1UKgRmuLmjl6qXSe3YkC2jjqb
# A/lw6In0sqOwP8Nk1wJI2ATkEN+5mXRuo22GY6ooBKm04giTwbPR3XFnP+t9WIUh
# SSnqbIRdmBfWAPO1ravWiZAThZ4b4XDcCLnnIDlSwIPs4qGzudAJGLUwTVsGcRV3
# eznKBCBP82QPyD/VR6viC/osjb3/s48tiW7o/+4jof2fRloIBP+PoYIS8TCCEu0G
# CisGAQQBgjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglg
# hkgBZQMEAgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEE
# AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIDcPfcGe6NkIMzFvr9uP1p/+GgvMRwA+
# ErMoDf5f852OAgZhRNRvLMUYEzIwMjEwOTI0MTM0NjA2LjQzNVowBIACAfSggdSk
# gdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNV
# BAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjo3ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABXIbS4+w5
# 9os4AAAAAAFcMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwMB4XDTIxMDExNDE5MDIxN1oXDTIyMDQxMTE5MDIxN1owgc4xCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m
# dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT
# Tjo3ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAqKz6vKh9i
# QLJoEvJcCUwd5fnatwQXmIZMlJ84v4nsMufnfJbG623f/U8OBu1syOXOZ7VzHOeC
# /+rIZrzcJQaQ8r5lCQjn9fiG3jk+pqPSFRl3w9YGFjouy/FxuSA6s/Mv7b0GS0ba
# HTlJFgRuDKBgBsTagGR4SRmHnFdrShT3tk1T3WASLTGISeGGx4i0DyDuz8zQVmpp
# L8+BUyiPir0W/XZOsuA6FExj3gTvyTLfiDVhmNcwzPv5LQlrIVir0m2+7UDTY8in
# zHl/2ClH4N42uqbWk9H2I4rpCCcUWSBw1m8De4hTsTGQET7qiR+FfI7PQlJQ+9ef
# 7ANAflPSNhsCAwEAAaOCARswggEXMB0GA1UdDgQWBBRf8xSsOShygJAFf7iaey1j
# GMG6PjAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBN
# MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0
# cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoG
# CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB4eUz2134gcw4cI/Uy2e2j
# kO7tepK7T5WXt76a/Az/pdNCN6B1D6QYRRHL90JmGlZMdUtwG/A6E9RqNqDv9aHQ
# 8/2FLFcrrNOTgDQ0cjZ/9Mx8sto17k4rW22QvTYOQBB14ouNAsDloZ9aqc/Qtmi8
# JFHd6Mc7vE5oDgeVGm3y8er7UgLn4gkTIYn8leTPY9H2yuoLfXbQ8Xrl0WWFBbmZ
# m6t3DEG+r6raImNJ7PrBQHnpdXxCjjF5WNPMYNeKEWA+RkyA5FD5YA0uKXtlNd5H
# gvwcWo/ACGyuPluxwYBcDCZQFFatov0uNjOsQxEMRgAzpA5vqEOOx/TUEYFkcRBJ
# MIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z
# b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEz
# NjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1
# +n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROO
# fGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEby
# WEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqv
# S2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noq
# CjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVP
# Ik0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpc
# ijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV
# HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo
# 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m
# dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j
# cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCB
# oAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0w
# QAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQA
# YQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+B
# cQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RX
# CCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31a
# PxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E
# 4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517
# IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl
# 5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ
# 3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm7
# 7MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1Qn
# IffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1
# rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrv
# CScc1bN+NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jv
# c29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNT
# IEVTTjo3ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAnuKlo8afKEeVnH5d6yP4nk5p8Eyg
# gYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0B
# AQUFAAIFAOT35IQwIhgPMjAyMTA5MjQwOTQ1NDBaGA8yMDIxMDkyNTA5NDU0MFow
# dzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5PfkhAIBADAKAgEAAgIjzQIB/zAHAgEA
# AgISKTAKAgUA5Pk2BAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC
# oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBACNKEzoe
# cEg7CUeAV8aRla5tPCNKAr4Fb6T0ifW4UlwhIWCTA6NR6JFOJml5hSctxYgdDlJx
# d0aavvwKGpeSdHHzA0sruqy3DAfQboNUhO0QYy2JK39TPNwZYedhtZJ0/L1ZpZFg
# 0im7sv6HAVlq2hy5f51NFYffzA8sZAimGzBuMYIDDTCCAwkCAQEwgZMwfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFchtLj7Dn2izgAAAAAAVwwDQYJ
# YIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkq
# hkiG9w0BCQQxIgQgJ2/hn8TiRW2vTFaWBe4eT+9ZSBid85/4mzjZa5z99vgwgfoG
# CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBPLWRXwiBPbAAwScykICtQPfQuIIhG
# bxXvtFyPDBnmtTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# AhMzAAABXIbS4+w59os4AAAAAAFcMCIEICiQwN4XdhMBGtjrjncxpLYLJqZ5aphB
# tjB+elsTDzGfMA0GCSqGSIb3DQEBCwUABIIBAJsQNiipXog7V8OpVJavmWmg+mR/
# sN+ZnRrfTdKZpY0PQLOlhv8xtwJyCaNm+oTUzhV7JV0mFjGM0quLSwefy/po8u+O
# TLkbykihjIVCtbsYGtfJoQNU7x77dSBM5810h3cK1QTDkpYNu0vdVabHhaQzKVDS
# kdUbJd/Feoms0GjDOR/tUP/bTeW51tPMcWokEbmEKs6HhYFJBB54TTaR3l2zzw/X
# Yw9FEatdPJs+F9ue13hQJ8SUk4bWg2njKGcjHzjr8AWJwSEnjfOsPksSazRJcc2z
# yWgOg8ILcvSQIi9pw648R1IK1R5M3+JtHgwzq2r2lenMdbpleIYQgX66fN0=
# SIG # End signature block