Classes/reporting.psm1

<#
.SYNOPSIS
    Common Reporting functions across all modules/scenarios
.DESCRIPTION
    Logging, Reporting
.INPUTS
    Inputs (if any)
.OUTPUTS
    Output (if any)
.NOTES
    General notes
#>


function Set-AzStackHciOutputPath
{

    param ($Path, $Source='azshciarc/Diagnostic')
    if ([string]::IsNullOrEmpty($Path))
    {
        $Path = Join-Path -Path $HOME -ChildPath ".AzStackHci"
    }
    $Global:AzStackHciEnvironmentLogFile = Join-Path -Path $Path -ChildPath 'AzStackHciArcIntegration.log'
    Assert-EventLog -source $Source
    Set-AzStackHciIdentifier
}




function Log-Info
{
    <#
    .SYNOPSIS
        Write verbose logging to disk
    .DESCRIPTION
        Formats and writes verbose logging to disk under scriptroot. Log type (or severity) is essentially cosmetic
        to the verbose log file, no action should be inferred, such as termination of the script.
    .EXAMPLE
        Write-AzStackHciEnvironmentLog -Message ('Script messaging include data {0}' -f $data) -Type 'Info|Warning|Error' -Function 'FunctionName'
    .INPUTS
        Message - a string of the body of the log entry
        Type - a cosmetic type or severity for the message, must be info, warning or error
        Function - ideally the name of the function or the script writing the log entry.
    .OUTPUTS
        Appends Log entry to AzStackHciArcIntegration.log under the script root.
    .NOTES
        General notes
    #>

    [cmdletbinding()]
    param(
        [string]
        $Message,

        [ValidateSet('Info', 'Warning', 'Error', 'Success')]
        [string]
        $Type = 'Info',

        [ValidateNotNullOrEmpty()]
        [string]$Function = ((Get-PSCallStack)[0].Command),

        [switch]$ConsoleOut,

        [switch]$Telemetry
    )
    $Message = RunMask $Message
    if ($ConsoleOut)
    {
        if ($true)
        {
            switch -wildcard ($function)
            {
                '*-AzStackHciArc*' { $foregroundcolor = 'DarkYellow' }
                default { $foregroundcolor = "White" }
            }
            switch ($Type)
            {
                'Success' { $foregroundcolor = 'Green' }
                'Warning' { $foregroundcolor = 'Yellow' }
                'Error' { $foregroundcolor = 'Red' }
                default { $foregroundcolor = "White" }
            }
            Write-Host $message -ForegroundColor $foregroundcolor
        }
        else
        {
            Write-Host $message
        }
    }
    else
    {
        Write-Verbose $message
    }

    if (-not [string]::IsNullOrEmpty($message))
    {
        # Log to ETW
        if ($Telemetry)
        {
            $source = "azshciarc/Telemetry"
            $EventId = 17201
        }
        else
        {
            $source = "azshciarc/Operational"
            $EventId = 17203
        }
        $logName = 'azshciarc'
        $EventType = switch ($Type)
        {
            "Error" { "Error" }
            "Warning" { "Warning" }
            "Success" { "Information" }
            "Info" { "Information" }
            Default { "Information" }
        }

        # Only write telemetry or non-info entries to the eventlog to save time and noise.
        if ($Telemetry -or $EventType -ne "Information")
        {
            Write-ETWLog -Source $Source -logName $logName -Message $Message -EventType $EventType -EventId $EventId
        }
        # Log to file
        $entry = "[{0}] [{1}] [{2}] {3}" -f ([datetime]::now).tostring(), $type, $function, ($Message -replace "`n|`t", "")
        if (-not (Test-Path $AzStackHciEnvironmentLogFile))
        {
            New-Item -Path $AzStackHciEnvironmentLogFile -Force | Out-Null
        }
        $retries = 3
        for ($i = 1; $i -le $retries; $i++) {
            try {
                $entry | Out-File -FilePath $AzStackHciEnvironmentLogFile -Append -Force -Encoding UTF8
                $writeFailed = $false
                break
            }
            catch {
                $writeFailed = "Log-info $i/$retries failed: $($_.ToString())"
                start-sleep -Seconds 5
            }
        }
        if ($writeFailed)
        {
            throw $writeFailed
        }
    }
}

function RunMask
{
    [cmdletbinding()]
    [OutputType([string])]
    Param (
        [Parameter(ValueFromPipeline = $True)]
        [string]
        $in
    )
    Begin {}
    Process
    {
        try
        {
            <#$in | Get-PIIMask | Get-GuidMask#>
            $in | Get-GuidMask
        }
        catch
        {
            $_.exception
        }
    }
    End {}
}

function Get-PIIMask
{
    [cmdletbinding()]
    [OutputType([string])]
    Param (
        [Parameter(ValueFromPipeline = $True)]
        [string]
        $in
    )
    Begin
    {
        $pii = $($ENV:USERDNSDOMAIN), $($ENV:COMPUTERNAME), $($ENV:USERNAME), $($ENV:USERDOMAIN) | ForEach-Object {
            if ($null -ne $PSITEM)
            {
                $PSITEM
            }
        }
        $r = $pii -join '|'
    }
    Process
    {
        try
        {
            return [regex]::replace($in, $r, "[*redacted*]")
        }
        catch
        {
            $_.exception
        }
    }
    End {}
}

function Get-GuidMask
{
    [OutputType([string])]
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $True)]
        [String]
        $guid
    )
    Begin
    {
        $r = [regex]::new("(-([a-fA-F0-9]{4}-){3})")

    }
    Process
    {
        try
        {
            return [regex]::replace($guid, $r, "-xxxx-xxxx-xxxx-")
        }
        catch
        {
            $_.exception
        }
    }
    End {}
}

function Write-AzStackHciHeader
{
    <#
    .SYNOPSIS
        Write invocation and system information into log and writes cmdlet name and version to screen.
    #>

    param (
        [Parameter()]
        [System.Management.Automation.InvocationInfo]
        $invocation,

        [psobject]
        $params,

        [switch]
        $PassThru
    )
    try
    {
        $paramToString = ($params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join ';'
        $cmdLetName = Get-CmdletName
        $cmdletVersion = (Get-Command $cmdletName -ErrorAction SilentlyContinue).version.tostring()
        Log-Info -Message ''
        Log-Info -Message ('{0} v{1} started.' -f `
                $cmdLetName, $cmdletVersion) `
            -ConsoleOut:(-not $PassThru)
        #TODO: Need to fix module name
        Log-Info -Telemetry -Message ('{0} started version: {1} with parameters: {2}. Id:{3}' `
                -f $cmdLetName, (Get-Module AzStackHci.EnvironmentChecker).Version.ToString(), $paramToString, $ENV:EnvChkrId)

        Log-Info -Message ('OSVersion: {0} PSVersion: {1} PSEdition: {2} Security Protocol: {3} Lanaguage Mode: {4}' -f `
                [environment]::OSVersion.Version.tostring(), $PSVersionTable.PSVersion.tostring(), $PSEdition, [Net.ServicePointManager]::SecurityProtocol, $ExecutionContext.SessionState.LanguageMode)
        Write-PsSessionInfo -params $params
    }
    catch
    {
        if (-not $PassThru)
        {
            Log-Info ("Unable to write header to screen. Error: {0}" -f $_.exception.message)
        }
    }
}

function Write-AzStackHciFooter
{
    <#
    .SYNOPSIS
        Writes report, log and cmdlet to screen.
    #>

    param (
        [Parameter()]
        [System.Management.Automation.InvocationInfo]
        $invocation,

        [switch]
        $failed,

        [switch]
        $PassThru
    )

    Log-Info -Message ("`nLog location: $AzStackHciEnvironmentLogFile") -ConsoleOut:(-not $PassThru)
    # Log-Info -Message ("Report location: $AzStackHciEnvironmentReport") -ConsoleOut:(-not $PassThru)
    # Log-Info -Message ("Use -Passthru parameter to return results as a PSObject.") -ConsoleOut:(-not $PassThru)
    if ($failed)
    {
        Log-Info -Message ("{0} failed" -f (Get-CmdletName)) -ConsoleOut:(-not $PassThru) -Type Error -Telemetry
    }
    else
    {
        Log-Info -Message ("{0} completed. Id:{1} " -f (Get-CmdletName),$ENV:EnvChkrId) -Telemetry
    }
}

function Get-CmdletName
{
    try
    {
        foreach ($c in (Get-PSCallStack).Command)
        {
            $functionCalled = Select-String -InputObject $c -Pattern "Invoke-AzStackHci(.*)Validation"
            if ($functionCalled)
            {
                 break
            }
        }
        $functionCalled
    }
    catch
    {
        throw "Hci Validation"
    }
}



function Write-ETWLog
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $source = 'azshciarc/Diagnostic',

        [Parameter()]
        [string]
        $logName = 'azshciarc',

        [Parameter(Mandatory = $true)]
        [string]
        $Message,

        [Parameter()]
        [string]
        $EventId = 0,

        [Parameter()]
        [string]
        $EventType = 'Information'
    )
    try
    {
        Write-EventLog -LogName $LogName -Source $Source -EntryType $EventType -Message $Message -EventId $EventId
    }
    catch
    {
        throw "Creating event log failed. Error $($_.exception.message)"
    }
}

function Assert-EventLog
{
    param (
        [Parameter()]
        [string]
        $source = 'azshciarc/Diagnostic'
    )
    try
    {
        $eventLog = Get-EventLog -LogName azshciarc -Source $Source -ErrorAction SilentlyContinue
    }
    catch {}
    # Try to create the log
    if (-not $eventLog)
    {
        New-AzStackHciArcIntegrationLog
    }
}

function Test-Elevation
{
    return  ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function New-AzStackHciArcIntegrationLog
{
    try
    {
        $scriptBlock = {
            $logName = 'azshciarc'
            $sources = @('azshciarc/Operational', 'azshciarc/Diagnostic', 'azshciarc/Telemetry')
            foreach ($source in $sources)
            {
                New-EventLog -LogName $logName -Source $Source -ErrorAction SilentlyContinue
                Limit-EventLog -LogName $logName -MaximumSize 250MB -ErrorAction SilentlyContinue
                Write-EventLog -Message ('Initializing log provider {0}' -f $source) -EventId 0 -EntryType Information -Source $source -LogName $logName -ErrorAction Stop
            }
        }

        if (Test-Elevation)
        {
            Invoke-Command -ScriptBlock $scriptBlock
        }
        else
        {
            $psProcess = if (Join-Path -Path $PSHOME -ChildPath powershell.exe -Resolve -ErrorAction SilentlyContinue)
            {
                Join-Path -Path $PSHOME -ChildPath powershell.exe
            }
            elseif (Join-Path -Path $PSHOME -ChildPath pwsh.exe -Resolve -ErrorAction SilentlyContinue)
            {
                Join-Path -Path $PSHOME -ChildPath pwsh.exe
            }
            else
            {
                throw "Cannot find powershell process. Please run powershell elevated and run the following command: 'New-EventLog -LogName $logName -Source $sourceName'"
            }
            Write-Warning "We need to run an elevated process to register our event log. `nPlease continue and accept the UAC prompt to continue. `nAlternatively, run: `nNew-EventLog -LogName $logName -Source $source `nmanually and restart this command."
            if (Grant-UACConcent)
            {
                Start-Process $psProcess -Verb Runas -ArgumentList "-command (Invoke-Command -ScriptBlock {$scriptBlock})" -Wait
            }
            else
            {
                throw "Unable to elevate and register event log provider."
            }
        }
    }
    catch
    {
        throw "Failed to create Environment Checker log. Error: $($_.Exception.Message)"
    }
}

function Remove-AzStackHciArcIntegrationEventLog
{
    <#
    .SYNOPSIS
        Remove AzStackHCI Environment Checker event log
    .EXAMPLE
        Remove-AzStackHciArcIntegrationEventLog -Verbose
        Remove AzStackHCI Environment Checker event log
    #>

    [cmdletbinding()]
    param()
    Remove-EventLog -LogName "azshciarc"
}


function Grant-UACConcent
{
    $concentAnswered = $false
    $concent = $false
    while ($false -eq $concentAnswered)
    {
        $promptResponse = Read-Host -Prompt "Register the event log. (Y/N)"
        if ($promptResponse -imatch '^y$|^yes$')
        {
            $concentAnswered = $true
            $concent = $true
        }
        elseif ($promptResponse -imatch '^n$|^no$')
        {
            $concentAnswered = $true
            $concent = $false
        }
        else
        {
            Write-Warning "Unexpected response"
        }
    }
    return $concent
}

function Write-Summary
{
    param ($result, $property1, $property2, $property3, $seperator = '->')
    try
    {
        $summary = Get-Summary @PSBoundParameters

        # Write percentage
        Write-Host "`nSummary"
        Write-Host $lTxt.Summary
        if (-not ([string]::IsNullOrEmpty($summary.FailedResourceCritical)))
        {
            Write-Host " " -NoNewline
            Write-StatusSymbol -status 'Failed' -Severity Critical
            Write-Host (" {0} Critical Issue(s)" -f @($summary.FailedResourceCritical).Count)
        }

        if (-not ([string]::IsNullOrEmpty($summary.FailedResourceWarning)))
        {
            Write-Host " " -NoNewline
            Write-StatusSymbol -status 'Failed' -Severity Warning
            Write-Host (" {0} Warning Issue(s)" -f @($summary.FailedResourceWarning).Count)
        }

        if (-not ([string]::IsNullOrEmpty($summary.FailedResourceInformational)))
        {
            Write-Host " " -NoNewline
            Write-StatusSymbol -status 'Failed' -Severity Informational
            Write-Host (" {0} Informational Issue(s)" -f @($summary.FailedResourceInformational).Count)
        }

        if ($Summary.successCount -gt 0)
        {
            Write-Host " " -NoNewline
            Write-StatusSymbol -status 'Succeeded'
            Write-Host (" {0} successes" -f ($Summary.successCount))
        }

        <#Write-Host @expandDownSymbol
        Write-Host " " -NoNewline
        switch ($Severity)
        {
            'Critical' { Write-Host @redCrossSymbol }
            'Warning' { Write-Host @warningSymbol }
            Default { Write-Host @redCrossSymbol }
        }#>

        #Write-Host (" {0} / {1} ({2}%)" -f $summary.SuccessCount, $Result.AdditionalData.Resource.Count, $summary.SuccessPercentage)

        # Write issues by severity
        foreach ($severity in 'Critical', 'Warning', 'Informational')
        {
            $SeverityProp = "FailedResource{0}" -f $severity
            $failedResources = $summary.$SeverityProp | Sort-Object | Get-Unique

            if ($failedResources -gt 0)
            {
                Write-Host ""
                Write-Severity -severity $Severity
                Write-Host ""
                #Write-Host "`n$Severity Issues:"
                $failedResources | Sort-Object | Get-Unique | ForEach-Object {
                    Write-Host " " -NoNewline
                    switch ($Severity)
                    {
                        'Critical' { Write-Host @redCrossSymbol }
                        'Warning' { Write-Host @warningSymbol }
                        Default { Write-Host @redCrossSymbol }
                    }
                    Write-Host " $PSITEM"
                }
            }
        }

        if ($Summary.HelpLinks)
        {
            Write-Host "`nRemediation: "
            $Summary.HelpLinks | ForEach-Object {
                Write-Host " " -NoNewline
                Write-Host @helpSymbol
                Write-Host " $PSITEM"
            }
        }

        if (-not $summary.FailedResourceCritical -and -not $summary.FailedResourceWarning -and -not $summary.FailedResourceInformational)
        {
            Write-Host "`nSummary"
            Write-Host @expandOutSymbol
            Write-Host " " -NoNewline
            Write-Host @greenTickSymbol
            Write-Host (" {0} / {1} ({2}%) resources test successfully." -f $summary.SuccessCount, $Result.AdditionalData.Resource.Count, $summary.SuccessPercentage)
        }
    }
    catch
    {
        Log-Info -Message "Summary failed. $($_.Exception.Message)" -ConsoleOut -Type Warning
    }
}

function Get-Summary
{
    param ($result, $property1, $property2, $property3, $seperator = '->')

    try
    {
        if (-not $result)
        {
            throw "Unable to write summary. Check tests run successfully."
        }
        [array]$success = $result | Select-Object -ExpandProperty AdditionalData | Where-Object Status -EQ 'Succeeded'
        [array]$HelpLinks = $result | Where-Object Status -NE 'Succeeded' | Select-Object -ExpandProperty Remediation | Sort-Object | Get-Unique
        [array]$nonSuccess = $result | Select-Object -ExpandProperty AdditionalData | Where-Object Status -NE 'Succeeded'
        [array]$nonSuccessCritical = $result | Where-Object Severity -EQ Critical | Select-Object -ExpandProperty AdditionalData | Where-Object Status -NE 'Succeeded'
        [array]$nonSuccessWarning = $result | Where-Object Severity -EQ Warning | Select-Object -ExpandProperty AdditionalData | Where-Object Status -NE 'Succeeded'
        [array]$nonSuccessInformational = $result | Where-Object Severity -EQ Informational | Select-Object -ExpandProperty AdditionalData | Where-Object Status -NE 'Succeeded'

        $successPercentage = if ($success.count -gt 0)
        {
            [Math]::Round(($success.Count / $result.AdditionalData.Resource.count) * 100)
        }
        else
        {
            0
        }

        $sourceDestsb = {
            if ([string]::IsNullOrEmpty($_.$property2) -and [string]::IsNullOrEmpty($_.$property3))
            {
                "{0}" -f $_.$property1
            }
            elseif ([string]::IsNullOrEmpty($_.$property3))
            {
                "{0}{1}{2}" -f $_.$property1, $seperator, $_.$property2
            }
            else
            {
                "{0}{1}{2}({3})" -f $_.$property1, $seperator, $_.$property2, $_.$property3
            }
        }
        $FailedResourceCritical = $nonSuccessCritical |
        Select-Object @{ label = 'SourceDest'; Expression = $sourceDestsb } -ErrorAction SilentlyContinue |
        Select-Object -ExpandProperty SourceDest |
        Sort-Object |
        Get-Unique

        $FailedResourceWarning = $nonSuccessWarning |
        Select-Object @{ label = 'SourceDest'; Expression = $sourceDestsb } -ErrorAction SilentlyContinue |
        Select-Object -ExpandProperty SourceDest |
        Sort-Object |
        Get-Unique

        $FailedResourceInformational = $nonSuccessInformational |
        Select-Object @{ label = 'SourceDest'; Expression = $sourceDestsb } -ErrorAction SilentlyContinue |
        Select-Object -ExpandProperty SourceDest |
        Sort-Object |
        Get-Unique

        $summary = New-Object -Type PsObject -Property @{
            successCount                = $success.Count
            nonSuccessCount             = $nonSuccess.Count
            successPercentage           = $successPercentage
            HelpLinks                   = $HelpLinks
            FailedResourceCritical      = $FailedResourceCritical
            FailedResourceWarning       = $FailedResourceWarning
            FailedResourceInformational = $FailedResourceInformational
        }
        return $summary
    }
    catch
    {
        throw "Unable to calculate summary. Error $($_.exception.message)"
    }
}

# Symbols
$global:greenTickSymbol = @{
    Object          = [Char]0x2713     #8730
    ForegroundColor = 'Green'
    NoNewLine       = $true
}
$global:redCrossSymbol = @{
    Object          = [Char]0x2622 #0x00D7
    ForegroundColor = 'Red'
    NoNewLine       = $true
}

$global:WarningSymbol = @{
    Object          = [char]0x26A0
    ForegroundColor = 'Yellow'
    NoNewLine       = $true
}

$global:bulletSymbol = @{
    Object    = [Char]0x25BA
    NoNewLine = $true
}

# Text
$global:needsAttention = @{
    object          = $lTxt.NeedsAttention;
    ForegroundColor = 'Yellow'
    NoNewLine       = $true
}

$global:needsRemediation = @{
    object          = $lTxt.NeedsRemediation;
    ForegroundColor = 'Red'
    NoNewLine       = $true
}

$global:ForInformation = @{
    object    = $lTxt.ForInformation;
    NoNewLine = $true
}

$global:expandDownSymbol = @{
    object    = [Char]0x25BC # expand down
    NoNewLine = $true
}

$global:expandOutSymbol = @{
    object    = [Char]0x25BA # expand out
    NoNewLine = $true
}

$global:helpSymbol = @{
    object    = [char]0x270E   #0x263C # sunshine
    NoNewLine = $true
    #ForegroundColor = 'Yellow'
}

$global:Critical = @{
    object          = $lTxt.Critical;
    ForegroundColor = 'Red'
    NoNewLine       = $true
}

$global:Warning = @{
    object          = $lTxt.Warning;
    ForegroundColor = 'Yellow'
    NoNewLine       = $true
}

$global:Information = @{
    object    = $lTxt.Informational;
    NoNewLine = $true
}

$global:isHealthy = @{
    object    = $lTxt.Healthy
    NoNewLine = $true
}

function Write-StatusSymbol
{
    param ($status, $severity)
    switch ($status)
    {
        "Succeeded" { Write-Host @greenTickSymbol }
        "Failed"
        {
            switch ($Severity)
            {
                'Critical' { Write-Host @redCrossSymbol }
                'Warning' { Write-Host @warningSymbol }
                Default { Write-Host @redCrossSymbol }
            }
        }
        Default { Write-Host @bulletSymbol }
    }
}

function Write-Severity
{
    param ($severity)
    switch ($severity)
    {
        'Critical' { Write-Host @needsRemediation }
        'Warning' { Write-Host @needsAttention }
        'Informational' { Write-Host @ForInformation }
        Default { Write-Host @Critical }
    }
}

function Set-AzStackHciIdentifier
{
    $ENV:EnvChkrId = $null
    if ([string]::IsNullOrEmpty($ENV:EnvChkrOp))
    {
        $ENV:EnvChkrOp = 'Manual'
    }
    $validatorCmd = Get-CmdletName
    if(-not [string]::IsNullOrWhiteSpace($validatorCmd))
    {
        $ENV:EnvChkrId = "{0}\{1}\{2}" -f $ENV:EnvChkrOp, $validatorCmd.matches.groups[1], (([system.guid]::newguid()) -split '-' | Select-Object -first 1)
    }
}

function Write-PsSessionInfo
{
    <#
    .SYNOPSIS
        Write some pertainent information to the log about any PsSessions passed
    #>

    [CmdletBinding()]
    param (
        $params
    )
    try {
        if ($params['PsSession'])
        {
            foreach ($session in $params['PsSession'])
            {
                Log-Info -Message ("PsSession info: {0}, {1}, {2}, {3}, {4}, {5}" -f $session.ComputerName, $session.Name, $session.Id, $session.Runspace.ConnectionInfo.credential.username, $session.Runspace.SessionStateProxy.LanguageMode, $session.Runspace.ConnectionInfo.AuthenticationMechanism)
            }
        }
        else
        {
            Log-Info -Message "No PsSession info to write"
        }
    }
    catch
    {
        Log-Info -Message "Failed to write PsSession info: $($_.exception.message)"
    }
}

function Write-AzStackHciResult
{
    <#
    .SYNOPSIS
        Displays results to screen
    .DESCRIPTION
        Displays test results to screen, highlighting failed tests.
    #>

    param (
        [Parameter()]
        [string]
        $Title,

        [Parameter()]
        [psobject]
        $result,

        $seperator = ' -> ',

        [switch]
        $Expand,

        [switch]
        $ShowFailedOnly
    )

    try
    {
        if (-not $result)
        {
            throw "Results missing. Ensure tests ran successfully."
        }
        Log-Info ("`n{0}:" -f $Title) -ConsoleOut


        foreach ($r in ($result | Sort-Object Status, Title, Description))
        {
            if ($r.status -ne 'Succeeded' -or $Expand)
            {
                Write-StatusSymbol -Status $r.Status -Severity $r.Severity
                Write-Host " " -NoNewline
                Write-Host @expandDownSymbol
                Write-Host " " -NoNewline
                if ($r.status -ne 'Succeeded')
                {
                    switch ($r.Severity)
                    {
                        Critical { Write-Host @needsRemediation }
                        Warning { Write-Host @needsAttention }
                        Informational { Write-Host @forInformation }
                        Default { Write-Host @Critical }
                    }
                }
                Write-Host " " -NoNewline
                Write-Host ($r.TargetResourceType + " - " + $r.Title + " " + $r.Description)
                foreach ($detail in ($r.AdditionalData | Sort-Object Status -Descending))
                {
                    if ($ShowFailedOnly -and $detail.Status -eq 'Succeeded')
                    {
                        continue
                    }
                    else
                    {
                        Write-Host " " -NoNewline
                        Write-StatusSymbol -Status $detail.Status -Severity $r.Severity
                        Write-Host " " -NoNewline
                        Write-Host " " -NoNewline
                        Write-Host ("{0}{1}{2}" -f $detail.Source, $seperator, $detail.Resource)
                    }
                }
                if ($detail.Status -ne 'Succeeded')
                {
                    Write-Host " " -NoNewline
                    Write-Host @helpSymbol
                    Write-Host (" Help URL: {0}" -f $r.Remediation)
                    Write-Host ""
                }
            }
            else
            {
                if (-not $ShowFailedOnly)
                {
                    Write-Host @expandOutSymbol
                    Write-Host " " -NoNewline
                    Write-Host @greenTickSymbol
                    Write-Host " " -NoNewline
                    Write-Host @isHealthy
                    Write-Host " " -NoNewline
                    Write-Host ($r.TargetResourceType + " " + $r.Title + " " + $r.Description)
                }
            }
        }
    }
    catch
    {
        Log-Info "Unable to write results. Error: $($_.exception.message)" -Type Warning
    }
}


# Export-ModuleMember -function Get-AzStackHciArcIntegrationEvents
Export-ModuleMember -function Log-Info
Export-ModuleMember -function Set-AzStackHciOutputPath
Export-ModuleMember -function Write-AzStackHciFooter
Export-ModuleMember -function Write-AzStackHciHeader
Export-ModuleMember -function Write-AzStackHciResult
Export-ModuleMember -function Write-ETWLog
# Export-ModuleMember -function Write-ETWResult
Export-ModuleMember -function Write-Summary

# SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD5Z24L9DOGRR/R
# DXINqHMVhHIep9IrNZxXgLIXtJh7zKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIKd1mJVc83J8zyLTLBmJBKuh
# r/sJouiTZPnSeX2lrFloMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAkN6wbh82iCzw29QTZp2RagsjTMjjOIPligo+LNjuxuP1yNA/k5gqtc+X
# sBVCFLQr9EXOP66ikIEtd7VAa0MWngQCZOW6kww/YGBDPGOW+BwIjp4tUtmmfg6z
# PRmjwz0Nh/hKtSzm5aRTCh2AYAkydBes3bYqSYFySJLFYUdg3FCPxehUclLuwfxv
# ME+5Giu0S3DjpHRgHpLpRl5EuyX4S601dPlkRrExPOmSZ7okClewLRv/gZlqKI7G
# wIHEik2DWDnJKU90Yb+Wucxuq4ap1H4kExTy7HXGyJ8ux29ftUEYR2kRHy7+cfSk
# LptnNA/fLy6/6DoRzLGyD2PH3GB2z6GCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCNdhmDyp77XHC7ZTarQJfe926C/Xcsd63+j7IFj7+MBQIGZbpfK/C8
# GBMyMDI0MDIwMTExMDUxMC4zNjZaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0w
# M0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAdGyW0AobC7SRQABAAAB0TANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
# MThaFw0yNDAyMDExOTEyMThaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCZTNo0OeGz2XFd2gLg5nTlBm8XOpuwJIiXsMU61rwq
# 1ZKDpa443RrSG/pH8Gz6XNnFQKGnCqNCtmvoKULApwrT/s7/e1X0lNFKmj7U7X4p
# 00S0uQbW6LwSn/zWHaG2c54ZXsGY+BYfhWDgbFpCTxRzTnRCG62bkWPp6ZHbZPg4
# Ht1CRCAMhhOGTR8wI4G7wwWZwdMc6UvUUlq0ql9AxAfzkYRpi2tRvDHMdmZ3vyXp
# qhFwvRG8cgCH/TTCjW5q6aNbdqKL3BFDPzUtuCNsPXL3/E0dR2bDMqa0aNH+iIfh
# GC4/vcwuteOMCPUIDVSqDCNfIaPDEwYci1fd9gu1zVw+HEhDZM7Ea3nxIUrzt+Rf
# p5ToMMj4QAmJ6Uadm+TPbDbo8kFIK70ShmW8wn8fJk9ReQQEpTtIN43eRv9QmXy3
# Ued80osOBE+WkdMvSCFh+qgCsKdzQxQJG62cTeoU2eqNhH3oppXmyfVUwbsefQzM
# PtbinCZd0FUlmlM/dH+4OniqQyaHvrtYy3wqIafY3zeFITlVAoP9q9vF4W7KHR/u
# F0mvTpAL5NaTDN1plQS0MdjMkgzZK5gtwqOe/3rTlqBzxwa7YYp3urP5yWkTzISG
# nhNWIZOxOyQIOxZfbiIbAHbm3M8hj73KQWcCR5JavgkwUmncFHESaQf4Drqs+/1L
# 1QIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFAuO8UzF7DcH0mmsF4XQxxHQvS2jMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCbu9rTAHV24mY0qoG5eEnImz5akGXTviBw
# Kp2Y51s26w8oDrWor+m00R4/3BcDmYlUK8Nrx/auYFYidZddcUjw42QxSStmv/qW
# nCQi/2OnH32KVHQ+kMOZPABQTG1XkcnYPUOOEEor6f/3Js1uj4wjHzE4V4aumYXB
# Asr4L5KR8vKes5tFxhMkWND/O7W/RaHYwJMjMkxVosBok7V21sJAlxScEXxfJa+/
# qkqUr7CZgw3R4jCHRkPqQhMWibXPMYar/iF0ZuLB9O89DMJNhjK9BSf6iqgZoMuz
# IVt+EBoTzpv/9p4wQ6xoBCs29mkj/EIWFdc+5a30kuCQOSEOj07+WI29A4k6QIRB
# 5w+eMmZ0Jec0sSyeQB5KjxE51iYMhtlMrUKcr06nBqCsSKPYsSAITAzgssJD+Z/c
# TS7Cu35fJrWhM9NYX24uAxYLAW0ipNtWptIeV6akuZEeEV6BNtM3VTk+mAlV5/eC
# /0Y17aVSjK5/gyDoLNmrgVwv5TAaBmq/wgRRFHmW9UJ3zv8Lmk6mIoAyTpqBbuUj
# MLyrtajuSsA/m2DnKMO0Qiz1v+FSVbqM38J/PTlhCTUbFOx0kLT7Y/7+ZyrilVCz
# yAYfFIinDIjWlM85tDeU8ZfJCjFKwq3DsRxV4JY18xww8TTmod3lkr9NqGQ54Lmy
# PVc+5ibNrjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBH
# JY2Fv+GhLQtRDR2vIzBaSv/7LKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6WWGUTAiGA8yMDI0MDIwMTAyNTQw
# OVoYDzIwMjQwMjAyMDI1NDA5WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDpZYZR
# AgEAMAcCAQACAhaoMAcCAQACAhMdMAoCBQDpZtfRAgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAEL3hetQ6S5kVcRV7PLddY6xYin4sTe8ObZ4u7b2mUIdyv5T
# d6yBh6X3XAV4Dj0Kgfb5+ZYIe4VdBTYo5tXIKdss98FU+6lPYS8FWNNOXbiKio8m
# QPiaZjwW3fae63NVWagsMMmHz0yw2w4wGMdKW4q7WTnoz+CZsAe57KKpbdDsDel2
# umcU75MTPOxRwajCemo5XkANzDejAnCetT5SeYYPKqZe6G/7y8gJklqd7xZ2L65E
# sDQbV5fGHUL1wuFJrViLxDtXzAZX2g/1N8t540FN84XRfJkGuC1jSHEgC/sD1JWA
# dw4Ahv0nWQEfa7WyHfdJE5rolft9KdnCZNo2PhcxggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdGyW0AobC7SRQABAAAB0TAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCA08jxWAeYeF8ptzxtT16KOIBYkJAUp5LufwpiobnchGDCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMy8YXkCALv57c5sRhrPTub1q4Tw
# J6oVA36k8IiI/AcMMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHRsltAKGwu0kUAAQAAAdEwIgQg3FvexdYHKCmckefasBUVBNSJjJOd
# GFHC/iSnRy6xOSswDQYJKoZIhvcNAQELBQAEggIAEPxwmrp3DbilZ/rXo92INpA2
# /G4s2ibWhkePTfSQHCls7vZs1ZCBCYlpuZK3gpPkNhM/l2i2NJGJ4wY2zXUcVHM9
# LZlmKeL3H7uU1OLFichKQxcND6PBQahrCbrwFLXplrlHwJNr+JfX//78LTTTS4HW
# OJ/cxw3y4XvYiC4thQIWFMEidSg8tYSAm32y+PL1P7fooO5C81ePUHLsG03WXaKZ
# v6btCItBtbHydViJ4ybNxizC06s26GS1f1cieihxN4xH5lu6htKCj2iKDAUPxoEO
# ChCgLo9hBsNjK9rCCWsdrjmVgQ18rohqFzpbqm1jf1XrqIhlTNz0UxY36iGBu8Sh
# VGgyLM9YnBU5WtlK3G9DobjK8+HKT7cyACe3f+dCpjV5s3Niae5640EVVskmJS1S
# qKqe4mhyQvMQ1gLujbx2lpCv0zoOcqHnh7vXa/ktuPKZ1S4Z8xaoD0Ix57Wnw65/
# Q/5r0scPOGoQf32eP/541Q7qxGkPyGaO51CIWYpGJnbms3Lz4kUXBzIsCT9TD299
# hvftAEeU7Jo4uGPMNYJpj08wkCP4pnloq+xzhBVGMQ3zT8IlA69+VOAwnEdCtZhr
# jqi0Q/8dgankIx+7Pbm9p/JmG82SO7Tj5peI1FK5994ziI7Yo06BqZOL9fd3gN0H
# RNucPVnv9E9rSs6kW6I=
# SIG # End signature block