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

#################################################################
# #
# 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
}


function Show-ValidatorDetails {
    <#
    .SYNOPSIS
        Displays detailed information for a set of validators with color-coded output.
    #>

    param (
        [Parameter(Mandatory = $true)]
        [object[]]$Validators,

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

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

        [Parameter(Mandatory = $true)]
        [System.ConsoleColor]$Color
    )

    Write-Host "`n====[ $Label ]====" -ForegroundColor $Color
    Write-Host "-----------------------------------------------" -ForegroundColor $Color

    foreach ($validator in $Validators) {
        Write-Host "`n[$Prefix] " -ForegroundColor $Color -NoNewline
        Write-Host "$($validator.Name)" -ForegroundColor White
        Write-Host "Target Resource: " -ForegroundColor Gray -NoNewline
        Write-Host "$($validator.TargetResourceID)" -ForegroundColor White
        Write-Host "Timestamp: " -ForegroundColor Gray -NoNewline
        Write-Host "$($validator.Timestamp)" -ForegroundColor White

        if ($validator.Description) {
            Write-Host "Description: " -ForegroundColor Gray -NoNewline
            Write-Host "$($validator.Description)" -ForegroundColor White
        }

        if ($validator.Remediation) {
            Write-Host "Remediation: " -ForegroundColor Gray -NoNewline
            Write-Host "$($validator.Remediation)" -ForegroundColor Cyan
        }

        if ($validator.AdditionalData) {
            Write-Host "Details: " -ForegroundColor Gray
            if ($validator.AdditionalData -is [string]) {
                Write-Host " $($validator.AdditionalData)" -ForegroundColor Yellow
            } else {
                # Handle object/hashtable additional data
                $validator.AdditionalData.PSObject.Properties | ForEach-Object {
                    Write-Host " $($_.Name): " -ForegroundColor Gray -NoNewline
                    Write-Host "$($_.Value)" -ForegroundColor Yellow
                }
            }
        }
        Write-Host "-----------------------------------------------" -ForegroundColor $Color
    }
}

function Show-AzsSupportEnvironmentValidatorSummary {
    <#
    .SYNOPSIS
        Retrieves and displays a comprehensive summary of environment validator results from the event log.
 
    .DESCRIPTION
        This function queries the AzStackHciEnvironmentChecker event log for validation results,
        filters to get the latest run results, and displays a summary of succeeded, warning, and failed validators.
        It shows color-coded counts for each status category and detailed information for failed and warning validators,
        including descriptions, remediation steps, and additional diagnostic data.
 
    .PARAMETER Concise
        Optional switch parameter. When specified, displays only the summary counts without detailed information
        for individual validators. Useful for quick status checks.
 
    .OUTPUTS
        None. This function displays formatted output to the console with color-coded information.
 
    .EXAMPLE
        Get-EnvironmentValidatorSummary
         
        Displays a comprehensive summary of the most recent environment validator run results, including:
        - Color-coded count summary (succeeded in green, warnings in yellow, failed in red)
        - Detailed information for each failed validator with description, remediation, and additional data
        - Detailed information for each warning validator with description, remediation, and additional data
        - Success message if all validators passed
 
    .EXAMPLE
        Get-EnvironmentValidatorSummary -Concise
         
        Displays only the summary counts without detailed validator information:
        - [SUCCEEDED] 25
        - [WARNING] 2
        - [FAILED] 1
 
    .NOTES
        - Queries event ID 17205 from the AzStackHciEnvironmentChecker event log
        - Automatically filters to show only the most recent validation run results
        - Uses validator name and target resource ID combination to identify unique validators
        - Displays results in a color-coded format for easy visual assessment
        - Failed validators are displayed with red headers and separators
        - Warning validators are displayed with yellow headers and separators
        - Remediation steps are highlighted in cyan for easy identification
        - Additional data objects are formatted as readable key-value pairs
    #>


    param (
        [Parameter(Mandatory = $false)]
        [switch]$Concise = $false
    )

    Trace-Output -Level:Verbose -Message "Starting Environment validator summary"

    $logName = "AzStackHciEnvironmentChecker"
    $eventID = 17205

    # Query the event log using FilterHashtable for efficient server-side filtering
    try {
        $events = Get-WinEvent -FilterHashtable @{LogName=$logName; Id=$eventID} -ErrorAction Stop
    }
    catch [Exception] {
        if ($_.Exception.Message -like "*No events were found*") {
            Write-Host "No environment validator events found in the '$logName' event log." -ForegroundColor Yellow
            Trace-Output -Level:Verbose -Message "No events found in '$logName' for event ID $eventID"
            return
        }
        Write-Host "Failed to query the '$logName' event log: $($_.Exception.Message)" -ForegroundColor Red
        Trace-Output -Level:Verbose -Message "Error querying event log '$logName': $($_.Exception.Message)"
        return
    }

    $results = [System.Collections.Generic.List[object]]::new()

    foreach ($environmentValidatorEvent in $events) {
        try {
            $result = $environmentValidatorEvent.Message | ConvertFrom-Json -ErrorAction Stop
            # Attach the event log's TimeCreated as a reliable timestamp for run detection
            $result | Add-Member -NotePropertyName '_EventTimeCreated' -NotePropertyValue $environmentValidatorEvent.TimeCreated -Force
            $results.Add($result)
        }
        catch {
            Trace-Output -Level:Verbose -Message "Skipping event with invalid JSON: $($_.Exception.Message)"
        }
    }

    if ($results.Count -eq 0) {
        Write-Host "No valid environment validator results found." -ForegroundColor Yellow
        Trace-Output -Level:Verbose -Message "No valid JSON results found in '$logName' events"
        return
    }

    # Sort by the event log's TimeCreated (reliable, never null) to identify the latest run.
    $results = $results | Sort-Object -Property _EventTimeCreated -Descending

    # Identify the latest run using gap-based detection.
    # Validators in a single run write results sequentially, so consecutive events have small
    # time gaps between them. A gap larger than the threshold signals a run boundary.
    # This works regardless of total run duration.
    $gapThresholdMinutes = 3
    $lastRun = [System.Collections.Generic.List[object]]::new()
    $lastRun.Add($results[0])

    for ($i = 1; $i -lt $results.Count; $i++) {
        $gap = ($results[$i - 1]._EventTimeCreated - $results[$i]._EventTimeCreated).TotalMinutes
        if ($gap -gt $gapThresholdMinutes) {
            break
        }
        $lastRun.Add($results[$i])
    }

    Trace-Output -Level:Verbose -Message "Latest run detected at $($results[0]._EventTimeCreated) with $($lastRun.Count) validators (gap threshold: $gapThresholdMinutes min)"

    $failedValidators = @($lastRun | Where-Object {$_.Status -eq 2})
    $succeededValidators = @($lastRun | Where-Object {$_.Status -eq 0})
    $warningValidators = @($lastRun | Where-Object {$_.Status -eq 1})


    Write-Host "====[ Environment Validators Summary ]====" -ForegroundColor White -BackgroundColor DarkGray

    Write-Host "[SUCCEEDED] $($succeededValidators.Count)" -ForegroundColor  Green
    Write-Host "[WARNING] $($warningValidators.Count)" -ForegroundColor Yellow
    Write-Host "[FAILED] $($failedValidators.Count)" -ForegroundColor Red

    # if the concise flag is true, don't show the details of failed validators
    if ($Concise -eq $true) {
        return
    }

    # Display failed validators
    if ($failedValidators.Count -gt 0) {
        Show-ValidatorDetails -Validators $failedValidators -Label "FAILED VALIDATORS" -Prefix "FAIL" -Color Red
    }

    # Display warning validators
    if ($warningValidators.Count -gt 0) {
        Show-ValidatorDetails -Validators $warningValidators -Label "WARNING VALIDATORS" -Prefix "WARN" -Color Yellow
    }

    # Display message if no failures or warnings
    if ($failedValidators.Count -eq 0 -and $warningValidators.Count -eq 0) {
        Write-Host "`n====[ ALL VALIDATORS PASSED ]====" -ForegroundColor Green
        Write-Host "No issues found in the latest environment validation run." -ForegroundColor Green
    }

    $message = "Finished Environment validator summary. [FAILED = $($failedValidators.Count)], [WARNING = $($warningValidators.Count)], [SUCCEEDED = $($succeededValidators.Count)]"
    Trace-Output -Level:Verbose -Message $message

}


function Show-AzsSupportSDNStateSummary {
<#
    .SYNOPSIS
        Checks the current status of SDN, including if FCNC is deployed and if SDN services are online.
 
    .DESCRIPTION
        This function checks the network controller configuration in ECE.
        Additionally, it checks the status of the SDN services.
 
    .OUTPUTS
        None. This function displays formatted output to the console with color-coded information.
 
    .EXAMPLE
        Show-AzsSupportSDNStateSummary
    #>

    
    Import-Module ECEClient -DisableNameChecking
    $eceClient = Create-ECEClusterServiceClient -Verbose:$false -ErrorAction Stop

    # Check for our FCNC fully deployed + MOC hydrated variable
    $FCNCCompleteFlag = $false
    $params = [xml] ($eceClient.GetCloudParameters().GetAwaiter().GetResult().CloudDefinitionAsXmlString)
    $SDNParams = $params.Parameters.Category | Where-Object { $_.Name -eq "SDNIntegration" }
    $eceNCConfig = $SDNParams.Parameter | Where-Object { $_.Name -eq "NetworkControllerConfiguration" }

    # ECE NC configuration pre-existed
    if ($eceNCConfig.Value) {
        $eceNCConfigJSON = ConvertFrom-Json -InputObject $eceNCConfig.Value
        $FCNCCompleteFlag = $eceNCConfigJSON.FCNCInstallFlag
    }

    # Check for SDN services
    
    try {
    
        $NcOwnerGroupNames = @(
                "ApiService",
                "ControllerService",
                "FirewallService",
                "FnmService",
                "GatewayManager",
                "SlbManagerService",
                "VSwitchService"
            )
        $ExpectedResourceCount = $NcOwnerGroupNames.Count

        $cluster = Get-Cluster
        $ncResources = Get-ClusterResource -Cluster $cluster |
                       Where-Object { $_.OwnerGroup -in $NcOwnerGroupNames }

        # Capture details as PSObjects (stable for formatting)
        $clusterResources = $ncResources | ForEach-Object {
            [pscustomobject]@{
                Name              = $_.Name
                OwnerGroup        = $_.OwnerGroup
                OwnerNode         = $_.OwnerNode
                State             = $_.State
                ResourceType      = $_.ResourceType
                StatusInformation = $_.StatusInformation
            }
        }

        $failedResources = $clusterResources | Where-Object { $_.State -ne 'Online' }
    }
    catch {
        $_ | Trace-Exception
        Trace-Output -Level:Error -Message "Failed to query cluster/SDN services." 
    }

    # Get NC URL
    try {
        $res = Get-NetworkControllerOnFailoverCluster -Verbose:$false 4>$null
        $NCURL = "https://$($res.RestCertificateSubjectName)/"
    }
    catch {
        $_ | Trace-Exception
        Trace-Output -Level:Error -Message "Failed to get SDN URL"
    }

    # Get NC services
    Write-Host ""

    Write-Host "========== SDN State Summary ==========" -ForegroundColor Green
    Write-Host ("FCNC installed + MOC hydrated : " + ($(if ($FCNCCompleteFlag) { "Yes" }else { "No" })))
    Write-Host ("NC URL : " + ($(if ($ncUrl) { $ncUrl }else { "(not found)" })))

    if ($failedResources.Count -gt 0) {
        Write-Host ""
        Write-Host "== Failed SDN resources:" -ForegroundColor Red
        Write-Host ($failedResources | Format-Table Name, OwnerGroup, OwnerNode, State, ResourceType -AutoSize | Out-String)
    }
    elseif ($FCNCCompleteFlag -eq $true) {
        Write-Host ""
        Write-Host "All SDN resources online." -ForegroundColor Green
    }

    if ($FCNCCompleteFlag -eq $true) {
        Write-Host ""
        Write-Host "SDN Services:" -ForegroundColor Cyan
        Write-Host ($clusterResources | Format-Table Name, OwnerGroup, OwnerNode, State, ResourceType -AutoSize | Out-String)
    }
}
# SIG # Begin signature block
# MIInRgYJKoZIhvcNAQcCoIInNzCCJzMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCChUN8N6PAKkYLr
# h6cAkoHV4viIjbLYKEpzEKR2jaCFYaCCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1
# OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8
# o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg
# 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4
# Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R
# X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk
# ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw
# HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg
# UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh
# MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9
# s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H
# VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3
# w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n
# 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs
# A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo
# Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb
# SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6
# 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z
# V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v
# 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs
# /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA
# AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow
# VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh
# emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h
# KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd
# M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp
# yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t
# Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5
# REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs
# 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK
# Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5
# pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW
# eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ
# 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI
# MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4
# NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh
# ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q
# hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU
# nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb
# H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z
# uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u
# vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW
# 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV
# DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10
# 1cY2L4A7GTQG1h32HHAvfQESWP0xghniMIIZ3gIBATBuMFcxCzAJBgNVBAYTAlVT
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w
# DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJupmGiM
# +FbQYvAfFbgR2kZObuu8dwfz2h9FHx46BZAFMEIGCisGAQQBgjcCAQwxNDAyoBSA
# EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w
# DQYJKoZIhvcNAQEBBQAEggEAFnhGqN6Yru6SQa+/+bAfdlx4CRtwY8J+nLRK+cKv
# IDTjrn/ifZ6lsmVpWIYsl9mnJEzDU5ACSME3BYhcyaD2RUgcTQB3jkOuUbPjD8GB
# zXDG+U5izx4ehCpfXvd6JJ2JMQTR5i6CL4pLT8W7PHsoFyDodJzM/othGx4LncZd
# H4d1d+ATizbKeAsP9lWZTRUXv9O1u4k59/hAEbMQ0PUIDBvqfUgzhZ6yoCQajfWp
# F9lz3djz+iRQwaWxyYyEmG0axwPfWnNHrvfQXtpODA4S+ejinXzCzHUyYX/D38Wc
# upYCsMJXLnAPF1iBuKU/z0tQ/2WMegtFn2xLdvr0NxPBdaGCF5QwgheQBgorBgEE
# AYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUD
# BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD
# ATAxMA0GCWCGSAFlAwQCAQUABCC4jcpyL90VdYAobWoZekPnJTPHek7J3GVeOYb2
# lmOYHQIGaefVQxXGGBMyMDI2MDUwNTE2MTM0MC4xMjZaMASAAgH0oIHRpIHOMIHL
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN
# aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT
# UyBFU046OTYwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgITMwAAAiY1tD5nQ5P2HwABAAAC
# JjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe
# Fw0yNjAyMTkxOTQwMDJaFw0yNzA1MTcxOTQwMDJaMIHLMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj
# YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0wNUUw
# LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC//w+ZZIL5RFFpVI8D3ZyuNu8I
# zcAEOD30OLYjh337rXjcrIlOSzpJc4ZeUxEyli6x6F6zm4NR8dbPb9diDp/hOUzH
# WGxiA1Z3RXKBb/4F/ojyvN43SEGWqSfVc3I3BlsYT35ecVAJ9kVf90YOv29tFjJB
# BZkYvrT/DwwyRLscOyP4p+9/lyJjD+ULs3YXBhVrfZ+MbQB+BYKLqRvBKbj/wR9a
# kNrMxQINoGaD5jZO/N/nSsmG2P1zv/cv4gSoMBnWeQIBkjd2I5w1DeXupp2vSiNm
# R5sA2ZkBK3yiQWaJvRxODlkfiyHk9Mkk/TrYTjmjPCbhe+uqhHNRy8UlbOvWsCq0
# tRtUykHv39DgqAfJNrE8OSt835rBzDprrcAhwmgfhoVi4AKeqwikY0nUa48K0Qy8
# 0XT4fiEA3ExEZNaRFo9Nq/GwbfgqKqGmc9xhKuRFcjtua4KHZvnAvpWgEFSOCkov
# Xs/BcLnkEHM9xZ8iUag5CyhNqXYYE/z0pcXdYaNIkQ68EWmuvLm7g9oofV2vOm5G
# VNoghnkWG6nGPo/JwEgmA9oSS0EfvFRMWPA/gpSvF3shArKHnaEpVSSi3DNbyiuY
# iEs9Ko0IkZc8xKFeQRaqGRxrB+2r/7B3X81Tps99KhFwg+wD87od22F2MUg1x7tw
# t3gaVnFk0IZIwUPCGwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFF3hn9fYJN2Y/Z9L
# VbBPIxAzXHsQMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud
# HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js
# L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr
# BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw
# MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw
# DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQA2Ux0tr9sYCjsq0FRy
# iVpx15OurNXv6Qk7iX+ArVPlz3w4tqjcTNm1dt3tTua2wJMpJhPH8n7UXhmT98d5
# Du44Ll4adnse4SQfVg3QL6aRkXHnJUn8y9iftB/Py22n9xnwPFfj3QlDOSgLuHle
# u97U0iH2ZaluYabWXJihdiYpK8cPHFlqZOAiot0+GD8dP+RMuvpxt/F2LmYelpoZ
# wriiFOUmlxEUV7xJHyZZlDquskeyuq01DTv91N4qM8cfPPhl/2pc4HeMf/nd2Hou
# ifJbDQFNd4WPhLzn0Sy3u1Zh3+S3tjQdqN+dyw60RaV+RXCoOLgFZ3MAg/GoDl+f
# vb5hy/1a71ctX8wEad1Pf6def2pqfl3wFc++hkF8DXXTZofJN4YVaN3InwbAGQDD
# kNK4lqecCixxmSKwidPynGeE5OtvNoK1pkLsm/i8F1RjGczZ/kSF2VDkqG866iQ+
# jVbGOQ6Du3eyyFcFKZoDJ4B5mEAS9aT2SKqllLeybOboH6r67siR5B/2Hnu7+KYu
# YZy0BEadtA6ngG4cnSR9JsrkhhsKmb11ujqwgJyNx92MsoGGwNgN1aI0QID8CsjC
# FwpfmMzlA44xHKYv3hmjxeqBS4uU5rQeiAnVgpJeaVGKm/lzPDtnppGV+7XhRp5b
# 1ZxT/Z7Xxc+I7H7/jCtQDZoaZTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA
# AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX
# 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q
# UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d
# q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN
# pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k
# rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d
# Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS
# Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8
# QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm
# gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF
# ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID
# AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU
# KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1
# GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0
# bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA
# QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL
# j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p
# Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU
# tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN
# 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU
# 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5
# KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy
# qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6
# 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE
# AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp
# AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd
# FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb
# atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd
# VTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjk2MDAtMDVFMC1E
# OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw
# BwYFKw4DAhoDFQCi/fMxFtkqr7XMXdsRyWU0lSKHZ6CBgzCBgKR+MHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7aQd8TAiGA8y
# MDI2MDUwNTA3NDI0MVoYDzIwMjYwNTA2MDc0MjQxWjB0MDoGCisGAQQBhFkKBAEx
# LDAqMAoCBQDtpB3xAgEAMAcCAQACAhJLMAcCAQACAhKcMAoCBQDtpW9xAgEAMDYG
# CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEA
# AgMBhqAwDQYJKoZIhvcNAQELBQADggEBAFnJjzVrG5nT7DG/r1Q77KTFria3OgN3
# Ugih2NxmGKfv4rW+veDNE0NjKXrJoHSjPIhGsWQPN9CBQdfRXaJcpsO2TaH3Rsl7
# H5NdzpvzQHauB2xr6ckDCvhuKBuBjkfSc/9T1X+Aofinz6bRccCSxLfWPLrgpLCY
# n0K3U0toQdw8r7AfPLFyAT84V5Lik2b7EvSCW0FVQ4+QhdoNnSjTWcNjua3I0yqY
# N8eKUiTRqqo9MJ8vaelW+5xiqqRIJjw7ByGxgkY7UvsM83dCOxq+SSqKMZVbbMWX
# 6/Ty62+IWcN4RJaJfCVwDVWHJGbv+WjWbesenQyIGEIH4M1OjkIDjeExggQNMIIE
# CQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiY1tD5n
# Q5P2HwABAAACJjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqG
# SIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDa73THgup4T48JXm7XUjwdXLc3vA9e
# B7OQKiFq3hFupDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMwyXGFnTNsZ
# RBrs6GN/BbV0okaNP3VBYqLFjUsFnbgqMIGYMIGApH4wfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAImNbQ+Z0OT9h8AAQAAAiYwIgQgObvbRQZ6vZ90
# OhEpTqG28nY445w6hAAzTbZ7A8jNJ9cwDQYJKoZIhvcNAQELBQAEggIAuQ1pvmMr
# 0iaUg+/+zecDFMxuHl7F2z8PcRY1h2wR3jliS2aItQELqmO3vT5R8KKyEpfL7mCZ
# eqNqPdDykniL4+nCsh8+W7G843v2uSIMZFHcdvcu8ahvglzBTvIrZkKW7xuLZh8w
# UBUOXH1PFkRMRaebHC5P5LM0Pt7f2zyhumFGe2FH/+4NKZwxd25zF3urNtxHj2oD
# kBf5r7VDI+WbR3l0O200oMGnn8G3pC9fT3/2B5VP0+lZY+HWuPGbRMIo6xajE4LX
# FoxsyTSf4+gG7j7ATCSieTt9plnOMxWsmvR8mEZ1PlVd3O0LhgPYi0ETkBi1omIE
# dchDx7XBoMKG3yS/7+I+YZ0SC1kJqmAOoanWpmEKVHtLixvkrh8JfMdFLXa39Owr
# Crwzz4V5LinWZ4IzmPfDGijcnl8WrYQzPZaLy7VjTitJivotL4neaYAhFdNA1C6G
# 0ou104qA2OzZriGPqJS9r1ORWbPs3GNlsBGtHaN4QfqSEi/xCz4wmKaAObH35JrA
# 23wLL+wpudbw4Sfz7TQ9G/TSfpy25ELtLJWILB3FQKpcRAQU0WSsUtC07K9N/Kgh
# 5O5u87tgW1j7Y9WB8IYCSFLdhLJyNxGE47nNe58T2zaWiM239WyrvnzGAlY1r20J
# i5AmQJd6AysnqYWlm7X77XMFFkvAtNjmI+A=
# SIG # End signature block