modules/AzStack.Network/AzStack.Network.psm1

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

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

Import-LocalizedData -BindingVariable 'msg' -BaseDirectory "$PSScriptRoot\locale" -UICulture (Get-Culture)

Import-Module $PSScriptRoot\..\AzStack.Utilities\AzStack.Utilities.psm1 -WarningAction SilentlyContinue
Import-Module $PSScriptRoot\..\AzStack.Common\AzStack.Common.psm1 -WarningAction SilentlyContinue

# Import SdnDiagnostics module if not already imported
if (-not (Get-Module -Name 'SdnDiagnostics')) {
    $sdnDiagModule = Get-Item -Path "$PSScriptRoot\..\..\packages\SdnDiagnostics.*\SdnDiagnostics.psd1" -ErrorAction SilentlyContinue
    if ($sdnDiagModule) {
        Import-Module $sdnDiagModule.FullName
    } else {
        Trace-Output -Level:Error -Message "SdnDiagnostics module not found at the specified path."
    }
}

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

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

function Set-AzsSupportNetworkATCIntentApplyWithTracing {
    <#
    .SYNOPSIS
        Applies a Network ATC intent with tracing enabled to capture diagnostic information.
 
    .DESCRIPTION
        This function enables Network ATC tracing, applies a retry state to a specified network intent,
        waits for the intent to complete, and then captures the trace data. If the intent fails to apply
        successfully, it optionally collects additional diagnostic data.
 
    .PARAMETER IntentName
        The name of the Network ATC intent to apply. This intent must already exist.
 
    .PARAMETER OutputDirectory
        Optional. The directory where trace files and diagnostic data will be saved.
 
    .EXAMPLE
        Set-AzsSupportNetworkATCIntentApplyWithTracing -IntentName "Compute_Management"
        Applies the "Compute_Management" intent with tracing enabled and saves output to default directory.
 
    .EXAMPLE
        Set-AzsSupportNetworkATCIntentApplyWithTracing -IntentName "Storage" -OutputDirectory "C:\Traces"
        Applies the "Storage" intent with tracing enabled and saves output to C:\Traces.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$IntentName,

        [Parameter(Mandatory = $false)]
        [string]$OutputDirectory = "$(Get-AzsSupportWorkingDirectory)\HostNetwork"
    )

    $resultObject = [PSCustomObject]@{
        IntentName = $IntentName
        IntentConfigStatus = $null
        IntentProvisioningStatus = $null
        OutputDirectory = $null
        TraceFilePath = $null
        AdditionalDiagnosticData = $null
        OccurrenceTimeUtc = (Get-Date).ToUniversalTime().ToString("o")
        Result = $null
    }

    $traceFilePath = "C:\Windows\NetworkATCTrace.etl" # NetworkATC always writes to this path

    # Validate that the intent exists
    $existingIntent = Get-NetIntent -Name $IntentName -ErrorAction SilentlyContinue
    if (-not $existingIntent) {
        Trace-Output -Level:Error -Message "NetIntent '$IntentName' not found."
        return
    }

    $OutputDirectory = Join-Path -Path $OutputDirectory -ChildPath "NetworkATC-IntentRetry_$($IntentName)"
    $null = Initialize-DataCollection -FilePath $OutputDirectory -MinimumMB 500

    # Cleanup existing trace file, if it exists
    if (Test-Path -Path $traceFilePath) {
        $confirm = Confirm-UserInput -Message "Trace file already exists at $traceFilePath. Would you like to delete the existing trace file?"
        if ($confirm) {
            Trace-Output -Level:Information -Message "Deleting existing trace file at $traceFilePath"
            Remove-Item -Path $traceFilePath -Force
        }
        else {
            Trace-Output -Level:Information -Message "Keeping existing trace file at $traceFilePath"
        }
    }

    $null = Set-NetIntentTracing -ComputerName $env:COMPUTERNAME *>&1
    Trace-Output -Level:Information -Message "Enabled network intent tracing"

    # Retry applying the intent
    Trace-Output -Level:Information -Message "Applying intent retry state for '$IntentName'"
    $null = Set-NetIntentRetryState -Name $IntentName -NodeName $env:COMPUTERNAME *>&1

    # Wait for the intent to be applied
    $iter = 1
    $maxIter = 12
    $sleepTime = 15
    Trace-Output -Level:Information -Message "Waiting for intent '$IntentName' to be applied. Maximum iterations: $maxIter, Sleep time between iterations: $sleepTime seconds."
    while ($iter -le $maxIter) {
        $status = Get-NetIntentStatus -Name $IntentName -ErrorAction SilentlyContinue | Where-Object { $_.Host -like $env:COMPUTERNAME }
        Trace-Output -Level:Information -Message "[$iter/$maxIter] Intent '$IntentName' ConfigurationStatus: $($status.ConfigurationStatus), ProvisioningStatus: $($status.ProvisioningStatus)"
        if ($status.ConfigurationStatus -eq "Success" -and $status.ProvisioningStatus -eq "Completed") {
            Trace-Output -Level:Information -Message "Intent '$IntentName' applied successfully."
            break
        }
        Start-Sleep -Seconds $sleepTime
        $iter++
    }

    $null = Set-NetIntentTracing -StopTracing *>&1
    Trace-Output -Level:Information -Message "Stopped network intent tracing"

    # Copy the trace file to output directory and convert to text
    if (Test-Path -Path $traceFilePath) {
        $destinationPath = Join-Path -Path $OutputDirectory -ChildPath "NetworkATC_Trace.etl"
        $txtOutputPath = Join-Path -Path $OutputDirectory -ChildPath "NetworkATC_Trace.txt"

        Trace-Output -Level:Verbose -Message "Copying trace file to output directory: $destinationPath"
        Copy-Item -Path $traceFilePath -Destination $destinationPath -Force

        Trace-Output -Level:Verbose -Message "Converting trace file to text format: $txtOutputPath"
        $null = netsh trace convert $destinationPath output=$txtOutputPath *>&1
    }

    # Summarize results
    $intentStatus = Get-NetIntentStatus -Name $IntentName -ErrorAction Ignore | Where-Object { $_.Host -like $env:COMPUTERNAME }
    Trace-Output -Level:Information -Message "Intent '$IntentName' Retry Result"

    $statusLevel = if ($intentStatus.ConfigurationStatus -eq "Success" -and $intentStatus.ProvisioningStatus -eq "Completed") { "Success" } else { "Warning" }
    $resultObject.IntentConfigStatus = $intentStatus.ConfigurationStatus
    $resultObject.IntentProvisioningStatus = $intentStatus.ProvisioningStatus
    $resultObject.OutputDirectory = $OutputDirectory
    $resultObject.TraceFilePath = $txtOutputPath
    $resultObject.Result = $statusLevel

    # If the intent was not applied successfully, collect the additional support data
    if ($intentStatus.ConfigurationStatus -ne "Success" -or $intentStatus.ProvisioningStatus -ne "Completed") {
        $confirm = Confirm-UserInput -Message "Would you like to collect additional Network ATC Diagnostic Data for this node?"
        if ($confirm) {
            $null = Get-AzsSupportHostNetworkDiagnosticData -OutputDirectory $OutputDirectory -SkipCompression
            $compressedData = Compress-AzsSupportArchive -Path $OutputDirectory -Destination "$OutputDirectory.zip"

            Show-UploadInstruction -DataType 'Network ATC Diagnostic Data' -SourcePath $compressedData.FullName
            $resultObject.AdditionalDiagnosticData = $compressedData.FullName
        }
    }
    else {
        Trace-Output -Level:Success -Message "Intent '$IntentName' applied successfully. No additional diagnostic data is needed."
    }

    return $resultObject
}

function Get-AzsSupportHostNetworkDiagnosticData {
    <#
    .SYNOPSIS
        Collects comprehensive network diagnostic data from the local host.
 
    .DESCRIPTION
        This function gathers extensive network diagnostic information including Network ATC configuration,
        Windows Event Logs, cluster configuration, network adapter settings, and host configuration.
        The collected data is saved to JSON files and optionally compressed into a ZIP archive.
 
    .PARAMETER FilePrefix
        Optional. A prefix to add to the diagnostic data folder name for easier identification.
 
    .PARAMETER OutputDirectory
        Optional. The directory where diagnostic data will be saved.
 
    .PARAMETER SkipCompression
        Optional. If specified, the diagnostic data will not be compressed into a ZIP file.
        By default, a ZIP archive is created for easier transport.
 
    .EXAMPLE
        Get-AzsSupportHostNetworkDiagnosticData
        Collects diagnostic data to the default Network folder and creates a ZIP archive.
 
    .EXAMPLE
        Get-AzsSupportHostNetworkDiagnosticData -FilePrefix "Issue123" -OutputDirectory "C:\Diagnostics"
        Collects diagnostic data with "Issue123" prefix to C:\Diagnostics and creates a ZIP archive.
 
    .EXAMPLE
        Get-AzsSupportHostNetworkDiagnosticData -SkipCompression
        Collects diagnostic data without creating a ZIP archive.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$FilePrefix = $null,

        [Parameter(Mandatory = $false)]
        [string]$OutputDirectory = "$(Get-AzsSupportWorkingDirectory)\HostNetwork",

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

    $winEventsToExport = @(
        "System",
        "Application",
        "Microsoft-Windows-Networking-NetworkAtc/Operational",
        "Microsoft-Windows-Networking-NetworkAtc/Admin",
        "Microsoft-Windows-FailoverClustering/Operational",
        "AzStackHciEnvironmentChecker"
    )

    $diagPath = Join-Path -Path $OutputDirectory -ChildPath "DiagnosticData"
    $null = Initialize-DataCollection -FilePath $diagPath -MinimumMB 500

    # Collect Network ATC Configuration
    Invoke-CommandToJSON -Command { Get-NetIntent } -OutputDirectory $diagPath -FileName "Get-NetIntent.json"
    Invoke-CommandToJSON -Command { Get-NetIntentStatus } -OutputDirectory $diagPath -FileName "Get-NetIntentStatus.json"
    Invoke-CommandToJSON -Command { Get-NetIntent -GlobalOverrides } -OutputDirectory $diagPath -FileName "Get-NetIntent-GlobalOverrides.json"
    Invoke-CommandToJSON -Command { Get-NetIntentAllGoalStates } -OutputDirectory $diagPath -FileName "Get-NetIntentAllGoalStates.json"

    # Collect Cluster Configuration
    Invoke-CommandToJSON -Command { Get-Cluster } -OutputDirectory $diagPath -FileName "Get-Cluster.json"
    Invoke-CommandToJSON -Command { Get-ClusterNode } -OutputDirectory $diagPath -FileName "Get-ClusterNode.json"
    Invoke-CommandToJSON -Command { Get-ClusterResource } -OutputDirectory $diagPath -FileName "Get-ClusterResource.json"
    Invoke-CommandToJSON -Command { Get-ClusterNetwork } -OutputDirectory $diagPath -FileName "Get-ClusterNetwork.json"
    Invoke-CommandToJSON -Command { Get-ClusterResourceType "Virtual Machine" | Get-ClusterParameter -Name "migration*" } -OutputDirectory $diagPath -FileName "Get-ClusterResourceType-Migration.json"

    # Collect Network Configuration
    Invoke-CommandToJSON -Command { Get-NetIPAddress } -OutputDirectory $diagPath -FileName "Get-NetIPAddress.json"
    Invoke-CommandToJSON -Command { Get-NetIPConfiguration 4>$null } -OutputDirectory $diagPath -FileName "Get-NetIPConfiguration.json"
    Invoke-CommandToJSON -Command { Get-NetAdapter } -OutputDirectory $diagPath -FileName "Get-NetAdapter.json"
    Invoke-CommandToJSON -Command { Get-NetAdapterAdvancedProperty } -OutputDirectory $diagPath -FileName "Get-NetAdapterAdvancedProperty.json"
    Invoke-CommandToJSON -Command { Get-VMSwitch } -OutputDirectory $diagPath -FileName "Get-VMSwitch.json"
    Invoke-CommandToJSON -Command { Get-VMSwitch | Get-VMSwitchTeam -ErrorAction SilentlyContinue } -OutputDirectory $diagPath -FileName "Get-VMSwitchTeam.json"
    Invoke-CommandToJSON -Command { Get-VMNetworkAdapter -ManagementOS } -OutputDirectory $diagPath -FileName "Get-VMNetworkAdapter-ManagementOS.json"
    Invoke-CommandToJSON -Command { Get-VMNetworkAdapterVlan -ManagementOS } -OutputDirectory $diagPath -FileName "Get-VMNetworkAdapterVlan-ManagementOS.json"
    Invoke-CommandToJSON -Command { Get-VMNetworkAdapterIsolation -ManagementOS } -OutputDirectory $diagPath -FileName "Get-VMNetworkAdapterIsolation-ManagementOS.json"

    # Collect Host Configuration
    Invoke-CommandToJSON -Command { Get-ComputerInfo } -OutputDirectory $diagPath -FileName "Get-ComputerInfo.json"
    Invoke-CommandToJSON -Command { Get-HotFix } -OutputDirectory $diagPath -FileName "Get-HotFix.json"

    # collect event logs
    $exportResults = Export-AzsSupportEventLog -LogName $winEventsToExport -Destination $diagPath
    $exportResults | Export-ObjectToFile -OutputDirectory $diagPath -FileName "Export-AzsSupportEventLog_Summary.json"

    # compress the results unless operator has request to skip compression
    if (!$SkipCompression) {
        $result = Compress-AzsSupportArchive -Path $diagPath -Destination "$diagPath.zip"
    }
    else {
        $result = Get-Item -Path $diagPath
    }

    return $result
}

# SIG # Begin signature block
# MIIoUgYJKoZIhvcNAQcCoIIoQzCCKD8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAWgFBtGzGHMRMw
# dO91g/efmT6oon6bEiPlT3Lqs/ybsqCCDYUwggYDMIID66ADAgECAhMzAAAEhJji
# 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
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEICKD
# 6HCBJPvn7jKsyZxJVHtYMB3nn1SOckMxNDBZXpOGMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEANC/QkDJFJdJxNZ+MMi4UuyEu613p+zSgw5PN
# nEaiku/mGKcUe4iFxi8ETcwAPW3ZPMcRVLvIEcqLJ+lAVqakgj/MB+GuC512jhpU
# eKKCspS2GfQNZXWUhxkXDEoLnGgz4oR7yPzM0uEuAomwIvYSyYnCIB15gJPUAMfn
# pE4CQwdfOcB9SpQmjE5Olu+P9/oyV/s707hgnyChGKFH7WGJgfjge0pYV+t5GTts
# uQxP0B8kzItKfStX8XnR19QadD7yqO4xbojRcBLViU9gF3zKVPaMNaxvOLKZk0r0
# I5jLEh5OdXo686EFeeq5uJs6L/Zr8bJM8cvTMIHAkGSyk83oCaGCF60wghepBgor
# BgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBKtXb9V2UI8HT8OXR2C5tfVqNZo39WHmBW
# ZM3wnHxg3wIGaPedHa0kGBMyMDI1MTAyODIwNDAwMy43NzlaMASAAgH0oIHZpIHW
# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
# Hm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAACF3H7
# LqWvAR3qAAEAAAIXMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyM1oXDTI2MTExMzE4NDgyM1owgdMxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jv
# c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVs
# ZCBUU1MgRVNOOjUyMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# wM82sEw+39vYR7iGCIFDnYNhRM+BzF2AYiq5dUpZpJFPRjCcipQ6RUbI+RAYNRAp
# Exx5ygrXbaWtuwvqsqAVSWbU/W6fecujjILkPqn9pngtWRkfQgbYgvaXALl6PY2y
# OH9f72MD+6AyxQenSpAMdUzY/Qk/jtjsHdFXVBe+tshlIkSJ3GZw8VVKqTg3GZEl
# ztwbJWNtrhBEvhf6anxMegQMJP7tO8/BJ7ITs4/AV3D2bv8eHk81Y+fOmQ8mQ61W
# Lq2wItvlzIT5bzelK9LvEycf5x1lXxAwEw5a7dpS+CKTanhtv+Q2mwebAybjf9io
# 4k48stTaq1rtcrOiDwddqVm1S9e8h1TszXFzjLLvE9EmjnNfIewsY+RChUaHnY4F
# FwwJEnEv/JS76oHT0oGdy7+J60fGOl7A1UoUyAkhpb2Bja+SwSIiHbQ4FDyJiLlZ
# 6drZZ84MoJ852JSxM0hBjGO6FZlPO8iuNyk680Di8VnbSNpIdJN+DhlepeTUMBDH
# qCmd0mVWRWZPm1pvgty93asNt/Ng6o4m2dnooWOdM3yKsJaWjyHqic9gfTrZBM+P
# CXqeTaO1oEiaQ+h4w0nHVdV+XSvI2m1yN4iibqjm5HPaAO3OJ+OmNLftNVmr4Z6U
# 2T6pIcLBysoKcDUvCqycXj4C/+n1KFBpDGdDMw9gmu8CAwEAAaOCAUkwggFFMB0G
# A1UdDgQWBBRQrN9jlwNOoeE5ZQqnF5x8S1bJQzAfBgNVHSMEGDAWgBSfpxVdAF5i
# XYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
# JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRp
# bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1Ud
# JQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
# AAOCAgEARmgFdhB7xIAIHEEg5I/5S+gx67aR6RiW8ZAwtE3mz8o0dyn+pIP+lidN
# R1IKQQ0r+RjYgI9cZ6mbvAyvh3e2q/BV8rjHE3ud9PyYyq32euFgdZ3vX4b5QXeP
# WlpBAYrdziR27rHz6WwpH5dZsSypbXDBbQkWkNl6g82yTy3AbBbKDXBdzxZsEaua
# OplatK7Er4dhglKBex8JQ2dMSkSZweCNDXqd9r/9W2VdRZsDJKP/Xc4UyQlVsboB
# otKtYESXFkjwR1HVsH+Q0C69/N5CP/Tq3YgI1ub4b9+3MJFKWhJXCcJGFZkcLwUm
# YwoFg1XLo7DLJdGjrIH1jsI2NFXJFQHef6AdRe1ERvYQeqtyrBvxIvR+P/83FNYy
# zx04inUT9TF2AwTOuqCC6Z67oNwR4pEEJyAIEREvkdhjjfWcgsk/nGTlfahvNY/S
# OHrNRKo49KDlccNzRCJQyQ+D59r7/qebNSyQPTfwI9++jEY0Q/UWKVNLhio55GYB
# seJ99s7NzkdxOr9Uftp597HEovbA69qGlZ3OpUE3H1RBGDVp/FvM2uXTum8LrMkP
# Xx5Ap/kbPASsC9ju9oMCe2IEXO2SeD1aD3IqvAOdHFKHg1vpbPUQSWb6g2xfBV30
# wFcqaPYgzcbxPWPyZqK+S8l7zw64aO5hmJ7eQwoMfTu0Vay6r48wggdxMIIFWaAD
# 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
# Hm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAabKAFaKt2haU
# dqkHfFYzAzfgSMuggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDANBgkqhkiG9w0BAQsFAAIFAOyrVXowIhgPMjAyNTEwMjgxNDQ1MTRaGA8yMDI1
# MTAyOTE0NDUxNFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA7KtVegIBADAHAgEA
# AgICJjAHAgEAAgISOjAKAgUA7Kym+gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor
# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUA
# A4IBAQCFqHNBTRfP+zUwUnQKUc8QFsWkUD893J6uL0HNJq4Gsu0PhSrXW6/oDN2p
# IdPT5zVvgcRa9rnTu8zAAoV7v/sU3AAInJrSYvTXCvRyrfNqe/rDGJRyiE3m2GN/
# HvoiRQpdIZ9KsLDAvZ2nl2Tsm3FaUT+WV4HhgAzZ8V2tNHBIoRlY4Pkv3QusquiZ
# eGip2m/oSxVvGPUVNP9e9XVeVa5Kh/csPC7RKxL3boy9uSAHUsq45qLXk7TVU0w8
# 54NB3nPpRZJKV9pmV1UoAoKxV+g8KSlAS0ceyDBwpdQJbapKMF+AiEeWUldyAOSn
# 7E2/DXl4QlTGrvS6y4RcuYN9M3sUMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAIXcfsupa8BHeoAAQAAAhcwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgQeBkUBE8zmH8h0km8Cnu49+cOdqqZkDFuRxIg9+PXSowgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCDQ8lBgPl23yZ0SzUSt5phOIegHPywrkNwevxe2
# k+RaWzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAC
# F3H7LqWvAR3qAAEAAAIXMCIEIIP42c6AjaR2eEAWNp2RinULnCtTAy329lRNrmIu
# 71RXMA0GCSqGSIb3DQEBCwUABIICAB/vad6RyNxDcxOBumo9ADVCamCpYbTJ5WbB
# /kg3QfuQOOhEDEf0xDuLA56XF00yttcJKTQF45PMb/i54w+urSAy8LFdpwtajNEC
# AkOrqvGgDKAEEwtfQqjxWNq3FQ0LtLgvgjXiLSphuwIlWLYJQEC4WFfteKEpQrC5
# MJy4XBSVXnNwfIzu8xaBAx4FfMJkkU63BZwGQvkD8+PnkAG3V8qB9swDiOI5I3fw
# rpej9xxB/qFy1mgMj110jPxBn58DrQV695Fdsn//AZuVeL6YGDPF7ES+sTGPK6qY
# tJ9ldHTEZOuL7N2qa4MueiVlpUVvFEm+VmXxglTo7SfQ2segoYBLz33Ih4eKthiU
# MeYNHg+8JuDbjzE4bheGRQlx8MTcs1Ka+5Jo089O5PGiNQ/41xpEOsPChqe07SXw
# NQB1Y2XkugcSW73AL51P8YmQxpkkfe65tLnLY+imkBD4erAh0pK0eKdtZl2W2+io
# MlTy77wEOfNUK3PzhuFSI8bsdSPMtq79TzeF4mDw5VPZoIQSdxDRGRYgHwHT8xvc
# 9vPTc8Cz3UtP4J+aL7DGX8AEAVIOq2ZYOBQqBszPhWUulk4pX53EfeCX4OsUQldz
# /aXbxr6WtTtqLsBMFqX3eRk/V44WECaUpCVAVBJgz2LKUgSpG3OZlVJPsoGJk9yW
# hTK4MrmA
# SIG # End signature block