helpers/discovery/Setup-LoadMaster-Discovery.ps1

<#
.SYNOPSIS
    Kemp LoadMaster Discovery - Discover appliance, virtual services,
    sub-VS, and real servers, then optionally push to WhatsUp Gold.
 
.DESCRIPTION
    Interactive script that discovers LoadMaster infrastructure via the
    RESTful APIv2, then lets you choose what to do with the results:
 
      [1] Push monitors to WhatsUp Gold (creates devices + monitors)
      [2] Export discovery plan to JSON
      [3] Export discovery plan to CSV
      [4] Show full plan table in console
      [5] Generate HTML dashboard (live metrics)
      [6] Exit
      [7] Dashboard + Push to WUG
 
    Architecture (when pushed to WUG):
      [LoadMaster Device]
          |-- "LM Health [hostname]" (Active Monitor)
          |-- "LM CPU % [hostname]" (Perf Monitor)
          |-- "LM Active Conns [hostname]" (Perf Monitor)
          |-- "LM TPS [hostname]" (Perf Monitor)
      [VS Device: WebApp]
          |-- "LM VS Health [WebApp]" (Active Monitor)
          |-- "LM VS Active Conns [WebApp]" (Perf Monitor)
          |-- "LM VS Conns/sec [WebApp]" (Perf Monitor)
      [RS Device: 10.0.0.10:8080]
          |-- "LM RS Active Conns [10.0.0.10:8080 on WebApp]" (Perf Monitor)
          |-- "LM RS Connections [10.0.0.10:8080 on WebApp]" (Perf Monitor)
 
    Authentication:
      - API Key (recommended for WUG monitoring)
      - Basic Auth (bal:password) for standalone discovery
 
    First Run:
      1. Prompts for LoadMaster host, port, auth method
      2. Stores credentials in DPAPI vault
      3. Discovers appliance + VS + RS hierarchy
      4. Shows summary, then asks what to do
 
    Subsequent Runs:
      Loads credentials from vault automatically.
 
.PARAMETER Target
    LoadMaster host(s) - IP address or FQDN. Accepts multiple values.
 
.PARAMETER ApiPort
    LoadMaster API port. Default: 443.
 
.PARAMETER AuthMethod
    Authentication method: 'ApiKey' (default) or 'Password'.
 
.PARAMETER Action
    What to do with results. When specified, skips the interactive menu.
    Valid: PushToWUG, ExportJSON, ExportCSV, ShowTable, Dashboard, DashboardAndPush, None.
 
.PARAMETER WUGServer
    WhatsUp Gold server address. Default: 192.168.74.74.
 
.PARAMETER WUGCredential
    PSCredential for WhatsUp Gold admin login.
 
.PARAMETER OutputPath
    Directory for export files and dashboards.
 
.PARAMETER NonInteractive
    Suppress all prompts. Uses cached vault credentials.
 
.EXAMPLE
    .\Setup-LoadMaster-Discovery.ps1
    # Interactive mode.
 
.EXAMPLE
    .\Setup-LoadMaster-Discovery.ps1 -Target 10.0.0.5 -Action PushToWUG -WUGServer 192.168.74.74
 
.EXAMPLE
    .\Setup-LoadMaster-Discovery.ps1 -Target 10.0.0.5 -AuthMethod Password -Action Dashboard
 
.NOTES
    Author: Jason Alberino (jason@wug.ninja)
    Requires: LoadMaster firmware 7.2.50+ for APIv2 JSON support.
    Encoding: UTF-8 with BOM
#>

[CmdletBinding()]
param(
    [string[]]$Target,

    [int]$ApiPort = 443,

    [ValidateSet('ApiKey', 'Password')]
    [string]$AuthMethod = 'ApiKey',

    [ValidateSet('PushToWUG', 'ExportJSON', 'ExportCSV', 'ShowTable', 'Dashboard', 'DashboardAndPush', 'None')]
    [string]$Action,

    [string]$WUGServer = '192.168.74.74',

    [PSCredential]$WUGCredential,

    [string]$OutputPath,

    [switch]$NonInteractive
)

# --- Output directory ---------------------------------------------------------
if (-not $OutputPath) {
    if ($NonInteractive) {
        $OutputPath = Join-Path $env:LOCALAPPDATA 'WhatsUpGoldPS\DiscoveryHelpers\Output'
    } else {
        $OutputPath = $env:TEMP
    }
}
if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null }
$OutputDir = $OutputPath

# --- Load helpers -------------------------------------------------------------
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
. (Join-Path $scriptDir 'DiscoveryHelpers.ps1')
. (Join-Path $scriptDir 'DiscoveryProvider-LoadMaster.ps1')

# Load dynamic dashboard generator
$dynDashPath = Join-Path (Split-Path $scriptDir -Parent) 'reports\Export-DynamicDashboardHtml.ps1'
if (Test-Path $dynDashPath) { . $dynDashPath }

# ==============================================================================
# STEP 1: Gather target info
# ==============================================================================
Write-Host "=== Kemp LoadMaster Discovery ===" -ForegroundColor Cyan
Write-Host ""

# --- Resolve LoadMaster host(s) -----------------------------------------------
$DefaultHost = '10.0.0.1'
if ($Target) {
    $LMHosts = @($Target)
}
elseif ($NonInteractive) {
    Write-Error 'No -Target specified for non-interactive mode. Exiting.'
    return
}
else {
    Write-Host "Enter LoadMaster host(s) - IP address or FQDN." -ForegroundColor Cyan
    Write-Host "For multiple hosts, separate with commas." -ForegroundColor Gray
    $hostInput = Read-Host -Prompt "LoadMaster host(s)"
    if ([string]::IsNullOrWhiteSpace($hostInput)) {
        Write-Error 'No host provided. Exiting.'
        return
    }
    $LMHosts = @($hostInput -split '\s*,\s*' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}
if ($LMHosts.Count -eq 0) {
    Write-Error 'No valid host provided. Exiting.'
    return
}
Write-Host "Targets: $($LMHosts -join ', ')" -ForegroundColor Cyan
Write-Host ""

# --- Resolve port --------------------------------------------------------------
if (-not $PSBoundParameters.ContainsKey('ApiPort') -and -not $NonInteractive) {
    $portInput = Read-Host -Prompt "LoadMaster API port [default: $ApiPort]"
    if ($portInput -and $portInput -match '^\d+$') {
        $ApiPort = [int]$portInput
    }
}

# ==============================================================================
# STEP 2: Authentication (API Key OR bal:password)
# ==============================================================================
$LMCredential = $null
$LMApiKey = $null

$authChoice = if ($AuthMethod -eq 'Password') { '2' } else { '1' }
if (-not $PSBoundParameters.ContainsKey('AuthMethod') -and -not $NonInteractive) {
    Write-Host ""
    Write-Host "Authentication method:" -ForegroundColor Cyan
    Write-Host " [1] API Key (recommended for WUG monitoring)" -ForegroundColor White
    Write-Host " [2] bal:password (standalone discovery or Basic Auth in WUG)" -ForegroundColor White
    Write-Host ""
    $authChoice = Read-Host -Prompt "Choice [1/2, default: 1]"
}

if ($authChoice -eq '2') {
    # Basic Auth (bal:password) - vault-backed
    $pwVaultName = "LoadMaster.$($LMHosts[0]).Credential"
    $credSplat = @{ Name = $pwVaultName; CredType = 'PSCredential'; ProviderLabel = 'LoadMaster' }
    if ($NonInteractive) { $credSplat.NonInteractive = $true }
    elseif ($Action) { $credSplat.AutoUse = $true }
    $psCred = Resolve-DiscoveryCredential @credSplat
    if (-not $psCred) {
        Write-Error 'No LoadMaster credentials available. Exiting.'
        return
    }
    $LMCredential = $psCred
    Write-Host "Using bal:password auth for discovery." -ForegroundColor Green
}
else {
    # API Key auth (default)
    $vaultName = "LoadMaster.$($LMHosts[0]).ApiKey"
    $credSplat = @{ Name = $vaultName; CredType = 'BearerToken'; ProviderLabel = 'LoadMaster' }
    if ($NonInteractive) { $credSplat.NonInteractive = $true }
    elseif ($Action) { $credSplat.AutoUse = $true }
    $LMApiKey = Resolve-DiscoveryCredential @credSplat
    if (-not $LMApiKey) {
        Write-Error 'No LoadMaster API key. Exiting.'
        return
    }
}

# Build credential hashtable for Invoke-Discovery
$discoveryCred = @{}
if ($LMApiKey) {
    $discoveryCred['ApiKey'] = $LMApiKey
}
elseif ($LMCredential) {
    $discoveryCred['UserName'] = $LMCredential.UserName
    $discoveryCred['Password'] = $LMCredential.Password
}

# ==============================================================================
# STEP 3: Discover - query LoadMaster API
# ==============================================================================
Write-Host ""
Write-Host "Querying LoadMaster at $($LMHosts -join ', ')..." -ForegroundColor Cyan

$allPlan = @()
foreach ($lmHost in $LMHosts) {
    $plan = Invoke-Discovery -ProviderName 'LoadMaster' `
        -Target @($lmHost) `
        -ApiPort $ApiPort `
        -Credential $discoveryCred

    if ($plan -and $plan.Count -gt 0) {
        $allPlan += $plan
    }
}
$plan = $allPlan

if (-not $plan -or $plan.Count -eq 0) {
    Write-Warning "No items discovered. Check LoadMaster connectivity and credentials."
    return
}

# ==============================================================================
# STEP 4: Show the plan
# ==============================================================================

# Group items by device
$devicePlan = [ordered]@{}

foreach ($item in $plan) {
    $type = $item.Attributes['LoadMaster.DeviceType']
    switch ($type) {
        'Appliance' {
            $key  = "appliance:$($item.Attributes['LoadMaster.ApiHost'])"
            $name = $item.Attributes['LoadMaster.Hostname']
            $ip   = $item.Attributes['LoadMaster.ApiHost']
        }
        'VirtualService' {
            $vsIdx = $item.Attributes['LoadMaster.VSIndex']
            $key   = "vs:$($item.Attributes['LoadMaster.ApiHost']):${vsIdx}"
            $name  = if ($item.Attributes['LoadMaster.VSNickName']) {
                $item.Attributes['LoadMaster.VSNickName']
            } else {
                "$($item.Attributes['LoadMaster.VSAddress']):$($item.Attributes['LoadMaster.VSPort'])"
            }
            $ip = $item.Attributes['LoadMaster.VSAddress']
        }
        'SubVirtualService' {
            $subIdx = $item.Attributes['LoadMaster.VSIndex']
            $key    = "subvs:$($item.Attributes['LoadMaster.ApiHost']):${subIdx}"
            $name   = "SubVS-${subIdx}"
            $ip     = $item.Attributes['LoadMaster.ApiHost']
        }
        'RealServer' {
            $rsAddr = $item.Attributes['LoadMaster.RSAddress']
            $rsPort = $item.Attributes['LoadMaster.RSPort']
            $rsIdx  = $item.Attributes['LoadMaster.RSIndex']
            $parentVS = $item.Attributes['LoadMaster.ParentVSIndex']
            $key  = "rs:$($item.Attributes['LoadMaster.ApiHost']):vs${parentVS}:${rsIdx}"
            $name = "${rsAddr}:${rsPort}"
            $ip   = $rsAddr
        }
        default { continue }
    }
    if (-not $devicePlan.Contains($key)) {
        $devicePlan[$key] = @{
            Name  = $name
            IP    = $ip
            Type  = $type
            Attrs = $item.Attributes
            Items = [System.Collections.ArrayList]@()
        }
    }
    [void]$devicePlan[$key].Items.Add($item)
}

$appDevices  = @($devicePlan.Values | Where-Object { $_.Type -eq 'Appliance' })
$vsDevices   = @($devicePlan.Values | Where-Object { $_.Type -eq 'VirtualService' })
$subVSDevs   = @($devicePlan.Values | Where-Object { $_.Type -eq 'SubVirtualService' })
$rsDevices   = @($devicePlan.Values | Where-Object { $_.Type -eq 'RealServer' })

$activeTemplates = @($plan | Where-Object { $_.ItemType -eq 'ActiveMonitor' } | Select-Object -ExpandProperty Name -Unique)
$perfTemplates   = @($plan | Where-Object { $_.ItemType -eq 'PerformanceMonitor' } | Select-Object -ExpandProperty Name -Unique)

Write-Host ""
Write-Host "Discovery complete!" -ForegroundColor Green
Write-Host " Appliances: $($appDevices.Count)" -ForegroundColor White
Write-Host " Virtual Services: $($vsDevices.Count)" -ForegroundColor White
Write-Host " Sub-Virtual Services: $($subVSDevs.Count)" -ForegroundColor White
Write-Host " Real Servers: $($rsDevices.Count)" -ForegroundColor White
Write-Host " Total WUG devices: $($devicePlan.Count)" -ForegroundColor White
Write-Host ""
Write-Host " Active monitor templates: $($activeTemplates.Count) ($(@($plan | Where-Object { $_.ItemType -eq 'ActiveMonitor' }).Count) assignments)" -ForegroundColor White
Write-Host " Perf monitor templates: $($perfTemplates.Count) ($(@($plan | Where-Object { $_.ItemType -eq 'PerformanceMonitor' }).Count) assignments)" -ForegroundColor White
Write-Host " Total plan items: $($plan.Count)" -ForegroundColor White
Write-Host ""

# Per-device summary
$devicePlan.Values | Sort-Object @{E={$_.Type}}, @{E={$_.Name}} |
    ForEach-Object { [PSCustomObject]@{
        Device   = $_.Name
        Type     = $_.Type
        IP       = if ($_.IP) { $_.IP } else { '0.0.0.0' }
        Monitors = $_.Items.Count
    }} |
    Format-Table -AutoSize

# ==============================================================================
# STEP 5: Export or push to WUG
# ==============================================================================

$choice = $null
if ($Action) {
    switch ($Action) {
        'PushToWUG'      { $choice = '1' }
        'ExportJSON'     { $choice = '2' }
        'ExportCSV'      { $choice = '3' }
        'ShowTable'      { $choice = '4' }
        'Dashboard'      { $choice = '5' }
        'None'           { $choice = '6' }
        'DashboardAndPush' { $choice = '7' }
    }
}

if (-not $choice -and $NonInteractive) { $choice = '5' }

if (-not $choice) {
    Write-Host "What would you like to do?" -ForegroundColor Cyan
    Write-Host " [1] Push monitors to WhatsUp Gold"
    Write-Host " [2] Export plan to JSON file"
    Write-Host " [3] Export plan to CSV file"
    Write-Host " [4] Show full plan table"
    Write-Host " [5] Generate HTML dashboard (live metrics)"
    Write-Host " [6] Exit (do nothing)"
    Write-Host " [7] Dashboard + Push to WUG"
    Write-Host ""
    $choice = Read-Host -Prompt "Choice [1-7]"
}

# Handle DashboardAndPush
if ($choice -eq '7') {
    $actionsToRun = @('5', '1')
} else {
    $actionsToRun = @($choice)
}

foreach ($currentChoice in $actionsToRun) {
switch ($currentChoice) {
    '1' {
        # ==================================================================
        # PushToWUG
        # ==================================================================
        Write-Host "Loading WhatsUpGoldPS module..." -ForegroundColor Cyan
        try {
            $repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
            $repoPsd1 = Join-Path $repoRoot 'WhatsUpGoldPS.psd1'
            if (Test-Path $repoPsd1) {
                Import-Module $repoPsd1 -Force -ErrorAction Stop
            } else {
                Import-Module WhatsUpGoldPS -ErrorAction Stop
            }
        }
        catch {
            Write-Error "Could not load WhatsUpGoldPS module: $_"
            return
        }
        $apiResponsePath = Join-Path $PSScriptRoot '..\..\functions\Get-WUGAPIResponse.ps1'
        if (Test-Path $apiResponsePath) { . $apiResponsePath }

        if ($WUGCredential) {
            Connect-WUGServer -serverUri $WUGServer -Credential $WUGCredential -IgnoreSSLErrors
        } else {
            Connect-WUGServer -AutoConnect -IgnoreSSLErrors
        }

        $stats = @{
            HealthCreated = 0; HealthSkipped = 0; HealthFailed = 0
            PerfCreated   = 0; PerfSkipped   = 0; PerfFailed   = 0
            DevicesCreated = 0; DevicesFound = 0
            CredsAssigned = 0
        }
        $wugDeviceMap = @{}

        # ---- 1. Create/find REST API credential ----------------------------
        Write-Host ""
        Write-Host "Setting up LoadMaster REST API credential in WUG..." -ForegroundColor Cyan

        $credName = "LoadMaster REST API - $($LMHosts[0])"
        $lmCredId = $null

        # Search for existing
        try {
            $existingCreds = @(Get-WUGCredential -Type restapi -SearchValue $credName -View basic)
            if ($existingCreds.Count -eq 0) {
                $existingCreds = @(Get-WUGCredential -SearchValue $credName -View basic)
            }
            if ($existingCreds.Count -gt 0) {
                $matchCred = $existingCreds | Where-Object { $_.name -eq $credName } | Select-Object -First 1
                if ($matchCred) {
                    $lmCredId = $matchCred.id
                    Write-Host " Found existing credential '$credName' (ID: $lmCredId)" -ForegroundColor Green
                }
            }
        }
        catch { Write-Verbose "Credential search error: $_" }

        if (-not $lmCredId) {
            Write-Host " Creating credential '$credName'..." -ForegroundColor Yellow
            try {
                if ($LMApiKey) {
                    # API Key mode: store key as password, username is a placeholder
                    $credResult = Add-WUGCredential -Name $credName `
                        -Description "LoadMaster API Key (auto-created by discovery)" `
                        -Type restapi `
                        -RestApiUsername 'apikey' `
                        -RestApiPassword $LMApiKey `
                        -RestApiAuthType '0' `
                        -RestApiIgnoreCertErrors 'True'
                }
                else {
                    # Basic Auth mode: bal:password
                    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($LMCredential.Password)
                    $plainPw = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
                    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
                    $credResult = Add-WUGCredential -Name $credName `
                        -Description "LoadMaster Basic Auth (auto-created by discovery)" `
                        -Type restapi `
                        -RestApiUsername $LMCredential.UserName `
                        -RestApiPassword $plainPw `
                        -RestApiAuthType '0' `
                        -RestApiIgnoreCertErrors 'True'
                }
                if ($credResult) {
                    if ($credResult.PSObject.Properties['data']) {
                        $lmCredId = $credResult.data.idMap.resultId
                    } elseif ($credResult.PSObject.Properties['resourceId']) {
                        $lmCredId = $credResult.resourceId
                    } elseif ($credResult.PSObject.Properties['id']) {
                        $lmCredId = $credResult.id
                    }
                    if ($lmCredId) {
                        Write-Host " Created credential (ID: $lmCredId)" -ForegroundColor Green
                    }
                }
            }
            catch {
                Write-Verbose "Standard credential creation failed: $_"
            }

            # Fallback: PATCH template
            if (-not $lmCredId) {
                try {
                    $credUsername = if ($LMApiKey) { 'apikey' } else { $LMCredential.UserName }
                    $credPassword = if ($LMApiKey) { $LMApiKey } else {
                        $b = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($LMCredential.Password)
                        $p = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($b)
                        [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($b)
                        $p
                    }
                    $credTpl = @{
                        templateId   = 'lm_restapi_1'
                        name         = $credName
                        description  = 'LoadMaster REST API (auto-created by discovery)'
                        type         = 'restapi'
                        propertyBags = @(
                            @{ name = 'CredRestAPI:Username'; value = $credUsername }
                            @{ name = 'CredRestAPI:Password'; value = $credPassword }
                            @{ name = 'CredRestAPI:Authtype'; value = '0' }
                            @{ name = 'CredRestAPI:IgnoreCertificateErrorsForOAuth2Token'; value = 'True' }
                        )
                    }
                    $credBody = @{ credentials = @($credTpl) } | ConvertTo-Json -Depth 5
                    $credUri  = "${global:WhatsUpServerBaseURI}/api/v1/credentials/-/config/template"
                    $tplResult = Get-WUGAPIResponse -Uri $credUri -Method 'PATCH' -Body $credBody
                    if ($tplResult.data -and $tplResult.data.idMap) {
                        $lmCredId = ($tplResult.data.idMap | Select-Object -First 1).resultId
                        Write-Host " Created credential via template (ID: $lmCredId)" -ForegroundColor Green
                    }
                }
                catch { Write-Verbose "Template credential creation also failed: $_" }
            }

            # Re-search fallback
            if (-not $lmCredId) {
                try {
                    $recheck = @(Get-WUGCredential -SearchValue $credName -View basic)
                    $match = $recheck | Where-Object { $_.name -eq $credName } | Select-Object -First 1
                    if ($match) {
                        $lmCredId = $match.id
                        Write-Host " Found credential '$credName' after creation (ID: $lmCredId)" -ForegroundColor Green
                    }
                }
                catch { }
            }

            if (-not $lmCredId) {
                Write-Warning "Could not create REST API credential. Create it manually in WUG."
            }
        }

        # ---- 2a. Create active monitors in library (bulk) ------------------
        Write-Host ""
        Write-Host "Creating active monitor templates in WUG library..." -ForegroundColor Cyan

        $uniqueActiveMonitors = @{}
        foreach ($item in $plan) {
            if ($item.ItemType -eq 'ActiveMonitor' -and -not $uniqueActiveMonitors.ContainsKey($item.Name)) {
                $uniqueActiveMonitors[$item.Name] = $item
            }
        }

        # Check existing active monitors
        $existingActiveNames = @{}
        try {
            foreach ($actName in $uniqueActiveMonitors.Keys) {
                $searchUri = "$($global:WhatsUpServerBaseURI)/api/v1/monitors/-?type=active&view=basic&search=$([uri]::EscapeDataString($actName))"
                $searchResult = Get-WUGAPIResponse -Uri $searchUri -Method GET -ErrorAction SilentlyContinue
                if ($searchResult.data.activeMonitors) {
                    $exact = $searchResult.data.activeMonitors | Where-Object { $_.name -eq $actName } | Select-Object -First 1
                    if ($exact) {
                        $existingActiveNames[$actName] = [int]$exact.id
                        $stats.HealthSkipped++
                    }
                }
            }
        }
        catch { Write-Verbose "Active monitor search error: $_" }

        $toCreateActive = @($uniqueActiveMonitors.Keys | Where-Object { -not $existingActiveNames.ContainsKey($_) })
        Write-Host " Existing: $($existingActiveNames.Count) | New: $($toCreateActive.Count)" -ForegroundColor DarkGray

        if ($toCreateActive.Count -gt 0) {
            $activeTemplateArr = @()
            $actTplIdMap = @{}
            $actTplIdx = 0
            foreach ($actName in $toCreateActive) {
                $actItem = $uniqueActiveMonitors[$actName]
                $mp = $actItem.MonitorParams
                $tplId = "act_$actTplIdx"
                $actTplIdMap[$tplId] = $actName
                $actTplIdx++

                $bags = @(
                    @{ name = 'MonRestApi:RestUrl';                value = "$($mp.RestApiUrl)" }
                    @{ name = 'MonRestApi:HttpMethod';             value = if ($mp.RestApiMethod) { "$($mp.RestApiMethod)" } else { 'GET' } }
                    @{ name = 'MonRestApi:HttpTimeoutMs';          value = if ($mp.RestApiTimeoutMs) { "$($mp.RestApiTimeoutMs)" } else { '10000' } }
                    @{ name = 'MonRestApi:IgnoreCertErrors';       value = if ($mp.RestApiIgnoreCertErrors) { "$($mp.RestApiIgnoreCertErrors)" } else { '1' } }
                    @{ name = 'MonRestApi:UseAnonymousAccess';     value = if ($mp.RestApiUseAnonymous) { "$($mp.RestApiUseAnonymous)" } else { '1' } }
                    @{ name = 'MonRestApi:CustomHeader';           value = if ($mp.RestApiCustomHeader) { "$($mp.RestApiCustomHeader)" } else { '' } }
                    @{ name = 'MonRestApi:DownIfResponseCodeIsIn'; value = if ($mp.RestApiDownIfResponseCodeIsIn) { "$($mp.RestApiDownIfResponseCodeIsIn)" } else { '[]' } }
                    @{ name = 'MonRestApi:ComparisonList';         value = if ($mp.RestApiComparisonList) { "$($mp.RestApiComparisonList)" } else { '[]' } }
                    @{ name = 'Cred:Type';                        value = '8192' }
                )

                $activeTemplateArr += @{
                    templateId      = $tplId
                    name            = $actName
                    description     = 'LoadMaster REST API active monitor (auto-created by discovery)'
                    useInDiscovery  = $false
                    monitorTypeInfo = @{
                        baseType = 'active'
                        classId  = 'f0610672-d515-4268-bd21-ac5ebb1476ff'
                    }
                    propertyBags    = $bags
                }
            }

            try {
                $batchSize = 50
                for ($bi = 0; $bi -lt $activeTemplateArr.Count; $bi += $batchSize) {
                    $batchEnd = [Math]::Min($bi + $batchSize - 1, $activeTemplateArr.Count - 1)
                    $actBatch = @($activeTemplateArr[$bi..$batchEnd])
                    if ($activeTemplateArr.Count -gt $batchSize) {
                        $batchNum = [Math]::Floor($bi / $batchSize) + 1
                        $totalBatches = [Math]::Ceiling($activeTemplateArr.Count / $batchSize)
                        Write-Host " Batch $batchNum/$totalBatches ($($actBatch.Count) monitors)..." -ForegroundColor DarkGray
                    }

                    $bulkActResult = Add-WUGMonitorTemplate -ActiveMonitors $actBatch
                    if ($bulkActResult.idMap) {
                        foreach ($mapping in $bulkActResult.idMap) {
                            $tplId = $mapping.templateId
                            $resultId = $mapping.resultId
                            if ($actTplIdMap.ContainsKey($tplId) -and $resultId) {
                                $actName = $actTplIdMap[$tplId]
                                $existingActiveNames[$actName] = [int]$resultId
                                $stats.HealthCreated++
                            }
                        }
                    }
                    if ($bulkActResult.errors) {
                        foreach ($err in $bulkActResult.errors) {
                            $errName = if ($actTplIdMap.ContainsKey($err.templateId)) { $actTplIdMap[$err.templateId] } else { $err.templateId }
                            Write-Warning "Active monitor create error for '$errName': $($err.messages -join '; ')"
                            $stats.HealthFailed++
                        }
                    }
                    if ($batchEnd -lt $activeTemplateArr.Count - 1) { Start-Sleep -Seconds 2 }
                }
            }
            catch {
                Write-Warning "Bulk active monitor creation failed, falling back to one-at-a-time: $_"
                foreach ($actName in $toCreateActive) {
                    if ($existingActiveNames.ContainsKey($actName)) { continue }
                    $actItem = $uniqueActiveMonitors[$actName]
                    try {
                        $actParams = @{ Type = $actItem.MonitorType; Name = $actName; ErrorAction = 'Stop' }
                        foreach ($ak in $actItem.MonitorParams.Keys) {
                            if ($ak -ne 'Name' -and $ak -ne 'Description') { $actParams[$ak] = $actItem.MonitorParams[$ak] }
                        }
                        $monLibId = Add-WUGActiveMonitor @actParams
                        if ($monLibId) {
                            $existingActiveNames[$actName] = [int]$monLibId
                            $stats.HealthCreated++
                        }
                    }
                    catch {
                        Write-Warning "Failed to create active monitor '$actName': $_"
                        $stats.HealthFailed++
                    }
                }
            }
        }

        Write-Host " Active monitors: $($stats.HealthCreated) created, $($stats.HealthSkipped) existing, $($stats.HealthFailed) failed" -ForegroundColor $(if ($stats.HealthFailed -gt 0) { 'Yellow' } else { 'Green' })

        # ---- 2b. Create performance monitors in library (bulk) -------------
        Write-Host ""
        Write-Host "Creating performance monitor templates in WUG library..." -ForegroundColor Cyan

        $uniquePerfMonitors = @{}
        foreach ($item in $plan) {
            if ($item.ItemType -eq 'PerformanceMonitor' -and -not $uniquePerfMonitors.ContainsKey($item.Name)) {
                $uniquePerfMonitors[$item.Name] = $item
            }
        }

        $existingPerfNames = @{}
        try {
            foreach ($perfName in $uniquePerfMonitors.Keys) {
                $searchUri = "$($global:WhatsUpServerBaseURI)/api/v1/monitors/-?type=performance&view=basic&search=$([uri]::EscapeDataString($perfName))"
                $searchResult = Get-WUGAPIResponse -Uri $searchUri -Method GET -ErrorAction SilentlyContinue
                if ($searchResult.data.performanceMonitors) {
                    $exact = $searchResult.data.performanceMonitors | Where-Object { $_.name -eq $perfName } | Select-Object -First 1
                    if ($exact) {
                        $existingPerfNames[$perfName] = [int]$exact.id
                        $stats.PerfSkipped++
                    }
                }
            }
        }
        catch { Write-Verbose "Perf monitor search error: $_" }

        $toCreatePerf = @($uniquePerfMonitors.Keys | Where-Object { -not $existingPerfNames.ContainsKey($_) })
        Write-Host " Existing: $($existingPerfNames.Count) | New: $($toCreatePerf.Count)" -ForegroundColor DarkGray

        if ($toCreatePerf.Count -gt 0) {
            $perfTemplateArr = @()
            $perfTplIdMap = @{}
            $perfTplIdx = 0
            foreach ($perfName in $toCreatePerf) {
                $perfItem = $uniquePerfMonitors[$perfName]
                $mp = $perfItem.MonitorParams
                $tplId = "perf_$perfTplIdx"
                $perfTplIdMap[$tplId] = $perfName
                $perfTplIdx++

                $bags = @(
                    @{ name = 'RdcRestApi:RestUrl';            value = "$($mp.RestApiUrl)" }
                    @{ name = 'RdcRestApi:JsonPath';           value = "$($mp.RestApiJsonPath)" }
                    @{ name = 'RdcRestApi:HttpMethod';         value = if ($mp.RestApiHttpMethod) { "$($mp.RestApiHttpMethod)" } else { 'GET' } }
                    @{ name = 'RdcRestApi:HttpTimeoutMs';      value = if ($mp.RestApiHttpTimeoutMs) { "$($mp.RestApiHttpTimeoutMs)" } else { '10000' } }
                    @{ name = 'RdcRestApi:IgnoreCertErrors';   value = if ($mp.RestApiIgnoreCertErrors) { "$($mp.RestApiIgnoreCertErrors)" } else { '1' } }
                    @{ name = 'RdcRestApi:UseAnonymousAccess'; value = if ($mp.RestApiUseAnonymousAccess) { "$($mp.RestApiUseAnonymousAccess)" } else { '1' } }
                    @{ name = 'RdcRestApi:CustomHeader';       value = if ($mp.RestApiCustomHeader) { "$($mp.RestApiCustomHeader)" } else { '' } }
                )

                $perfTemplateArr += @{
                    templateId      = $tplId
                    name            = $perfName
                    description     = 'LoadMaster REST API perf monitor (auto-created by discovery)'
                    monitorTypeInfo = @{
                        baseType = 'performance'
                        classId  = '987bb6a4-70f4-4f46-97c6-1c9dd1766437'
                    }
                    propertyBags    = $bags
                }
            }

            try {
                $batchSize = 50
                for ($bi = 0; $bi -lt $perfTemplateArr.Count; $bi += $batchSize) {
                    $batchEnd = [Math]::Min($bi + $batchSize - 1, $perfTemplateArr.Count - 1)
                    $perfBatch = @($perfTemplateArr[$bi..$batchEnd])
                    if ($perfTemplateArr.Count -gt $batchSize) {
                        $batchNum = [Math]::Floor($bi / $batchSize) + 1
                        $totalBatches = [Math]::Ceiling($perfTemplateArr.Count / $batchSize)
                        Write-Host " Batch $batchNum/$totalBatches ($($perfBatch.Count) monitors)..." -ForegroundColor DarkGray
                    }

                    $bulkPerfResult = Add-WUGMonitorTemplate -PerformanceMonitors $perfBatch
                    if ($bulkPerfResult.idMap) {
                        foreach ($mapping in $bulkPerfResult.idMap) {
                            $tplId = $mapping.templateId
                            $resultId = $mapping.resultId
                            if ($perfTplIdMap.ContainsKey($tplId) -and $resultId) {
                                $perfName = $perfTplIdMap[$tplId]
                                $existingPerfNames[$perfName] = [int]$resultId
                                $stats.PerfCreated++
                            }
                        }
                    }
                    if ($bulkPerfResult.errors) {
                        foreach ($err in $bulkPerfResult.errors) {
                            $errName = if ($perfTplIdMap.ContainsKey($err.templateId)) { $perfTplIdMap[$err.templateId] } else { $err.templateId }
                            Write-Warning "Perf monitor create error for '$errName': $($err.messages -join '; ')"
                            $stats.PerfFailed++
                        }
                    }
                    if ($batchEnd -lt $perfTemplateArr.Count - 1) { Start-Sleep -Seconds 2 }
                }
            }
            catch {
                Write-Warning "Bulk perf monitor creation failed, falling back to one-at-a-time: $_"
                foreach ($perfName in $toCreatePerf) {
                    if ($existingPerfNames.ContainsKey($perfName)) { continue }
                    $perfItem = $uniquePerfMonitors[$perfName]
                    try {
                        $perfParams = @{ Type = 'RestApi'; Name = $perfName; ErrorAction = 'Stop' }
                        foreach ($pk in $perfItem.MonitorParams.Keys) {
                            if ($pk -ne 'Name' -and $pk -ne 'Description') { $perfParams[$pk] = $perfItem.MonitorParams[$pk] }
                        }
                        $monLibId = Add-WUGPerformanceMonitor @perfParams
                        if ($monLibId) {
                            $existingPerfNames[$perfName] = [int]$monLibId
                            $stats.PerfCreated++
                        }
                    }
                    catch {
                        Write-Warning "Failed to create perf monitor '$perfName': $_"
                        $stats.PerfFailed++
                    }
                }
            }
        }

        Write-Host " Perf monitors: $($stats.PerfCreated) created, $($stats.PerfSkipped) existing, $($stats.PerfFailed) failed" -ForegroundColor $(if ($stats.PerfFailed -gt 0) { 'Yellow' } else { 'Green' })

        # ---- 2c-e. Create/update devices -----------------------------------
        Write-Host ""
        Write-Host "Creating/updating devices in WUG..." -ForegroundColor Cyan

        $deviceKeys = @($devicePlan.Keys | Sort-Object)
        foreach ($key in $deviceKeys) {
            $dev = $devicePlan[$key]
            $devIP = if ($dev.IP) { $dev.IP } else { '0.0.0.0' }
            $devName = "$($dev.Name) ($($dev.Type))"

            # Check if device exists
            $existingDevId = $null
            try {
                $searchDevs = @(Get-WUGDevice -SearchValue $devName -Column DisplayName -View basic)
                if ($searchDevs.Count -gt 0) {
                    $existingDevId = $searchDevs[0].id
                }
            }
            catch { }

            # Try by IP if name search failed and IP is not 0.0.0.0
            if (-not $existingDevId -and $devIP -ne '0.0.0.0') {
                try {
                    $searchDevs = @(Get-WUGDevice -SearchValue $devIP -Column NetworkAddress -View basic)
                    if ($searchDevs.Count -gt 0) {
                        $existingDevId = $searchDevs[0].id
                    }
                }
                catch { }
            }

            if ($existingDevId) {
                Write-Host " Found: $devName (ID: $existingDevId)" -ForegroundColor DarkGray
                $stats.DevicesFound++
                $wugDeviceMap[$key] = $existingDevId
            }
            else {
                # Create new device
                Write-Host " Creating: $devName ($devIP)" -ForegroundColor Yellow
                try {
                    $devAttrs = @()
                    if ($dev.Attrs) {
                        foreach ($ak in $dev.Attrs.Keys) {
                            $devAttrs += @{ name = $ak; value = "$($dev.Attrs[$ak])" }
                        }
                    }

                    # Collect monitor names for this device
                    $devActiveNames = @($dev.Items | Where-Object { $_.ItemType -eq 'ActiveMonitor' } | Select-Object -ExpandProperty Name -Unique)
                    $devPerfNames   = @($dev.Items | Where-Object { $_.ItemType -eq 'PerformanceMonitor' } | Select-Object -ExpandProperty Name -Unique)

                    $splat = @{
                        displayName            = $devName
                        DeviceAddress          = $devIP
                        Hostname               = $dev.Name
                        Brand                  = 'LoadMaster'
                        Note                   = "LoadMaster $($dev.Type) (auto-created by discovery)"
                        Attributes             = $devAttrs
                        NoDefaultActiveMonitor = $true
                    }
                    if ($devActiveNames.Count -gt 0) { $splat['ActiveMonitors'] = $devActiveNames }
                    if ($devPerfNames.Count -gt 0)   { $splat['PerformanceMonitors'] = $devPerfNames }
                    if ($lmCredId) { $splat['CredentialRestApi'] = $credName }

                    $newDev = Add-WUGDeviceTemplate @splat
                    if ($newDev) {
                        $newDevId = $null
                        if ($newDev.PSObject.Properties['data']) { $newDevId = $newDev.data.idMap.resultId }
                        elseif ($newDev.PSObject.Properties['id']) { $newDevId = $newDev.id }
                        if ($newDevId) {
                            $wugDeviceMap[$key] = $newDevId
                            $stats.DevicesCreated++
                            Write-Host " Created device (ID: $newDevId)" -ForegroundColor Green
                        }
                    }
                }
                catch {
                    Write-Warning "Failed to create device '$devName': $_"
                }
            }

            # Assign credential and monitors to existing devices
            if ($existingDevId) {
                $wugDevId = $existingDevId

                # Assign credential
                if ($lmCredId) {
                    try {
                        Set-WUGDeviceCredential -DeviceId $wugDevId -CredentialId $lmCredId -Assign
                        $stats.CredsAssigned++
                    }
                    catch { Write-Verbose "Credential assignment error for device $wugDevId : $_" }
                }

                # Assign active monitors
                $devActiveNames = @($dev.Items | Where-Object { $_.ItemType -eq 'ActiveMonitor' } | Select-Object -ExpandProperty Name -Unique)
                $activeIds = @()
                foreach ($an in $devActiveNames) {
                    if ($existingActiveNames.ContainsKey($an)) { $activeIds += $existingActiveNames[$an] }
                }
                if ($activeIds.Count -gt 0) {
                    try { Add-WUGActiveMonitorToDevice -DeviceId $wugDevId -MonitorId $activeIds } catch { Write-Verbose "Active monitor assign error: $_" }
                }

                # Assign perf monitors
                $devPerfNames = @($dev.Items | Where-Object { $_.ItemType -eq 'PerformanceMonitor' } | Select-Object -ExpandProperty Name -Unique)
                $perfIds = @()
                foreach ($pn in $devPerfNames) {
                    if ($existingPerfNames.ContainsKey($pn)) { $perfIds += $existingPerfNames[$pn] }
                }
                if ($perfIds.Count -gt 0) {
                    try { Add-WUGPerformanceMonitorToDevice -DeviceId $wugDevId -MonitorId $perfIds -PollingIntervalMinutes 5 } catch { Write-Verbose "Perf monitor assign error: $_" }
                }
            }
        }

        Write-Host ""
        Write-Host "=== WUG Push Summary ===" -ForegroundColor Cyan
        Write-Host " Devices created: $($stats.DevicesCreated)" -ForegroundColor White
        Write-Host " Devices found: $($stats.DevicesFound)" -ForegroundColor White
        Write-Host " Active monitors: $($stats.HealthCreated) created, $($stats.HealthSkipped) existing" -ForegroundColor White
        Write-Host " Perf monitors: $($stats.PerfCreated) created, $($stats.PerfSkipped) existing" -ForegroundColor White
        Write-Host " Credentials assigned: $($stats.CredsAssigned)" -ForegroundColor White
        Write-Host ""
    }

    '2' {
        # Export JSON
        $jsonPath = Join-Path $OutputDir "LoadMaster-Discovery-$(Get-Date -Format 'yyyyMMdd-HHmmss').json"
        $plan | Export-DiscoveryPlan -Format JSON -Path $jsonPath -IncludeParams
        Write-Host " Exported: $jsonPath" -ForegroundColor Green
    }

    '3' {
        # Export CSV
        $csvPath = Join-Path $OutputDir "LoadMaster-Discovery-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
        $plan | Export-DiscoveryPlan -Format CSV -Path $csvPath
        Write-Host " Exported: $csvPath" -ForegroundColor Green
    }

    '4' {
        # Show Table
        $plan | Export-DiscoveryPlan -Format Table
    }

    '5' {
        # Dashboard
        Write-Host "Generating LoadMaster dashboard..." -ForegroundColor Cyan
        $dashPath = Join-Path $OutputDir "LoadMaster-Dashboard.html"

        # Build API params
        $dashSplat = @{
            LMHost           = $LMHosts[0]
            LMPort           = $ApiPort
            OutputPath       = $dashPath
            IgnoreCertErrors = $true
        }
        if ($LMApiKey)     { $dashSplat['ApiKey'] = $LMApiKey }
        if ($LMCredential) { $dashSplat['Credential'] = $LMCredential }

        Export-LoadMasterDashboardHtml @dashSplat

        Write-Host "Dashboard generated: $dashPath" -ForegroundColor Green

        # Copy to WUG NmConsole if available
        $nmConsolePaths = @(
            "${env:ProgramFiles(x86)}\Ipswitch\WhatsUp\Html\NmConsole"
            "${env:ProgramFiles}\Ipswitch\WhatsUp\Html\NmConsole"
        )
        $nmConsolePath = $nmConsolePaths | Where-Object { Test-Path $_ } | Select-Object -First 1
        if ($nmConsolePath) {
            $wugDashDir = Join-Path $nmConsolePath 'dashboards'
            if (-not (Test-Path $wugDashDir)) { New-Item -ItemType Directory -Path $wugDashDir -Force | Out-Null }
            $wugDashPath = Join-Path $wugDashDir 'LoadMaster-Dashboard.html'
            try {
                Copy-Item -Path $dashPath -Destination $wugDashPath -Force
                Write-Host " Copied to WUG: $wugDashPath" -ForegroundColor Green
                Write-Host " Access via WUG web UI: /NmConsole/dashboards/LoadMaster-Dashboard.html" -ForegroundColor Cyan
            }
            catch {
                Write-Warning "Could not copy to NmConsole (run as admin?): $_"
            }
            Deploy-DashboardWebConfig -Path $wugDashDir
        }
    }

    default {
        Write-Host "No action taken." -ForegroundColor DarkGray
    }
}
}

Write-Host ""
Write-Host "Done." -ForegroundColor Green

# SIG # Begin signature block
# MIIr+wYJKoZIhvcNAQcCoIIr7DCCK+gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBOGWnatDXulIFc
# FtshQj/Eu/Ue7quCa/EC10t/e2GmZaCCJQ0wggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
# dmljZXMwHhcNMjEwNTI1MDAwMDAwWhcNMjgxMjMxMjM1OTU5WjBWMQswCQYDVQQG
# EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS0wKwYDVQQDEyRTZWN0aWdv
# IFB1YmxpYyBDb2RlIFNpZ25pbmcgUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCN55QSIgQkdC7/FiMCkoq2rjaFrEfUI5ErPtx94jGgUW+s
# hJHjUoq14pbe0IdjJImK/+8Skzt9u7aKvb0Ffyeba2XTpQxpsbxJOZrxbW6q5KCD
# J9qaDStQ6Utbs7hkNqR+Sj2pcaths3OzPAsM79szV+W+NDfjlxtd/R8SPYIDdub7
# P2bSlDFp+m2zNKzBenjcklDyZMeqLQSrw2rq4C+np9xu1+j/2iGrQL+57g2extme
# me/G3h+pDHazJyCh1rr9gOcB0u/rgimVcI3/uxXP/tEPNqIuTzKQdEZrRzUTdwUz
# T2MuuC3hv2WnBGsY2HH6zAjybYmZELGt2z4s5KoYsMYHAXVn3m3pY2MeNn9pib6q
# RT5uWl+PoVvLnTCGMOgDs0DGDQ84zWeoU4j6uDBl+m/H5x2xg3RpPqzEaDux5mcz
# mrYI4IAFSEDu9oJkRqj1c7AGlfJsZZ+/VVscnFcax3hGfHCqlBuCF6yH6bbJDoEc
# QNYWFyn8XJwYK+pF9e+91WdPKF4F7pBMeufG9ND8+s0+MkYTIDaKBOq3qgdGnA2T
# OglmmVhcKaO5DKYwODzQRjY1fJy67sPV+Qp2+n4FG0DKkjXp1XrRtX8ArqmQqsV/
# AZwQsRb8zG4Y3G9i/qZQp7h7uJ0VP/4gDHXIIloTlRmQAOka1cKG8eOO7F/05QID
# AQABo4IBEjCCAQ4wHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD
# VR0OBBYEFDLrkpr/NZZILyhAQnAgNpFcF4XmMA4GA1UdDwEB/wQEAwIBhjAPBgNV
# HRMBAf8EBTADAQH/MBMGA1UdJQQMMAoGCCsGAQUFBwMDMBsGA1UdIAQUMBIwBgYE
# VR0gADAIBgZngQwBBAEwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21v
# ZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEE
# KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZI
# hvcNAQEMBQADggEBABK/oe+LdJqYRLhpRrWrJAoMpIpnuDqBv0WKfVIHqI0fTiGF
# OaNrXi0ghr8QuK55O1PNtPvYRL4G2VxjZ9RAFodEhnIq1jIV9RKDwvnhXRFAZ/ZC
# J3LFI+ICOBpMIOLbAffNRk8monxmwFE2tokCVMf8WPtsAO7+mKYulaEMUykfb9gZ
# pk+e96wJ6l2CxouvgKe9gUhShDHaMuwV5KZMPWw5c9QLhTkg4IUaaOGnSDip0TYl
# d8GNGRbFiExmfS9jzpjoad+sPKhdnckcW67Y8y90z7h+9teDnRGWYpquRRPaf9xH
# +9/DUp/mBlXpnYzyOmJRvOwkDynUWICE5EV7WtgwggWNMIIEdaADAgECAhAOmxiO
# +dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYD
# VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAi
# BgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAw
# MDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp
# Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERp
# Z2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsb
# hA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iT
# cMKyunWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGb
# NOsFxl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclP
# XuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCr
# VYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFP
# ObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTv
# kpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWM
# cCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls
# 5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBR
# a2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6
# MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qY
# rhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8E
# BAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k
# aWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDig
# NoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v
# dENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCg
# v0NcVec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQT
# SnovLbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh
# 65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSw
# uKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAO
# QGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjD
# TZ9ztwGpn1eqXijiuZQwggYaMIIEAqADAgECAhBiHW0MUgGeO5B5FSCJIRwKMA0G
# CSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExp
# bWl0ZWQxLTArBgNVBAMTJFNlY3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBSb290
# IFI0NjAeFw0yMTAzMjIwMDAwMDBaFw0zNjAzMjEyMzU5NTlaMFQxCzAJBgNVBAYT
# AkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNlY3RpZ28g
# UHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwggGiMA0GCSqGSIb3DQEBAQUAA4IB
# jwAwggGKAoIBgQCbK51T+jU/jmAGQ2rAz/V/9shTUxjIztNsfvxYB5UXeWUzCxEe
# AEZGbEN4QMgCsJLZUKhWThj/yPqy0iSZhXkZ6Pg2A2NVDgFigOMYzB2OKhdqfWGV
# oYW3haT29PSTahYkwmMv0b/83nbeECbiMXhSOtbam+/36F09fy1tsB8je/RV0mIk
# 8XL/tfCK6cPuYHE215wzrK0h1SWHTxPbPuYkRdkP05ZwmRmTnAO5/arnY83jeNzh
# P06ShdnRqtZlV59+8yv+KIhE5ILMqgOZYAENHNX9SJDm+qxp4VqpB3MV/h53yl41
# aHU5pledi9lCBbH9JeIkNFICiVHNkRmq4TpxtwfvjsUedyz8rNyfQJy/aOs5b4s+
# ac7IH60B+Ja7TVM+EKv1WuTGwcLmoU3FpOFMbmPj8pz44MPZ1f9+YEQIQty/NQd/
# 2yGgW+ufflcZ/ZE9o1M7a5Jnqf2i2/uMSWymR8r2oQBMdlyh2n5HirY4jKnFH/9g
# Rvd+QOfdRrJZb1sCAwEAAaOCAWQwggFgMB8GA1UdIwQYMBaAFDLrkpr/NZZILyhA
# QnAgNpFcF4XmMB0GA1UdDgQWBBQPKssghyi47G9IritUpimqF6TNDDAOBgNVHQ8B
# Af8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcD
# AzAbBgNVHSAEFDASMAYGBFUdIAAwCAYGZ4EMAQQBMEsGA1UdHwREMEIwQKA+oDyG
# Omh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVTaWduaW5n
# Um9vdFI0Ni5jcmwwewYIKwYBBQUHAQEEbzBtMEYGCCsGAQUFBzAChjpodHRwOi8v
# Y3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNDb2RlU2lnbmluZ1Jvb3RSNDYu
# cDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG
# 9w0BAQwFAAOCAgEABv+C4XdjNm57oRUgmxP/BP6YdURhw1aVcdGRP4Wh60BAscjW
# 4HL9hcpkOTz5jUug2oeunbYAowbFC2AKK+cMcXIBD0ZdOaWTsyNyBBsMLHqafvIh
# rCymlaS98+QpoBCyKppP0OcxYEdU0hpsaqBBIZOtBajjcw5+w/KeFvPYfLF/ldYp
# mlG+vd0xqlqd099iChnyIMvY5HexjO2AmtsbpVn0OhNcWbWDRF/3sBp6fWXhz7Dc
# ML4iTAWS+MVXeNLj1lJziVKEoroGs9Mlizg0bUMbOalOhOfCipnx8CaLZeVme5yE
# Lg09Jlo8BMe80jO37PU8ejfkP9/uPak7VLwELKxAMcJszkyeiaerlphwoKx1uHRz
# NyE6bxuSKcutisqmKL5OTunAvtONEoteSiabkPVSZ2z76mKnzAfZxCl/3dq3dUNw
# 4rg3sTCggkHSRqTqlLMS7gjrhTqBmzu1L90Y1KWN/Y5JKdGvspbOrTfOXyXvmPL6
# E52z1NZJ6ctuMFBQZH3pwWvqURR8AgQdULUvrxjUYbHHj95Ejza63zdrEcxWLDX6
# xWls/GDnVNueKjWUH3fTv1Y8Wdho698YADR7TNx8X8z2Bev6SivBBOHY+uqiirZt
# g0y9ShQoPzmCcn63Syatatvx157YK9hlcPmVoa1oDE5/L9Uo2bC5a4CH2RwwggY+
# MIIEpqADAgECAhAHnODk0RR/hc05c892LTfrMA0GCSqGSIb3DQEBDAUAMFQxCzAJ
# BgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxKzApBgNVBAMTIlNl
# Y3RpZ28gUHVibGljIENvZGUgU2lnbmluZyBDQSBSMzYwHhcNMjYwMjA5MDAwMDAw
# WhcNMjkwNDIxMjM1OTU5WjBVMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLQ29ubmVj
# dGljdXQxFzAVBgNVBAoMDkphc29uIEFsYmVyaW5vMRcwFQYDVQQDDA5KYXNvbiBB
# bGJlcmlubzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPN6aN4B1yYW
# kI5b5TBj3I0VV/peETrHb6EY4BHGxt8Ap+eT+WpEpJyEtRYPxEmNJL3A38Bkg7mw
# zPE3/1NK570ZBCuBjSAn4mSDIgIuXZnvyBO9W1OQs5d67MlJLUAEufl18tOr3ST1
# DeO9gSjQSAE5Nql0QDxPnm93OZBon+Fz3CmE+z3MwAe2h4KdtRAnCqwM+/V7iBdb
# w+JOxolpx+7RVjGyProTENIG3pe/hKvPb501lf8uBAADLdjZr5ip8vIWbf857Yw1
# Bu10nVI7HW3eE8Cl5//d1ribHlzTzQLfttW+k+DaFsKZBBL56l4YAlIVRsrOiE1k
# dHYYx6IGrEA809R7+TZA9DzGqyFiv9qmJAbL4fDwetDeyIq+Oztz1LvEdy8Rcd0J
# BY+J4S0eDEFIA3X0N8VcLeAwabKb9AjulKXwUeqCJLvN79CJ90UTZb2+I+tamj0d
# n+IKMEsJ4v4Ggx72sxFr9+6XziodtTg5Luf2xd6+PhhamOxF2px9LObhBLLEMyRs
# CHZIzVZOFKu9BpHQH7ufGB+Sa80Tli0/6LEyn9+bMYWi2ttn6lLOPThXMiQaooRU
# q6q2u3+F4SaPlxVFLI7OJVMhar6nW6joBvELTJPmANSMjDSRFDfHRCdGbZsL/keE
# LJNy+jZctF6VvxQEjFM8/bazu6qYhrA7AgMBAAGjggGJMIIBhTAfBgNVHSMEGDAW
# gBQPKssghyi47G9IritUpimqF6TNDDAdBgNVHQ4EFgQU6YF0o0D5AVhKHbVocr8G
# aSIBibAwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYI
# KwYBBQUHAwMwSgYDVR0gBEMwQTA1BgwrBgEEAbIxAQIBAwIwJTAjBggrBgEFBQcC
# ARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQQBMEkGA1UdHwRCMEAw
# PqA8oDqGOGh0dHA6Ly9jcmwuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY0NvZGVT
# aWduaW5nQ0FSMzYuY3JsMHkGCCsGAQUFBwEBBG0wazBEBggrBgEFBQcwAoY4aHR0
# cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVibGljQ29kZVNpZ25pbmdDQVIz
# Ni5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqG
# SIb3DQEBDAUAA4IBgQAEIsm4xnOd/tZMVrKwi3doAXvCwOA/RYQnFJD7R/bSQRu3
# wXEK4o9SIefye18B/q4fhBkhNAJuEvTQAGfqbbpxow03J5PrDTp1WPCWbXKX8Oz9
# vGWJFyJxRGftkdzZ57JE00synEMS8XCwLO9P32MyR9Z9URrpiLPJ9rQjfHMb1BUd
# vaNayomm7aWLAnD+X7jm6o8sNT5An1cwEAob7obWDM6sX93wphwJNBJAstH9Ozs6
# LwISOX6sKS7CKm9N3Kp8hOUue0ZHAtZdFl6o5u12wy+zzieGEI50fKnN77FfNKFO
# WKlS6OJwlArcbFegB5K89LcE5iNSmaM3VMB2ADV1FEcjGSHw4lTg1Wx+WMAMdl/7
# nbvfFxJ9uu5tNiT54B0s+lZO/HztwXYQUczdsFon3pjsNrsk9ZlalBi5SHkIu+F6
# g7tWiEv3rtVApmJRnLkUr2Xq2a4nbslUCt4jKs5UX4V1nSX8OM++AXoyVGO+iTj7
# z+pl6XE9Gw/Td6WKKKswgga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0G
# CSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEy
# NTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHT
# CphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPh
# of6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mA
# xAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBv
# MgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps
# 0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF
# 83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXi
# UOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOM
# CZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydP
# pOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrU
# G2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+
# sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s
# BwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WI
# GjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+
# IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8M
# yb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2
# th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjaj
# V/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2
# Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFze
# GxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG
# 7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+N
# Jpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckT
# etiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszW
# kPZPubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0B
# AQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/
# BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYg
# U0hBMjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVow
# YzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQD
# EzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIw
# MjUgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5g
# VrMrV7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN
# +vo8dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qo
# me7MrxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ/
# /nBZZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouT
# MYFOnHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8
# DD+nigNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnp
# JeItK/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP5
# 1ho1zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49
# kPmk8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5P
# WPsWeupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7Y
# ufAkprxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAA
# MB0GA1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK
# 6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYI
# KwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZT
# SEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2
# U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9
# bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP
# 2zWLpQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O
# 6Lgjg8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskg
# iC3QYIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMU
# BaB5bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDF
# kxUGtMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+
# zJNEsuEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/l
# wd6UArb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxl
# RcGG0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2
# zRWVFjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJg
# baP5t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOC
# IUjsarfNZzGCBkQwggZAAgEBMGgwVDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1Nl
# Y3RpZ28gTGltaXRlZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWdu
# aW5nIENBIFIzNgIQB5zg5NEUf4XNOXPPdi036zANBglghkgBZQMEAgEFAKCBhDAY
# BgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3
# AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEi
# BCCcca5ZsgTzAyXqbkDa+ioHrkx8/A9sPeI/dn03gM8pZjANBgkqhkiG9w0BAQEF
# AASCAgAHvMCTmSuwRLD3hqPr4Xi5XvnABmlLiXVi/mEifnjoEbX5JbmVkLW+b2UO
# DREojNUT6lqQxWduDyuRs+tRg/73BNjoxvedy4XU5sTiGXySz7Fo3VB9rMdZ6IpY
# wW0GEMAOMFd/eaI+2X79OoCyXUuYXF/zdtJcX09SD4ptfYa9YbpemqhIih1UHOBt
# qsgwvys2QbcZjDkQ1H34Gq3zOuZhzJ/61g19JLey2c69eYh0K9+OOCpEsyJJYZn+
# iPv1pfK9k0uyJaJ0MAs2rfysmdhJ0umyhci/zsLNQObqVwq1tMIMmuHNTK7y+mq3
# 4HLIxh3TIzKLsggBVRmbOo/9A0/1UD+UBZ5repGtwtwJEAkECLZTQQw++KFP17WW
# gXLTzkB821/FhJ8ygkbBAiTk9iM87cGH/5bSVEVfqsMFN5Q61Gbe/eTkgvPpTqDZ
# lCoBtW4tMHgMDXj1wATalcrF3IZDYWRlj9H62tAgpousMI0zTGEs5LvZWYN3dndq
# xih65nt1sEOM9zG7SXdLWz9xWtCvIYhRQj4AfbNzLiBmRBDBz+plQ2/c8INNg+9n
# purwaMWfoh3nsYRTj8j6Kkbj6Asb2vMzBbIskf24rb3hN+P+2SKKwmUvGlw939LN
# nJKePbhG0QUYihTKd+r7OzSP1gCwwfKojxuSDw8e6apLNXJi1KGCAyYwggMiBgkq
# hkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E
# aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1l
# U3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeV
# dGgwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNjA3MDcwMjI1MzJaMC8GCSqGSIb3DQEJBDEiBCDtMJHL
# eNd38WIHljKatL/rcktAgHiBMiDA7YmZg1Op2zANBgkqhkiG9w0BAQEFAASCAgBG
# 5+OuXuxmYclw9Qf2pWbH0yQ/f7LDi/6Ap56LQRWHH8PAlDM+Mf3yXayUWc/G0tK+
# DC5psZcwBdTVD+LyL6kGxc4o0lr58SVVKcPi5m4Zd2Sl7vbMp7A06ggFeSLYh34y
# ZQ8XmRrPF4wek8+vh/+9BO6nUq8p+D9IljOQb0Zb0kmZP+uCiOW2psjrWipVd0bM
# j5bC9YE0mOy+4jwlwv6WHCsDmG7sJ60rNaqyuAo0BQNsvg+80zZiOFqoYmKM1OJX
# SEVv8+fg2XW5N9g8Hc82ntRmHZWwa17Q1PDyHhqTY7LjS9Ajx3R4zXJ7MkT2aNJS
# O/GMeifvZIk0e3Q2PzWS9S/O4KDjNyG8aXgUgHyfgCC3eJH8cCVCM8c5jCUuYasl
# ukscjw3bHwvJOIKYPQO9qlRECgeIypVMv+V37VFelMbav1OX795pN2yR4Rp0O6LI
# gVBN53TzI6Yfzpj28AWfih7u2nvx0ZvkyF3n1Ggz7fBkdAyF0srbVgIO9PQmp1OX
# j/h6bChJKO5jrEeD8HuEzrGjrPxyqO1QPvo1wp+MA2+JD+cnfm0hjn23ZOYm872B
# Hf3j7kXcSDMNa7cDREeSN6tG+FXqqXTbjRUBXkUjoNqEKfC1ODRHLPXUoJSqLkAv
# F0YYGf+sXtv/OjR3BQU6+dwwNGk0dGJ4Xv1ja9ZDvA==
# SIG # End signature block