modules/AzStack.Ece/AzStack.Ece.psm1

#################################################################
# #
# Copyright (C) Microsoft Corporation. All rights reserved. #
# #
#################################################################

#################################################################
# #
# STARTUP ACTIONS ON IMPORT #
# #
#################################################################

Import-LocalizedData -BindingVariable 'msg' -BaseDirectory "$PSScriptRoot\locale" -UICulture (Get-Culture) -WarningAction SilentlyContinue
Import-Module $PSScriptRoot\..\AzStack.Core\AzStack.Core.psm1 -WarningAction SilentlyContinue
Import-Module $PSScriptRoot\..\AzStack.Common\AzStack.Common.psm1 -WarningAction SilentlyContinue

# import the EceClient module if it is not already imported
if (-not (Get-Module -Name 'EceClient')) {
    Import-Module -Name 'EceClient' -Global -ErrorAction Continue 4> $null 3> $null
}

$configurationData = Import-PowerShellDataFile -Path "$PSScriptRoot\AzStack.Ece.Config.psd1"
New-Variable -Name 'CSSTools_AzStack_ECE' -Scope 'Global' -Force -Value @{
    Config = $configurationData
}

#################################################################
# #
# ENUMS AND CLASSES #
# #
#################################################################


#################################################################
# #
# FUNCTIONS #
# #
#################################################################

function Show-AzsSupportEceData {
    <#
    .SYNOPSIS
        Show ECE data for the current Azure Stack HCI cluster.
    .DESCRIPTION
        This function retrieves and displays information about the ECE deployment, including the deployment status, policy
    #>


    Confirm-AzsSupportOSVersion -MinimumVersion 23H2

    $clusterNodes = Get-ClusterNode
    $policyInfoJob = Start-DiagnosticCheck -ComputerNames $clusterNodes -DiagCheck {
        return @{
            "policyInfo" = Get-ASLocalWDACPolicyInfo
        }
    }

    # in some deployments there is no deployment xml - we will now output this information.
    if (Test-Path -Path $Global:CSSTools_AzStack_ECE.Config.EceStore.Files.Deployment) {
        $deploymentActionPlan = [xml](Get-Content -Raw -Path $Global:CSSTools_AzStack_ECE.Config.EceStore.Files.Deployment)
        $deploymentAction = $deploymentActionPlan.Action
    }
    else {
        $deploymentAction = @{"status" = "Unknown - Deployment file not found. You may not be on the Seed node." }
    }

    $policyInfo = Reconcile-DiagnosticCheck -jobs $policyInfoJob

    # 1. List Deployment information
    Write-Host "====[ $($msg.eceClusterOverview) ]====" -ForegroundColor White -BackgroundColor DarkGray
    Get-StampInformation | Select-Object DeploymentID, *version*, Hardware*, NumberOfNodes
    Write-Host "`n"

    # 2. Check if cluster is deployed
    Write-Host "====[ $($msg.eceClusterDeployment) ]====" -ForegroundColor White -BackgroundColor DarkGray
    if ($deploymentAction.status -eq "Error") {
        Trace-Output -Level:Exception -Message $msg.eceClusterDeploymentFail
    }
    else {
        Trace-Output -Level:Success -Message $msg.eceClusterDeploymentSuccess
    }
    Write-Host "`n"

    # 3. Check policy mode
    Write-Host "====[ $($msg.eceWdacState) ]====" -ForegroundColor White -BackgroundColor DarkGray

    foreach ($info in $policyInfo) {
        Trace-Output -Level:Information -Message ($msg.eceWdacStateNode -f $info.name)
        $info.output.policyInfo | Select-Object PolicyMode, PolicyName, PolicyVersion, Status, PolicyScope, MicrosoftProvided, IsSigned | Format-Table -AutoSize
    }

    # 3. Cluster Action Plan State
    Write-Host "====[ $($msg.eceActionPlanState) ]====" -ForegroundColor White -BackgroundColor DarkGray
    Get-ActionPlanInstances | Select-Object InstanceID, ActionPlanName, ActionTypeName, Status, StartDateTime, EndDateTime | Sort-Object -Property StartDateTime | Select-Object -Last 20 | Format-Table -AutoSize
    Write-Host "`n"
}

function Show-AzsSupportEceDeploymentDetail {
    <#
    .SYNOPSIS
        Show ECE deployment details for the current Azure Stack HCI cluster.
    .DESCRIPTION
        This function retrieves and displays detailed information about the ECE deployment action plan, including all steps and their statuses.
    .EXAMPLE
        PS> Show-AzsSupportEceDeploymentDetail
    #>

    Confirm-AzsSupportOSVersion -MinimumVersion 23H2

    $deploymentActionPlan = [xml](Get-Content -Raw -Path $Global:CSSTools_AzStack_ECE.Config.EceStore.Files.Deployment)
    $deploymentAction = $deploymentActionPlan.Action

    Write-Host "====[ $($msg.eceDeploymentOverview) ]====" -ForegroundColor White -BackgroundColor DarkGray
    Trace-Output -Level:Information -Message ($msg.eceDeploymentStart -f $deploymentAction.StartTimeUtc)
    Trace-Output -Level:Information -Message ($msg.eceDeploymentEnd -f $deploymentAction.EndTimeUtc)
    Trace-Output -Level:Information -Message ($msg.eceDeploymentState -f $deploymentAction.Status)

    # get all steps from the deployment
    Write-Host "====[ $($msg.eceActionplanAnalysis) ]====" -ForegroundColor White -BackgroundColor DarkGray
    Show-EceStep -obj $deploymentAction -showException $true
}

function Show-AzsSupportEceUpdateDetail {
    [CmdletBinding()]
    param(
        $MaxUpdateRuns = 20
    )

    Confirm-AzsSupportOSVersion -MinimumVersion 23H2

    $actionPlanInstances = Get-ActionPlanInstances | Where-Object { $_.ActionPlanName -like "*MAS Update*" } | Select-Object InstanceID, ActionPlanName, ActionTypeName, Status, StartDateTime, EndDateTime
    if ($actionPlanInstances) {
        $actionPlanInstances | Sort-Object -Property StartDateTime | Select-Object -Last $MaxUpdateRuns | Format-Table -AutoSize

        $actionPlanInstanceID = Read-Host -Prompt $msg.eceUpdateActionPlan
        if (-not [string]::IsNullOrEmpty($actionPlanInstanceID)) {
            $actionPlanObj = Get-ActionPlanInstance -actionPlanInstanceID $actionPlanInstanceID
            $actionPlanXml = [xml]$actionPlanObj.ProgressAsXml
            Show-EceStep -obj $actionPlanXml.Action -showException $true
        }
    }
}

function Show-EceStep {
    param (
        $obj,
        [int]$indentLevel = 0,
        [bool]$showException
    )

    $spaceBuffer = ""
    for ($i = 0; $i -lt $indentLevel; $i++) {
        $spaceBuffer += "|--"
    }
    $spaceBuffer += "->"

    if ($null -ne $obj.Action) {
        # Next move: Action
        foreach ($action in $obj.Action) {
            Show-EceStep -obj $action -indentLevel $indentLevel -showException $showException
        }
    }
    elseif ($null -ne $obj.Steps) {
        Trace-Output -Message ("{0} {1}" -f $spaceBuffer, $obj.Type)

        # Next move: Steps
        foreach ($steps in $obj.Steps) {
            Show-EceStep -obj $steps -indentLevel $indentLevel -showException $showException
        }
    }
    elseif ($null -ne $obj.Step) {
        # Next move: Step
        foreach ($step in $obj.Step) {
            Show-EceStep -obj $step -indentLevel $indentLevel -showException $showException
        }
    }
    elseif ($null -ne $obj.Task) {
        if ($obj.Status -eq "Error") {
            Trace-Output -Level:Error -Message ("{0}[{1}] {2}" -f $spaceBuffer, $obj.FullStepIndex , $obj.Name)
        }
        elseif ($obj.Status -eq "" -or $null -ieq $obj.Status) {
            Trace-Output -Message ("{0}[{1}] {2}" -f $spaceBuffer, $obj.FullStepIndex , $obj.Name)
        }
        else {
            Trace-Output -Message ("{0}[{1}] {2}" -f $spaceBuffer, $obj.FullStepIndex , $obj.Name)
        }

        $indentLevel = $obj.FullStepIndex.Split(".").Length

        # Next move: Task
        foreach ($task in $obj.Task) {
            Show-EceStep -obj $task -indentLevel $indentLevel -showException $showException
        }
    }
    elseif ($null -ne $obj.Exception) {
        if ($showException) {
            # we are in a task which has an exception linked to it.
            Trace-Output -Level:Exception -Message ($obj.Exception.Message)
        }
    }
}

function Get-AzsSupportECECloudDefinitionXml {
    <#
    .SYNOPSIS
        Retrieves the cloud definition from ECE and returns it as an XML object.
    .DESCRIPTION
        This function retrieves the cloud definition from the ECE deployment and returns it as an XML object
    .EXAMPLE
        PS> Get-AzsSupportECECloudDefinitionXml
    #>

    [CmdletBinding()]
    param()

    Confirm-AzsSupportOSVersion -MinimumVersion 23H2

    $cloudDef = Get-CloudDefinition -ErrorAction Stop
    return ([Xml]($cloudDef.CloudDefinitionAsXmlString)).CustomerConfiguration
}

function Invoke-AzsSupportECECustomActionPlan {
    <#
        .SYNOPSIS
            Executes a custom Enterprise Cloud Engine (ECE) action plan that are included as part of the Azs.Support module
        .PARAMETER Name
            The name of the action plan that should be executed via Enterprise Cloud Engine (ECE). This only looks at ECE action plans packaged within the Azs.Support module.
        .PARAMETER FilePath
            The full path to a custom ECE action plan xml file.
        .PARAMETER WaitForResult
            Waits for the cmdlet operation to complete
    #>


    [CmdletBinding(DefaultParameterSetName = 'Name')]
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'Name')]
        [ArgumentCompleter({
            $possibleValues = Get-ChildItem -Path "$PSScriptRoot\ActionXml" | Select-Object -ExpandProperty BaseName
            return $possibleValues | ForEach-Object { $_ }
        })]
        [System.String]$Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'FilePath')]
        [ValidateScript({
            if($_ -notmatch "(\.xml)"){
                throw "The leaf name specified must be a .xml file type"
            }
            return $true
        })]
        [System.IO.FileInfo]$FilePath,

        [Parameter(Mandatory = $false, ParameterSetName = 'Name')]
        [Parameter(Mandatory = $false, ParameterSetName = 'FilePath')]
        [Switch]$WaitForResult
    )

    Confirm-AzsSupportOSVersion -MinimumVersion 23H2

    try {
        switch($PSCmdlet.ParameterSetName) {
            'Name' {
                $eceActionPlanXML = Get-ChildItem -Path "$PSScriptRoot\ActionXml" | Where-Object {$_.BaseName -ieq $Name} -ErrorAction Stop
            }
            'FilePath' {
                $eceActionPlanXML = Get-Item -Path $FilePath.FullName -ErrorAction Continue
            }
            default {
                throw New-Object System.Exception("Invalid ParameterSetName")
            }
        }

        if ($eceActionPlanXML) {
            # get the description of the ECE action plan
            [xml]$content = Get-Content -Path $eceActionPlanXML.FullName
            $content.Action.Description | Trace-Output

            # invoke the ECE action plan
            Invoke-ActionPlanInstance -ActionPlanPath $eceActionPlanXML.FullName -WaitForResult:($WaitForResult)
        }
    }
    catch {
        $_ | Trace-Exception
        $_ | Write-Error
    }
}

function Get-AzsSupportEceDeploymentDetails {
    # TODO: Deprecated function - remove in 2026.01 release
    # has been replaced with Show-AzsSupportEceDeploymentDetail to align with PowerShell naming conventions
    $msg.AzsSupportEceDeploymentDetailsDeprecated | Write-Information -InformationAction Continue
}

# SIG # Begin signature block
# MIIoUgYJKoZIhvcNAQcCoIIoQzCCKD8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBTVLOqOGUyxgL2
# DVILMXDHi3A5Y+fv3QGJLMm1rweJlqCCDYUwggYDMIID66ADAgECAhMzAAAEhJji
# EuB4ozFdAAAAAASEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM1WhcNMjYwNjE3MTgyMTM1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDtekqMKDnzfsyc1T1QpHfFtr+rkir8ldzLPKmMXbRDouVXAsvBfd6E82tPj4Yz
# aSluGDQoX3NpMKooKeVFjjNRq37yyT/h1QTLMB8dpmsZ/70UM+U/sYxvt1PWWxLj
# MNIXqzB8PjG6i7H2YFgk4YOhfGSekvnzW13dLAtfjD0wiwREPvCNlilRz7XoFde5
# KO01eFiWeteh48qUOqUaAkIznC4XB3sFd1LWUmupXHK05QfJSmnei9qZJBYTt8Zh
# ArGDh7nQn+Y1jOA3oBiCUJ4n1CMaWdDhrgdMuu026oWAbfC3prqkUn8LWp28H+2S
# LetNG5KQZZwvy3Zcn7+PQGl5AgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUBN/0b6Fh6nMdE4FAxYG9kWCpbYUw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwNTM2MjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AGLQps1XU4RTcoDIDLP6QG3NnRE3p/WSMp61Cs8Z+JUv3xJWGtBzYmCINmHVFv6i
# 8pYF/e79FNK6P1oKjduxqHSicBdg8Mj0k8kDFA/0eU26bPBRQUIaiWrhsDOrXWdL
# m7Zmu516oQoUWcINs4jBfjDEVV4bmgQYfe+4/MUJwQJ9h6mfE+kcCP4HlP4ChIQB
# UHoSymakcTBvZw+Qst7sbdt5KnQKkSEN01CzPG1awClCI6zLKf/vKIwnqHw/+Wvc
# Ar7gwKlWNmLwTNi807r9rWsXQep1Q8YMkIuGmZ0a1qCd3GuOkSRznz2/0ojeZVYh
# ZyohCQi1Bs+xfRkv/fy0HfV3mNyO22dFUvHzBZgqE5FbGjmUnrSr1x8lCrK+s4A+
# bOGp2IejOphWoZEPGOco/HEznZ5Lk6w6W+E2Jy3PHoFE0Y8TtkSE4/80Y2lBJhLj
# 27d8ueJ8IdQhSpL/WzTjjnuYH7Dx5o9pWdIGSaFNYuSqOYxrVW7N4AEQVRDZeqDc
# fqPG3O6r5SNsxXbd71DCIQURtUKss53ON+vrlV0rjiKBIdwvMNLQ9zK0jy77owDy
# XXoYkQxakN2uFIBO1UNAvCYXjs4rw3SRmBX9qiZ5ENxcn/pLMkiyb68QdwHUXz+1
# fI6ea3/jjpNPz6Dlc/RMcXIWeMMkhup/XEbwu73U+uz/MIIHejCCBWKgAwIBAgIK
# 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/Xmfwb1tbWrJUnMTDXpQzTGCGiMwghofAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAASEmOIS4HijMV0AAAAA
# BIQwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEICAF
# 30EgmsuPoG9qaSTiV2KmG/h03ZHAC+T4slNJTcnnMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAiH4VJY8+286Q+tM4JqNXZK9RKOewZR2poybp
# xb9zL2b81lX9vhUvAL/m/pLNioYmGUvbtm/5kdmoFIJnJWCdw33eN46DcvijcSfF
# qUbBlCSGxAIGnt+oveSEAcgSkU62mIdHtXPErYP6+D8/AHpXJ15/sSdaXReBDl1f
# JRjwRgEN7JOAcN5OFUkiC+6GBsv+SZK4HrxdP1qdcjMJrt2dzAOxImXVmixZjVfm
# e8Ps8J49ZIxRlDvt6mgUitI64okS7gJLKVwSBVhydcd3pA3o4F2vgF6/5B8UvDTP
# KJcbqnFyS2MNWc2n9uhwhGSkUcegTD5GEYm9byDDU+K30RVxbaGCF60wghepBgor
# BgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBg1JrW0wW2vi5aveXChbbBsLlhxgpfSeym
# vyKYnNbmngIGaPG+juf1GBMyMDI1MTAyODIwNDEwNy4xNjVaMASAAgH0oIHZpIHW
# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
# Hm5TaGllbGQgVFNTIEVTTjoyRDFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAACEtEI
# BjzKGE+qAAEAAAISMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxNVoXDTI2MTExMzE4NDgxNVowgdMxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jv
# c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVs
# ZCBUU1MgRVNOOjJEMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# r0zToDkpWQtsZekS0cV0quDdKSTGkovvBaZH0OAIEi0O3CcO77JiX8c4Epq9uibH
# VZZ1W/LoufE172vkRXO+QYNtWWorECJ2AcZQ10bpAltkhZNiXlVJ8L3QzhKgrXrm
# Mkm2J+/g81U23JPcO4wXHEftonT3wpd//936rjmwxMm7NkbsygbJf+4AVBMNr4aM
# PQhBd76od0KMB6WrvyEGOOU0893OFufS5EDey4n44WgaxJE0Vnv3/OOvuOw5Kp1K
# PqjjYJ+L9ywLuBMtcDfLpNQO/h1eFEoMrbiEM67TOfNlXfxbDz4MlsYvLioxgd2X
# zey1QxrV1+i+JyVDJMiSe9gKOuzpiQQFE19DUPgsidyjLTzXEhSVLBlRor0eCVf7
# gC6Rfk8NY3rO2sggOL79vU5FuDKTh/sIOtcUHeHC42jBGB+tfdKC1KOBR+UlN9aO
# zg8mpUNI2FgqQvirVP9ppbeMUfvp2wA9voyTiRWvDgzCxo8xlJ1nscYTHIQrmkF9
# j/Ca0IDmt8fvOn64nnlJOGUYZYHMC1l0xtgkYTE1ESUqqkawKk7iqbxdnLyycS+d
# R+zaxPudMDLrQFz8lgfy9obk0D8HC2dzhWpYNn5hdkoPEzgCqQUOp8v3Qj/sd4an
# yupe5KoCkjABOP3yhSQ4W9Z+DrJnhM/rbsXC7oTv26cCAwEAAaOCAUkwggFFMB0G
# A1UdDgQWBBRSBblSxb5cYKYOwvd/VfoXOfu33jAfBgNVHSMEGDAWgBSfpxVdAF5i
# XYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
# JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRp
# bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1Ud
# JQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
# AAOCAgEAXnSAkmX79Rc7lxS1wOozXJ7V0ou5DntVcOJplIkDjvEN8BIQph4U+gSO
# LZuVReP/z9YdUiUkcPwL1PM245/kEX1EegpxNc8HDA6hKCHg0ALNEcuxnGOlgKLo
# kXfUer1D5hiW8PABM9R+neiteTgPaaRlJFvGTYvotc0uqGiES5hMQhL8RNFhpS9R
# cIWHtnQGEnrdOUvCAhs4FeViawcmLTKv+1870c/MeTHi0QDdeR+7/Wg4qhkJ2k1i
# EHJdmYf8rIV0NRBZcdRTTdHee35SXP5neNCfAkjDIuZycRud6jzPLCNLiNYzGXBs
# wzJygj4EeSORT7wMvaFuKeRAXoXC3wwYvgIsI1zn3DGY625Y+yZSi8UNSNHuri36
# Zv9a+Q4vJwDpYK36S0TB2pf7xLiiH32nk7YK73Rg98W6fZ2INuzYzZ7Ghgvfffkj
# 4EUXg1E0EffY1pEqkbpDTP7h/DBqtzoPXsyw2MUh+7yvWcq2BGZSuca6CY6X4ioM
# uc5PWpsmvOOli7ARNA7Ab8kKdCc2gNDLacglsweZEc9/VQB6hls/b6Kk32nkwuHE
# xKlaeoSVrKB5U9xlp1+c8J/7GJj4Rw7AiQ8tcp+WmfyD8KxX2QlKbDi4SUjnglv4
# 617R8+a/cDWJyaMt8279Wn7f2yMedN7kfGIQ5SZj66RdhdlZOq8wggdxMIIFWaAD
# AgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIy
# MjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5
# vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64
# NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhu
# je3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl
# 3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPg
# yY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I
# 5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2
# ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/
# TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy
# 16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y
# 1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6H
# XtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMB
# AAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQW
# BBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30B
# ATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYB
# BAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMB
# Af8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBL
# oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr
# BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNS
# b29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1Vffwq
# reEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27
# DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pv
# vinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9Ak
# vUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWK
# NsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2
# kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+
# c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep
# 8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+Dvk
# txW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1Zyvg
# DbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/
# 2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZpIHW
# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
# Hm5TaGllbGQgVFNTIEVTTjoyRDFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA5VHBr4h00EN7
# jUdQ33SE+qbk/8CggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDANBgkqhkiG9w0BAQsFAAIFAOyrZTwwIhgPMjAyNTEwMjgxNTUyMjhaGA8yMDI1
# MTAyOTE1NTIyOFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA7KtlPAIBADAHAgEA
# AgIufTAHAgEAAgIS5TAKAgUA7Ky2vAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor
# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUA
# A4IBAQAoDSwy4FH2x1Fp6oANeZzMVaRMkbh5MBBZR+JAjhgvbCvp2W5qyKXPKmLU
# wcBB5e4H3lM5Uqsb4sfauKeuqnKK2nEsss0LMb55dmtZqEr3Trol7ET4VbZtXN/U
# rev/DPJTVlRw2n1U8zVSmGQ9DMiF2TY46zkDs1oYmzfQKuxUZiLVqHk1TZV3Obt6
# e9ptR1OOoNZrmKVXOWlr8n7UDJ5FYce+c/T3UN0KtrfhHIwgnn5iHUkiQHYxYnOv
# Fs3Z4m+80BF+6fA6Y2ex0ketJ3wN6RVo+FFDn5fBs4Zgt+ykJO6LnDX5kthOr8e2
# pcYll29ld2kKwelNgN3XAAk68KuMMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAIS0QgGPMoYT6oAAQAAAhIwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgt4CRi4ltQf9zbnCeTr/J6qnY2h/tICxV+1QBcmcaCHcwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCBz+X5GvO7WngknH4BZeYU+BzBL1Jy5oJ8wVlTN
# IxfYgzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAC
# EtEIBjzKGE+qAAEAAAISMCIEILt8kTw+7ut0ySxqpAQeNssK4NV/55MTYdTKMChs
# 1LLvMA0GCSqGSIb3DQEBCwUABIICAGhs8xcKlOQfOlZzriLa2kHfIaKhQfkgQbsq
# g+HZNJ/5M5qZHgBeJEQfu5XPoSeYRtcjmZgVTb3PIxecClRjXNWo8EldRcTUSC6w
# X5jYkQggDDQg0RWQvTm2DeEbZSmtY6vG+a8jzavTexTHa3oAfM6XGXE0XoGGAVsX
# ZHqxqbzymoXVCDAO+OiAEc3irimXGItNAWlvZ7Qzf4wkDUVq9ALtZRcw46qla2/K
# 0F6pLn7hsCsg4Pih9hdKTgD52BPa0TIRPq/Fuymxn1hy1cUMv0VeEDAvq/iw7HOn
# yQMRksrNHDsJAdaA5HNkMbQIiV7wsxMjL7eCXSepS11lZeoS48mBG/StwKVHoN+W
# edWBpWFAVgNs2eJKsp5j0f1grHcxhP+ag9y0h209ht7HPPkZwWX+C9SCT2wpNI0p
# M0C3hz6dyE5bdq2bblKEJL4CSeKfFdL3U2bNq6K3jr/Snx5sVNA/KR3k9Rpwcn2D
# XvvZBpdiFxQoq28+kKpV/VlyJ6UWsqohoI4AeFNJISFlueykHIO/sEKL90WQ/kjJ
# dYwhbxhIDScNP7jI0Uzwrk26sQnpdYmyCNzApaCx3fbAmU8G9frHnWgKXAnJjwdp
# 8i0nAxwJJs20GTs6GerhR4oI0pgi/98NJVCPBT1BQUxDniUCpxk5rOubGTXHMrZz
# k5A4vDwH
# SIG # End signature block