helpers/discovery/Setup-MSSQL-Discovery.ps1

#requires -Version 5.1

<#
.SYNOPSIS
    Discovers Microsoft SQL Server instances and databases via WMI, then creates
    database-specific WMI Formatted performance monitors in WhatsUp Gold.
 
.DESCRIPTION
    This helper connects to Windows servers via WMI to:
      1. Enumerate SQL Server services (Win32_Service) to discover instance names
      2. Query the SQL Server Databases WMI performance class per instance to
         discover all databases hosted on each instance
      3. Create WmiFormatted performance monitors in the WUG library for each
         database+counter combination
      4. Assign those monitors to the applicable WUG devices
 
    For each database discovered, the following monitors can be created:
      - Transactions/sec
      - Log Bytes Flushed/sec
      - Log Flush Waits/sec
      - Active Transactions
      - Data File(s) Size (KB)
      - Log File(s) Size (KB)
      - Log File(s) Used Size (KB)
      - Percent Log Used
 
    The script automatically handles both default (MSSQLSERVER) and named instances
    by building the correct WMI class path:
      - Default: Win32_PerfFormattedData_MSSQLSERVER_SQLServerDatabases
      - Named: Win32_PerfFormattedData_MSSQL$INSTANCENAME_SQLServerDatabases
 
    Prerequisites:
      1. WhatsUpGoldPS module loaded and connected (Connect-WUGServer)
      2. WMI/DCOM or WinRM access to target Windows servers
      3. Credentials with admin access on target servers
      4. SQL Server must be running on the target
 
.PARAMETER DeviceId
    One or more WUG device IDs to scan. When omitted, devices are resolved
    automatically from a device group (see DeviceGroupSearch).
 
.PARAMETER Credential
    One or more PSCredentials for WMI access to the target servers.
    Multiple credentials are tried in order per device. If omitted, credentials
    are resolved from the DPAPI discovery vault.
 
.PARAMETER DeviceGroupSearch
    Search string used to find a WUG device group containing SQL servers.
    Default: 'SQL Servers'. Set to '' to skip group-based filtering.
 
.PARAMETER DeviceGroupId
    Explicit WUG device group ID. Overrides DeviceGroupSearch.
 
.PARAMETER ExcludeDatabase
    Array of database name patterns to skip. Default: @('_Total', 'tempdb').
    Use @() to include all databases.
 
.PARAMETER ExcludeInstance
    Array of SQL instance name patterns to skip. Default: @().
 
.PARAMETER IncludeCounter
    Array of counter names to create monitors for. Default: TransactionsPersec,
    PercentLogUsed. Use 'All' to include all 8 counters.
    Valid values: All, TransactionsPersec, LogBytesFlushedPersec,
    LogFlushWaitsPersec, ActiveTransactions, DataFilesSizeKB, LogFileSizeKB,
    LogFileUsedSizeKB, PercentLogUsed.
 
.PARAMETER IncludeInstanceCounter
    Array of instance-level (non per-database) counter names or category names.
    These monitor SQL Server engine health at the instance level via additional
    WMI performance classes (Buffer Manager, SQL Statistics, Locks, etc.).
    Specify individual counter names, category names to include all counters in
    that category, or 'All' for everything.
    Categories: BufferManager, SQLStatistics, GeneralStatistics, MemoryManager,
    AccessMethods, Locks, Latches.
    Example counters: PageLifeExpectancy, BatchRequestsPersec, NumberOfDeadlocksPersec.
 
.PARAMETER PollingIntervalMinutes
    Polling interval for assigned monitors. Default: 5.
 
.PARAMETER WmiTimeout
    WMI query timeout in seconds. Default: 10.
 
.PARAMETER NamePrefix
    Prefix for monitor display names. Default: 'MSSQL'.
 
.PARAMETER Action
    What to do with discovery results. When specified, skips the interactive menu.
    Valid values: PushToWUG, ExportJSON, ExportCSV, ShowTable, Dashboard, DashboardAndPush, None.
 
.PARAMETER Target
    Windows host(s) to scan via WMI -- IP address or FQDN. Accepts multiple values.
    Enables standalone mode (no WhatsUp Gold connection needed for device resolution).
 
.PARAMETER DryRun
    Show what would be created/assigned without making changes.
 
.PARAMETER WUGServer
    WhatsUp Gold server address. Default: resolved from vault.
 
.PARAMETER WUGCredential
    PSCredential for authenticating to WhatsUp Gold.
 
.PARAMETER NonInteractive
    Suppress interactive prompts. Uses cached vault credentials.
 
.PARAMETER OutputPath
    Directory for exported data. Default: %LOCALAPPDATA%\WhatsUpGoldPS\DiscoveryHelpers\Output.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -Target '192.168.74.74' -Action Dashboard
 
    Standalone mode: scan SQL Server on 192.168.74.74, generate dashboard.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -Target '192.168.74.74' -Action PushToWUG -NonInteractive
 
    Scan and push monitors to WUG non-interactively using vault credentials.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -Target '192.168.74.74' -IncludeCounter All -Action Dashboard
 
    Discover all counters for all databases and generate a dashboard.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -DeviceGroupSearch 'SQL Servers' -Action PushToWUG
 
    Scan all devices in the 'SQL Servers' group and push monitors.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -Target '192.168.74.74' -DryRun
 
    Show what monitors would be created without making changes.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -DeviceGroupSearch 'Windows' -IncludeInstanceCounter All -Action Dashboard
 
    Discover all instance-level counters (Buffer Manager, SQL Statistics, Locks, etc.)
    across devices in the 'Windows' group and generate a performance dashboard.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -Target '192.168.74.74' -IncludeCounter All -IncludeInstanceCounter BufferManager,Locks -Action DashboardAndPush
 
    Discover all database counters plus Buffer Manager and Locks instance counters,
    generate dashboards, and push all monitors to WUG.
 
.EXAMPLE
    .\Setup-MSSQL-Discovery.ps1 -DeviceGroupSearch 'SQL Servers' -IncludeInstanceCounter PageLifeExpectancy,ProcessesBlocked,NumberOfDeadlocksPersec -Action PushToWUG
 
    Push only specific high-value instance counters as WUG monitors.
 
.NOTES
    Author: Jason Alberino (jason@wug.ninja)
    Requires: WhatsUpGoldPS module, PowerShell 5.1+
    Encoding: UTF-8 with BOM
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param(
    [int[]]$DeviceId,

    [PSCredential[]]$Credential,

    [string]$DeviceGroupSearch = 'SQL Servers',

    [string]$DeviceGroupId,

    [string[]]$ExcludeDatabase = @('_Total', 'tempdb'),

    [string[]]$ExcludeInstance = @(),

    [ValidateSet('All', 'TransactionsPersec', 'WriteTransactionsPersec', 'LogBytesFlushedPersec',
                 'LogFlushesPersec', 'LogFlushWaitsPersec', 'LogFlushWaitTime',
                 'ActiveTransactions', 'DataFilesSizeKB', 'LogFilesSizeKB',
                 'LogFilesUsedSizeKB', 'PercentLogUsed', 'LogGrowths',
                 'LogTruncations', 'LogCacheHitRatio', 'LogShrinks')]
    [string[]]$IncludeCounter,

    [ValidateSet('All',
                 'BufferManager', 'SQLStatistics', 'GeneralStatistics', 'MemoryManager',
                 'AccessMethods', 'Locks', 'Latches',
                 'BufferCacheHitRatio', 'PageLifeExpectancy', 'LazyWritesPersec',
                 'CheckpointPagesPersec', 'PageReadsPersec', 'PageWritesPersec', 'DatabasePages',
                 'BatchRequestsPersec', 'SQLCompilationsPersec', 'SQLReCompilationsPersec',
                 'UserConnections', 'ProcessesBlocked', 'LoginsPersec', 'LogoutsPersec',
                 'TotalServerMemoryKB', 'TargetServerMemoryKB', 'MemoryGrantsPending',
                 'FullScansPersec', 'IndexSearchesPersec', 'PageSplitsPersec', 'ForwardedRecordsPersec',
                 'NumberOfDeadlocksPersec', 'LockWaitsPersec', 'LockTimeoutsPersec',
                 'LockRequestsPersec', 'AverageLockWaitTimeMs',
                 'LatchWaitsPersec', 'AverageLatchWaitTimeMs')]
    [string[]]$IncludeInstanceCounter,

    [ValidateRange(1, 1440)]
    [int]$PollingIntervalMinutes = 5,

    [int]$WmiTimeout = 10,

    [string]$NamePrefix = 'MSSQL',

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

    [string[]]$Target,

    [string]$WUGServer,

    [PSCredential]$WUGCredential,

    [switch]$DryRun,

    [switch]$NonInteractive,

    [string]$OutputPath
)

# --- Load discovery helpers (vault, credential resolver) ----------------------
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
$discoveryHelpersPath = Join-Path $scriptDir 'DiscoveryHelpers.ps1'
if (Test-Path $discoveryHelpersPath) {
    . $discoveryHelpersPath
}
else {
    Write-Error "DiscoveryHelpers.ps1 not found at '$discoveryHelpersPath'. Cannot continue."
    return
}

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

# =============================================================================
# Configuration: Counter definitions
# =============================================================================
# Property = WMI property name (used for discovery query results)
# PerfCounter = Windows Performance Counter name (used in WUG monitor creation)
# Label = Human-readable label for display/dashboards
$script:CounterDefinitions = [ordered]@{
    TransactionsPersec      = @{ Property = 'TransactionsPersec';      PerfCounter = 'Transactions/sec';              Label = 'Transactions/sec' }
    WriteTransactionsPersec = @{ Property = 'WriteTransactionsPersec'; PerfCounter = 'Write Transactions/sec';        Label = 'Write Transactions/sec' }
    LogBytesFlushedPersec   = @{ Property = 'LogBytesFlushedPersec';   PerfCounter = 'Log Bytes Flushed/sec';         Label = 'Log Bytes Flushed/sec' }
    LogFlushesPersec        = @{ Property = 'LogFlushesPersec';        PerfCounter = 'Log Flushes/sec';               Label = 'Log Flushes/sec' }
    LogFlushWaitsPersec     = @{ Property = 'LogFlushWaitsPersec';     PerfCounter = 'Log Flush Waits/sec';           Label = 'Log Flush Waits/sec' }
    LogFlushWaitTime        = @{ Property = 'LogFlushWaitTime';        PerfCounter = 'Log Flush Wait Time';           Label = 'Log Flush Wait Time (ms)' }
    ActiveTransactions      = @{ Property = 'ActiveTransactions';      PerfCounter = 'Active Transactions';           Label = 'Active Transactions' }
    DataFilesSizeKB         = @{ Property = 'DataFilesSizeKB';         PerfCounter = 'Data File(s) Size (KB)';        Label = 'Data File(s) Size (KB)' }
    LogFilesSizeKB          = @{ Property = 'LogFilesSizeKB';          PerfCounter = 'Log File(s) Size (KB)';         Label = 'Log File(s) Size (KB)' }
    LogFilesUsedSizeKB      = @{ Property = 'LogFilesUsedSizeKB';      PerfCounter = 'Log File(s) Used Size (KB)';    Label = 'Log File(s) Used Size (KB)' }
    PercentLogUsed          = @{ Property = 'PercentLogUsed';          PerfCounter = 'Percent Log Used';              Label = 'Percent Log Used' }
    LogGrowths              = @{ Property = 'LogGrowths';              PerfCounter = 'Log Growths';                   Label = 'Log Growths' }
    LogTruncations          = @{ Property = 'LogTruncations';          PerfCounter = 'Log Truncations';               Label = 'Log Truncations' }
    LogCacheHitRatio        = @{ Property = 'LogCacheHitRatio';        PerfCounter = 'Log Cache Hit Ratio';           Label = 'Log Cache Hit Ratio' }
    LogShrinks              = @{ Property = 'LogShrinks';              PerfCounter = 'Log Shrinks';                   Label = 'Log Shrinks' }
}

# Default counters when -IncludeCounter is not specified
$script:DefaultCounters = @('TransactionsPersec', 'PercentLogUsed')

# Filter to requested counters only
if ($IncludeCounter -and $IncludeCounter -contains 'All') {
    $activeCounters = $script:CounterDefinitions
}
elseif ($IncludeCounter) {
    $activeCounters = [ordered]@{}
    foreach ($key in $IncludeCounter) {
        if ($script:CounterDefinitions.Contains($key)) {
            $activeCounters[$key] = $script:CounterDefinitions[$key]
        }
    }
}
else {
    $activeCounters = [ordered]@{}
    foreach ($key in $script:DefaultCounters) {
        $activeCounters[$key] = $script:CounterDefinitions[$key]
    }
}

# =============================================================================
# Configuration: Instance-level counter definitions (not per-database)
# =============================================================================
# Category maps to a WMI performance class (e.g. SQLServerBufferManager)
# Each counter belongs to a category and specifies the WMI property,
# the Windows Performance Counter name (for WUG), and a display label.
$script:InstanceCounterCategories = [ordered]@{
    BufferManager     = @{ WmiClassSuffix = 'BufferManager';     PerfObjectName = 'Buffer Manager';     HasInstances = $false }
    SQLStatistics     = @{ WmiClassSuffix = 'SQLStatistics';     PerfObjectName = 'SQL Statistics';     HasInstances = $false }
    GeneralStatistics = @{ WmiClassSuffix = 'GeneralStatistics'; PerfObjectName = 'General Statistics'; HasInstances = $false }
    MemoryManager     = @{ WmiClassSuffix = 'MemoryManager';     PerfObjectName = 'Memory Manager';     HasInstances = $false }
    AccessMethods     = @{ WmiClassSuffix = 'AccessMethods';     PerfObjectName = 'Access Methods';     HasInstances = $false }
    Locks             = @{ WmiClassSuffix = 'Locks';             PerfObjectName = 'Locks';              HasInstances = $true; DefaultInstance = '_Total' }
    Latches           = @{ WmiClassSuffix = 'Latches';           PerfObjectName = 'Latches';            HasInstances = $false }
}

$script:InstanceCounterDefinitions = [ordered]@{
    # Buffer Manager -- memory pressure indicators
    BufferCacheHitRatio   = @{ Category = 'BufferManager'; Property = 'Buffercachehitratio';    PerfCounter = 'Buffer cache hit ratio';    Label = 'Buffer Cache Hit Ratio (%)' }
    PageLifeExpectancy    = @{ Category = 'BufferManager'; Property = 'Pagelifeexpectancy';     PerfCounter = 'Page life expectancy';      Label = 'Page Life Expectancy (s)' }
    LazyWritesPersec      = @{ Category = 'BufferManager'; Property = 'LazywritesPersec';       PerfCounter = 'Lazy writes/sec';           Label = 'Lazy Writes/sec' }
    CheckpointPagesPersec = @{ Category = 'BufferManager'; Property = 'CheckpointpagesPersec';  PerfCounter = 'Checkpoint pages/sec';      Label = 'Checkpoint Pages/sec' }
    PageReadsPersec       = @{ Category = 'BufferManager'; Property = 'PagereadsPersec';        PerfCounter = 'Page reads/sec';            Label = 'Page Reads/sec' }
    PageWritesPersec      = @{ Category = 'BufferManager'; Property = 'PagewritesPersec';       PerfCounter = 'Page writes/sec';           Label = 'Page Writes/sec' }
    DatabasePages         = @{ Category = 'BufferManager'; Property = 'Databasepages';          PerfCounter = 'Database pages';            Label = 'Database Pages' }
    # SQL Statistics -- workload volume
    BatchRequestsPersec     = @{ Category = 'SQLStatistics'; Property = 'BatchRequestsPersec';    PerfCounter = 'Batch Requests/sec';        Label = 'Batch Requests/sec' }
    SQLCompilationsPersec   = @{ Category = 'SQLStatistics'; Property = 'SQLCompilationsPersec';  PerfCounter = 'SQL Compilations/sec';      Label = 'SQL Compilations/sec' }
    SQLReCompilationsPersec = @{ Category = 'SQLStatistics'; Property = 'SQLReCompilationsPersec'; PerfCounter = 'SQL Re-Compilations/sec';  Label = 'SQL Re-Compilations/sec' }
    # General Statistics -- connections and blocking
    UserConnections  = @{ Category = 'GeneralStatistics'; Property = 'UserConnections';  PerfCounter = 'User Connections';  Label = 'User Connections' }
    ProcessesBlocked = @{ Category = 'GeneralStatistics'; Property = 'ProcessesBlocked'; PerfCounter = 'Processes blocked'; Label = 'Processes Blocked' }
    LoginsPersec     = @{ Category = 'GeneralStatistics'; Property = 'LoginsPersec';     PerfCounter = 'Logins/sec';        Label = 'Logins/sec' }
    LogoutsPersec    = @{ Category = 'GeneralStatistics'; Property = 'LogoutsPersec';    PerfCounter = 'Logouts/sec';       Label = 'Logouts/sec' }
    # Memory Manager -- SQL Server memory allocation
    TotalServerMemoryKB  = @{ Category = 'MemoryManager'; Property = 'TotalServerMemoryKB';   PerfCounter = 'Total Server Memory (KB)';  Label = 'Total Server Memory (KB)' }
    TargetServerMemoryKB = @{ Category = 'MemoryManager'; Property = 'TargetServerMemoryKB';  PerfCounter = 'Target Server Memory (KB)'; Label = 'Target Server Memory (KB)' }
    MemoryGrantsPending  = @{ Category = 'MemoryManager'; Property = 'MemoryGrantsPending';   PerfCounter = 'Memory Grants Pending';     Label = 'Memory Grants Pending' }
    # Access Methods -- index and scan efficiency
    FullScansPersec        = @{ Category = 'AccessMethods'; Property = 'FullScansPersec';        PerfCounter = 'Full Scans/sec';        Label = 'Full Scans/sec' }
    IndexSearchesPersec    = @{ Category = 'AccessMethods'; Property = 'IndexSearchesPersec';    PerfCounter = 'Index Searches/sec';    Label = 'Index Searches/sec' }
    PageSplitsPersec       = @{ Category = 'AccessMethods'; Property = 'PageSplitsPersec';       PerfCounter = 'Page Splits/sec';       Label = 'Page Splits/sec' }
    ForwardedRecordsPersec = @{ Category = 'AccessMethods'; Property = 'ForwardedRecordsPersec'; PerfCounter = 'Forwarded Records/sec'; Label = 'Forwarded Records/sec' }
    # Locks -- concurrency and deadlocks (_Total instance)
    NumberOfDeadlocksPersec = @{ Category = 'Locks'; Property = 'NumberofDeadlocksPersec'; PerfCounter = 'Number of Deadlocks/sec'; Label = 'Deadlocks/sec' }
    LockWaitsPersec         = @{ Category = 'Locks'; Property = 'LockWaitsPersec';         PerfCounter = 'Lock Waits/sec';          Label = 'Lock Waits/sec' }
    LockTimeoutsPersec      = @{ Category = 'Locks'; Property = 'LockTimeoutsPersec';      PerfCounter = 'Lock Timeouts/sec';       Label = 'Lock Timeouts/sec' }
    LockRequestsPersec      = @{ Category = 'Locks'; Property = 'LockRequestsPersec';      PerfCounter = 'Lock Requests/sec';       Label = 'Lock Requests/sec' }
    AverageLockWaitTimeMs   = @{ Category = 'Locks'; Property = 'AverageWaitTimems';       PerfCounter = 'Average Wait Time (ms)';  Label = 'Avg Lock Wait Time (ms)' }
    # Latches -- internal contention
    LatchWaitsPersec       = @{ Category = 'Latches'; Property = 'LatchWaitsPersec';       PerfCounter = 'Latch Waits/sec';               Label = 'Latch Waits/sec' }
    AverageLatchWaitTimeMs = @{ Category = 'Latches'; Property = 'AverageLatchWaitTimems'; PerfCounter = 'Average Latch Wait Time (ms)';  Label = 'Avg Latch Wait Time (ms)' }
}

# Resolve active instance counters from -IncludeInstanceCounter parameter
$activeInstanceCounters = [ordered]@{}
if ($IncludeInstanceCounter) {
    if ($IncludeInstanceCounter -contains 'All') {
        $activeInstanceCounters = $script:InstanceCounterDefinitions
    }
    else {
        foreach ($key in $IncludeInstanceCounter) {
            # Check if it is a category name -- expand to all counters in that category
            if ($script:InstanceCounterCategories.Contains($key)) {
                foreach ($ck in $script:InstanceCounterDefinitions.Keys) {
                    if ($script:InstanceCounterDefinitions[$ck].Category -eq $key) {
                        $activeInstanceCounters[$ck] = $script:InstanceCounterDefinitions[$ck]
                    }
                }
            }
            elseif ($script:InstanceCounterDefinitions.Contains($key)) {
                $activeInstanceCounters[$key] = $script:InstanceCounterDefinitions[$key]
            }
        }
    }
}

# Determine which WMI categories we actually need to query
$activeInstanceCategories = [ordered]@{}
foreach ($ck in $activeInstanceCounters.Keys) {
    $cat = $activeInstanceCounters[$ck].Category
    if (-not $activeInstanceCategories.Contains($cat)) {
        $activeInstanceCategories[$cat] = $script:InstanceCounterCategories[$cat]
    }
}

if ($activeCounters.Count -eq 0 -and $activeInstanceCounters.Count -eq 0) {
    Write-Error "No valid counters selected. Exiting."
    return
}

# =============================================================================
# Preflight: WUG connection (only needed when resolving devices from WUG or pushing)
# =============================================================================
$needsWUG = (-not $Target) -or ($Action -eq 'PushToWUG') -or ($Action -eq 'DashboardAndPush')
if ($needsWUG) {
    if (-not $global:WUGBearerHeaders -or -not $global:WhatsUpServerBaseURI) {
        if ($WUGCredential) {
            $wugUri = if ($WUGServer) { $WUGServer } else { 'https://localhost:9644' }
            try {
                Connect-WUGServer -serverUri $wugUri -Credential $WUGCredential -IgnoreSSLErrors
                Write-Host "Connected to WhatsUp Gold." -ForegroundColor Green
            }
            catch {
                Write-Error "Failed to connect to WhatsUp Gold: $_"
                return
            }
        }
        else {
            Write-Host "Not connected to WhatsUp Gold. Resolving from vault..." -ForegroundColor Yellow
            $wugSplat = @{ Name = 'WUG.Server'; CredType = 'WUGServer'; ProviderLabel = 'WhatsUp Gold' }
            if ($NonInteractive) { $wugSplat.NonInteractive = $true }
            $wugResolved = Resolve-DiscoveryCredential @wugSplat
            if (-not $wugResolved) {
                Write-Error "No WhatsUp Gold server credentials available. Run Connect-WUGServer or configure the vault."
                return
            }
            $wugConnSplat = @{}
            if ($wugResolved -is [hashtable]) {
                $wugConnSplat.Server = $wugResolved.Server
                if ($wugResolved.Port)       { $wugConnSplat.Port = $wugResolved.Port }
                if ($wugResolved.Protocol)   { $wugConnSplat.Protocol = $wugResolved.Protocol }
                if ($wugResolved.Credential) { $wugConnSplat.Credential = $wugResolved.Credential }
                if ($wugResolved.IgnoreSSL)  { $wugConnSplat.IgnoreSSL = $true }
            }
            elseif ($WUGServer) {
                $wugConnSplat.Server = $WUGServer
            }
            try {
                Connect-WUGServer @wugConnSplat
                Write-Host "Connected to WhatsUp Gold." -ForegroundColor Green
            }
            catch {
                Write-Error "Failed to connect to WhatsUp Gold: $_"
                return
            }
        }
    }
}

# =============================================================================
# Resolve WMI credentials (vault-backed, multi-credential support)
# =============================================================================
$credentialList = [System.Collections.Generic.List[PSCredential]]::new()

if ($Credential) {
    foreach ($c in $Credential) { $credentialList.Add($c) }
}
else {
    $credIndex = 1
    $wmiVaultName = "Windows.WMI.Credential.$credIndex"
    $credSplat = @{ Name = $wmiVaultName; CredType = 'PSCredential'; ProviderLabel = "Windows WMI #$credIndex" }
    if ($NonInteractive) { $credSplat.NonInteractive = $true }
    $firstCred = Resolve-DiscoveryCredential @credSplat
    if (-not $firstCred) {
        Write-Error "No WMI credentials available. Provide -Credential or configure the vault."
        return
    }
    $credentialList.Add($firstCred)

    if (-not $NonInteractive) {
        while ($true) {
            Write-Host ""
            Write-Host " Credential #$($credentialList.Count) loaded: $($credentialList[-1].UserName)" -ForegroundColor Gray
            Write-Host " Add another credential to try if this one fails on some servers?" -ForegroundColor Cyan
            $addMore = Read-Host -Prompt " [Y]es / [N]o (default: N)"
            if ($addMore -notmatch '^[Yy]') { break }
            $credIndex++
            $wmiVaultName = "Windows.WMI.Credential.$credIndex"
            $credSplat = @{ Name = $wmiVaultName; CredType = 'PSCredential'; ProviderLabel = "Windows WMI #$credIndex" }
            $nextCred = Resolve-DiscoveryCredential @credSplat
            if ($nextCred) { $credentialList.Add($nextCred) } else { break }
        }
    }
    else {
        while ($true) {
            $credIndex++
            $wmiVaultName = "Windows.WMI.Credential.$credIndex"
            $credSplat = @{ Name = $wmiVaultName; CredType = 'PSCredential'; NonInteractive = $true }
            $nextCred = Resolve-DiscoveryCredential @credSplat
            if ($nextCred) { $credentialList.Add($nextCred) } else { break }
        }
    }
}

Write-Host ""
Write-Host " Credentials to try ($($credentialList.Count)):" -ForegroundColor Cyan
for ($ci = 0; $ci -lt $credentialList.Count; $ci++) {
    Write-Host " [$($ci + 1)] $($credentialList[$ci].UserName)" -ForegroundColor Gray
}

# =============================================================================
# STEP 1: Resolve target devices
# =============================================================================
Write-Host ""
Write-Host "=== Microsoft SQL Server Discovery ===" -ForegroundColor Cyan
Write-Host ""

$standaloneMode = $false
if ($Target) {
    $standaloneMode = $true
    Write-Host "Standalone mode: scanning $($Target.Count) specified host(s)..." -ForegroundColor Cyan
    $devices = @()
    $targetId = 0
    foreach ($t in $Target) {
        $targetId++
        $devices += [PSCustomObject]@{
            id             = "target-$targetId"
            displayName    = $t
            hostName       = $t
            networkAddress = $t
            role           = 'Windows'
        }
    }
}
elseif ($DeviceId) {
    Write-Host "Fetching $($DeviceId.Count) specified device(s) from WhatsUp Gold..." -ForegroundColor Cyan
    $devices = @()
    foreach ($dId in $DeviceId) {
        try {
            $dev = Get-WUGDevice -DeviceId $dId
            if ($dev) { $devices += $dev }
        }
        catch {
            Write-Warning "Could not retrieve device ID ${dId}: $_"
        }
    }
}
else {
    # Auto-resolve device group
    $resolvedGroupId = $null

    if ($DeviceGroupId) {
        $resolvedGroupId = $DeviceGroupId
        Write-Host "Using explicit device group ID: $resolvedGroupId" -ForegroundColor Cyan
    }
    elseif ($DeviceGroupSearch) {
        Write-Host "Searching for device group matching '$DeviceGroupSearch'..." -ForegroundColor Cyan
        try {
            $matchingGroups = @(Get-WUGDeviceGroup -SearchValue $DeviceGroupSearch)
            if ($matchingGroups.Count -eq 1) {
                $resolvedGroupId = $matchingGroups[0].id
                Write-Host " Found group: '$($matchingGroups[0].name)' (ID: $resolvedGroupId)" -ForegroundColor Green
            }
            elseif ($matchingGroups.Count -gt 1) {
                Write-Host " Found $($matchingGroups.Count) matching groups:" -ForegroundColor Cyan
                for ($gi = 0; $gi -lt $matchingGroups.Count; $gi++) {
                    $g = $matchingGroups[$gi]
                    $devCount = if ($g.deviceCount) { $g.deviceCount } else { '?' }
                    Write-Host " [$($gi + 1)] $($g.name) (ID: $($g.id), Devices: $devCount)" -ForegroundColor White
                }
                if (-not $NonInteractive) {
                    Write-Host ""
                    $groupChoice = Read-Host -Prompt " Select group number [default: 1]"
                    if (-not $groupChoice) { $groupChoice = '1' }
                    $groupIdx = [int]$groupChoice - 1
                    if ($groupIdx -ge 0 -and $groupIdx -lt $matchingGroups.Count) {
                        $resolvedGroupId = $matchingGroups[$groupIdx].id
                        Write-Host " Selected: '$($matchingGroups[$groupIdx].name)' (ID: $resolvedGroupId)" -ForegroundColor Green
                    }
                    else {
                        Write-Warning "Invalid selection. Using first match."
                        $resolvedGroupId = $matchingGroups[0].id
                    }
                }
                else {
                    $resolvedGroupId = $matchingGroups[0].id
                    Write-Host " Auto-selected: '$($matchingGroups[0].name)' (ID: $resolvedGroupId)" -ForegroundColor Gray
                }
            }
            else {
                Write-Host " No groups matching '$DeviceGroupSearch'. Falling back to all devices." -ForegroundColor Yellow
            }
        }
        catch {
            Write-Warning "Failed to search device groups: $_. Falling back to all devices."
        }
    }

    if (-not $resolvedGroupId) { $resolvedGroupId = '-1' }
    Write-Host "Fetching devices from group $resolvedGroupId..." -ForegroundColor Cyan
    $devices = @(Get-WUGDevice -DeviceGroupID $resolvedGroupId)
}

if (-not $devices -or $devices.Count -eq 0) {
    Write-Error "No devices found. Exiting."
    return
}

Write-Host "Found $($devices.Count) device(s)." -ForegroundColor Green
Write-Host ""

# =============================================================================
# STEP 2: Scan each device via WMI to discover SQL instances and databases
# =============================================================================
# Structure: $deviceDatabases[deviceId] = @( @{ Instance='MSSQLSERVER'; Database='master'; WmiClass='...' }, ... )
$deviceDatabases = @{}
$allDbKeys = @{}          # "InstanceName\DatabaseName" -> $true (dedup)
$deviceInfo = @{}         # deviceId -> @{ Name; IP }
$deviceDbStats = @{}      # deviceId -> @{ "Instance\DB" -> @{ CounterKey = value; ... } }
$instanceWmiClassMap = @{} # "InstanceName" -> WMI class name (for monitor creation)

# Instance-level counter data structures
$deviceInstances = @{}     # deviceId -> @( @{ Instance='MSSQLSERVER' }, ... )
$allInstanceKeys = @{}     # "InstanceName" -> $true (dedup)
$deviceInstanceStats = @{} # deviceId -> @{ "InstanceName" -> @{ Category -> @{ CounterKey = value } } }

$scanIndex = 0
foreach ($dev in $devices) {
    $scanIndex++
    $devId   = $dev.id
    $devName = if ($dev.displayName) { $dev.displayName } else { $dev.hostName }
    $devIP   = $dev.networkAddress

    if (-not $devIP) {
        Write-Warning "Device '$devName' (ID: $devId) has no network address. Skipping."
        continue
    }

    $deviceInfo[$devId] = @{ Name = $devName; IP = $devIP }

    $pct = [Math]::Round(($scanIndex / $devices.Count) * 100)
    Write-Progress -Activity "Scanning SQL Server instances via WMI" `
        -Status "$devName ($devIP) [$scanIndex of $($devices.Count)]" `
        -PercentComplete $pct

    Write-Host " [$scanIndex/$($devices.Count)] Scanning $devName ($devIP)..." -ForegroundColor Gray

    # Try each credential in order
    $scanSuccess = $false
    for ($ci = 0; $ci -lt $credentialList.Count; $ci++) {
        $tryCred = $credentialList[$ci]
        if ($credentialList.Count -gt 1 -and $ci -gt 0) {
            Write-Host " Retrying with credential #$($ci + 1) ($($tryCred.UserName))..." -ForegroundColor DarkGray
        }

        try {
            $isLocal = ($devIP -eq 'localhost' -or $devIP -eq '127.0.0.1' -or $devIP -eq '::1' -or $devIP -eq $env:COMPUTERNAME -or $devIP -eq '.')
            $wmiSplat = @{ ErrorAction = 'Stop' }
            if (-not $isLocal) { $wmiSplat.ComputerName = $devIP; $wmiSplat.Credential = $tryCred }

            # --- Step 2a: Discover SQL Server services ---
            $sqlServices = @(Get-WmiObject -Class Win32_Service @wmiSplat |
                Where-Object { $_.Name -match '^MSSQL(\$|SERVER)' -and $_.State -eq 'Running' })

            if (-not $sqlServices -or $sqlServices.Count -eq 0) {
                Write-Host " No running SQL Server services found." -ForegroundColor Yellow
                $scanSuccess = $true
                break
            }

            # Parse instance names from service names
            $instanceNames = @()
            foreach ($svc in $sqlServices) {
                if ($svc.Name -eq 'MSSQLSERVER') {
                    $instanceNames += 'MSSQLSERVER'
                }
                elseif ($svc.Name -match '^MSSQL\$(.+)$') {
                    $instanceNames += $Matches[1]
                }
            }

            # Apply instance exclusion filter
            $filteredInstances = @()
            foreach ($inst in $instanceNames) {
                $excluded = $false
                foreach ($pattern in $ExcludeInstance) {
                    if ($inst -like "*$pattern*") { $excluded = $true; break }
                }
                if (-not $excluded) { $filteredInstances += $inst }
            }

            if ($filteredInstances.Count -eq 0) {
                Write-Host " All SQL instances excluded by filter." -ForegroundColor Yellow
                $scanSuccess = $true
                break
            }

            Write-Host " SQL instances found: $($filteredInstances -join ', ')" -ForegroundColor Green

            # --- Step 2b: Query databases per instance ---
            $deviceDbs = @()
            foreach ($instanceName in $filteredInstances) {
                # Build the WMI class name based on instance
                # Default instance: Win32_PerfFormattedData_MSSQLSERVER_SQLServerDatabases
                # Named instance: Win32_PerfFormattedData_MSSQL<NAME>_MSSQL<NAME>Databases
                if ($instanceName -eq 'MSSQLSERVER') {
                    $wmiClass = 'Win32_PerfFormattedData_MSSQLSERVER_SQLServerDatabases'
                }
                else {
                    $wmiClass = "Win32_PerfFormattedData_MSSQL${instanceName}_MSSQL${instanceName}Databases"
                }

                $instanceWmiClassMap[$instanceName] = $wmiClass

                # Build the perfmon object path for WUG monitor creation
                # (different from WMI class -- this is what WUG expects)
                if ($instanceName -eq 'MSSQLSERVER') {
                    $instancePerfObjectMap = 'SQLServer:Databases'
                }
                else {
                    $instancePerfObjectMap = 'MSSQL$' + $instanceName + ':Databases'
                }

                try {
                    $wmiQuery = "Select * from $wmiClass"
                    $dbData = @(Get-WmiObject -Query $wmiQuery @wmiSplat |
                        Where-Object { $_.Name -and $_.Name -ne '' })

                    foreach ($db in $dbData) {
                        $dbName = $db.Name

                        # Apply database exclusion filter
                        $excluded = $false
                        foreach ($pattern in $ExcludeDatabase) {
                            if ($dbName -like "*$pattern*") { $excluded = $true; break }
                        }
                        if ($excluded) { continue }

                        $dbKey = "$instanceName\$dbName"
                        $allDbKeys[$dbKey] = $true

                        $deviceDbs += @{
                            Instance   = $instanceName
                            Database   = $dbName
                            WmiClass   = $wmiClass
                            PerfObject = $instancePerfObjectMap
                            DbKey      = $dbKey
                        }

                        # Capture counter values for dashboard/export
                        if (-not $deviceDbStats.ContainsKey($devId)) {
                            $deviceDbStats[$devId] = @{}
                        }
                        $snap = [ordered]@{}
                        foreach ($ck in $activeCounters.Keys) {
                            $prop = $activeCounters[$ck].Property
                            $val  = $db.$prop
                            if ($null -ne $val) { $snap[$ck] = [double]$val }
                        }
                        $deviceDbStats[$devId][$dbKey] = $snap
                    }
                }
                catch {
                    Write-Warning " Could not query $wmiClass on ${devIP}: $($_.Exception.Message)"
                }
            }

            if ($deviceDbs.Count -gt 0) {
                $deviceDatabases[$devId] = $deviceDbs
                $dbNames = ($deviceDbs | ForEach-Object { $_.Database }) | Sort-Object -Unique
                Write-Host " Databases: $($dbNames -join ', ')" -ForegroundColor Green
            }
            else {
                Write-Host " No databases found after filtering." -ForegroundColor Yellow
            }

            # --- Step 2c: Query instance-level performance counters ---
            if ($activeInstanceCounters.Count -gt 0) {
                $devInstanceEntries = @()
                foreach ($instanceName in $filteredInstances) {
                    $allInstanceKeys[$instanceName] = $true
                    $devInstanceEntries += @{ Instance = $instanceName }

                    if (-not $deviceInstanceStats.ContainsKey($devId)) {
                        $deviceInstanceStats[$devId] = @{}
                    }
                    if (-not $deviceInstanceStats[$devId].ContainsKey($instanceName)) {
                        $deviceInstanceStats[$devId][$instanceName] = [ordered]@{}
                    }

                    foreach ($catName in $activeInstanceCategories.Keys) {
                        $catDef = $activeInstanceCategories[$catName]
                        $suffix = $catDef.WmiClassSuffix

                        # Build WMI class name for this category
                        if ($instanceName -eq 'MSSQLSERVER') {
                            $catWmiClass = "Win32_PerfFormattedData_MSSQLSERVER_SQLServer$suffix"
                        }
                        else {
                            $catWmiClass = "Win32_PerfFormattedData_MSSQL${instanceName}_MSSQL${instanceName}$suffix"
                        }

                        try {
                            $catQuery = "Select * from $catWmiClass"
                            if ($catDef.HasInstances) {
                                $defInst = $catDef.DefaultInstance
                                $catResult = Get-WmiObject -Query $catQuery @wmiSplat |
                                    Where-Object { $_.Name -eq $defInst } | Select-Object -First 1
                            }
                            else {
                                $catResult = Get-WmiObject -Query $catQuery @wmiSplat | Select-Object -First 1
                            }

                            if ($catResult) {
                                foreach ($ck in $activeInstanceCounters.Keys) {
                                    $cDef = $activeInstanceCounters[$ck]
                                    if ($cDef.Category -ne $catName) { continue }
                                    $val = $catResult.($cDef.Property)
                                    if ($null -ne $val) {
                                        $deviceInstanceStats[$devId][$instanceName][$ck] = [double]$val
                                    }
                                }
                            }
                        }
                        catch {
                            Write-Verbose " Could not query $catWmiClass on ${devIP}: $($_.Exception.Message)"
                        }
                    }
                }

                if ($devInstanceEntries.Count -gt 0) {
                    $deviceInstances[$devId] = $devInstanceEntries
                    $instCounterCount = 0
                    if ($deviceInstanceStats.ContainsKey($devId)) {
                        foreach ($iKey in $deviceInstanceStats[$devId].Keys) {
                            $instCounterCount += $deviceInstanceStats[$devId][$iKey].Count
                        }
                    }
                    Write-Host " Instance counters collected: $instCounterCount value(s)" -ForegroundColor Green
                }
            }

            $scanSuccess = $true
            break
        }
        catch [System.UnauthorizedAccessException] {
            if ($ci -lt ($credentialList.Count - 1)) { continue }
            Write-Host " Access denied (all $($credentialList.Count) credential(s) failed)." -ForegroundColor Red
        }
        catch [System.Runtime.InteropServices.COMException] {
            $comMsg = $_.Exception.Message
            if ($comMsg -match 'Access is denied|0x80070005') {
                if ($ci -lt ($credentialList.Count - 1)) { continue }
                Write-Host " Access denied (all $($credentialList.Count) credential(s) failed)." -ForegroundColor Red
            }
            else {
                Write-Host " WMI/RPC unavailable: $comMsg" -ForegroundColor Red
                break
            }
        }
        catch {
            Write-Host " Failed: $($_.Exception.Message)" -ForegroundColor Red
            break
        }
    }
}

Write-Progress -Activity "Scanning SQL Server instances via WMI" -Completed

if ($deviceDatabases.Count -eq 0 -and $deviceInstances.Count -eq 0) {
    Write-Warning "No SQL Server databases or instances discovered on any device. Nothing to do."
    return
}

$sortedDbKeys = @($allDbKeys.Keys | Sort-Object)
$sortedInstanceKeys = @($allInstanceKeys.Keys | Sort-Object)

Write-Host ""
Write-Host "--- Discovery Summary ---" -ForegroundColor Cyan
Write-Host " Devices scanned: $($devices.Count)" -ForegroundColor White
$sqlDeviceCount = ($deviceDatabases.Keys + $deviceInstances.Keys) | Sort-Object -Unique
Write-Host " Devices with SQL: $(@($sqlDeviceCount).Count)" -ForegroundColor White
if ($activeCounters.Count -gt 0) {
    Write-Host " Unique instance\db: $($sortedDbKeys.Count)" -ForegroundColor White
    Write-Host " Counters per database: $($activeCounters.Count)" -ForegroundColor White
}
if ($activeInstanceCounters.Count -gt 0) {
    Write-Host " SQL instances found: $($sortedInstanceKeys.Count)" -ForegroundColor White
    Write-Host " Instance-level counters: $($activeInstanceCounters.Count)" -ForegroundColor White
}
Write-Host ""

# Show per-device breakdown
if ($deviceDatabases.Count -gt 0) {
    Write-Host " Per-device database map:" -ForegroundColor White
    foreach ($devId in ($deviceDatabases.Keys | Sort-Object)) {
        $info = $deviceInfo[$devId]
        $dbs = ($deviceDatabases[$devId] | ForEach-Object { "$($_.Instance)\$($_.Database)" }) -join ', '
        Write-Host " $($info.Name) ($($info.IP)): $dbs" -ForegroundColor Gray
    }
    Write-Host ""
}

$totalMonitors = $sortedDbKeys.Count * $activeCounters.Count
$totalAssignments = 0
foreach ($devId in $deviceDatabases.Keys) {
    $totalAssignments += $deviceDatabases[$devId].Count * $activeCounters.Count
}

# Instance-level monitor totals
$totalInstanceMonitors = $sortedInstanceKeys.Count * $activeInstanceCounters.Count
$totalInstanceAssignments = 0
foreach ($devId in $deviceInstances.Keys) {
    $totalInstanceAssignments += $deviceInstances[$devId].Count * $activeInstanceCounters.Count
}

$totalMonitors += $totalInstanceMonitors
$totalAssignments += $totalInstanceAssignments

Write-Host " Monitors to create (library): up to $totalMonitors" -ForegroundColor White
Write-Host " Assignments to make (devices): up to $totalAssignments" -ForegroundColor White
if ($activeInstanceCounters.Count -gt 0) {
    Write-Host " (database-level: $($sortedDbKeys.Count * $activeCounters.Count) monitors, instance-level: $totalInstanceMonitors monitors)" -ForegroundColor Gray
}
Write-Host ""

if ($DryRun) {
    Write-Host "[DRY RUN] Listing planned monitors:" -ForegroundColor Yellow
    Write-Host ""
    if ($activeCounters.Count -gt 0) {
        Write-Host " --- Database-level monitors ---" -ForegroundColor Cyan
        foreach ($dbKey in $sortedDbKeys) {
            foreach ($counterKey in $activeCounters.Keys) {
                $def = $activeCounters[$counterKey]
                $monName = "${NamePrefix} - ${dbKey} - $($def.Label)"
                $assignTo = @()
                foreach ($devId in $deviceDatabases.Keys) {
                    $match = $deviceDatabases[$devId] | Where-Object { $_.DbKey -eq $dbKey }
                    if ($match) { $assignTo += "$($deviceInfo[$devId].Name)(#$devId)" }
                }
                Write-Host " [CREATE] $monName" -ForegroundColor Gray
                Write-Host " Assign to: $($assignTo -join ', ')" -ForegroundColor DarkGray
            }
        }
    }
    if ($activeInstanceCounters.Count -gt 0) {
        Write-Host ""
        Write-Host " --- Instance-level monitors ---" -ForegroundColor Cyan
        foreach ($instKey in $sortedInstanceKeys) {
            foreach ($counterKey in $activeInstanceCounters.Keys) {
                $def = $activeInstanceCounters[$counterKey]
                $monName = "${NamePrefix} - ${instKey} - $($def.Label)"
                $assignTo = @()
                foreach ($devId in $deviceInstances.Keys) {
                    $match = $deviceInstances[$devId] | Where-Object { $_.Instance -eq $instKey }
                    if ($match) { $assignTo += "$($deviceInfo[$devId].Name)(#$devId)" }
                }
                Write-Host " [CREATE] $monName" -ForegroundColor Gray
                Write-Host " Assign to: $($assignTo -join ', ')" -ForegroundColor DarkGray
            }
        }
    }
    Write-Host ""
    Write-Host "[DRY RUN] No changes made." -ForegroundColor Yellow
    return
}

# =============================================================================
# STEP 3: Action routing (menu or -Action parameter)
# =============================================================================
if ($OutputPath) {
    $OutputDir = $OutputPath
}
else {
    $OutputDir = Join-Path $env:LOCALAPPDATA 'WhatsUpGoldPS\DiscoveryHelpers\Output'
}
if (-not (Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null }

$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 with the discovered databases?" -ForegroundColor Cyan
    if ($standaloneMode) {
        Write-Host " [1] Push monitors to WhatsUp Gold (not available in standalone mode)" -ForegroundColor DarkGray
    }
    else {
        Write-Host " [1] Push monitors to WhatsUp Gold (creates library monitors + assigns)"
    }
    Write-Host " [2] Export to JSON file"
    Write-Host " [3] Export to CSV file"
    Write-Host " [4] Show full table in console"
    Write-Host " [5] Generate HTML dashboard (SQL database inventory)"
    Write-Host " [6] Exit (do nothing)"
    if (-not $standaloneMode) {
        Write-Host " [7] Dashboard + Push to WUG"
    }
    Write-Host ""
    $choice = Read-Host -Prompt "Choice [1-7]"
}

# Handle DashboardAndPush: run Dashboard then PushToWUG sequentially
if ($choice -eq '7') {
    $actionsToRun = @('5', '1')
}
else {
    $actionsToRun = @($choice)
}

# Build export-friendly data for JSON/CSV/Table/Dashboard
$exportData = @()
foreach ($devId in ($deviceDatabases.Keys | Sort-Object)) {
    $info = $deviceInfo[$devId]
    foreach ($dbEntry in $deviceDatabases[$devId]) {
        $row = [ordered]@{
            DeviceId = $devId
            Host     = $info.Name
            IP       = $info.IP
            Instance = $dbEntry.Instance
            Database = $dbEntry.Database
            WmiClass = $dbEntry.WmiClass
        }
        # Add counter values
        $dbKey = $dbEntry.DbKey
        $snap = $null
        if ($deviceDbStats.ContainsKey($devId) -and $deviceDbStats[$devId].ContainsKey($dbKey)) {
            $snap = $deviceDbStats[$devId][$dbKey]
        }
        foreach ($ck in $activeCounters.Keys) {
            $label = $activeCounters[$ck].Label
            if ($snap -and $snap.Contains($ck)) {
                $row[$label] = $snap[$ck]
            }
            else {
                $row[$label] = $null
            }
        }
        $exportData += [PSCustomObject]$row
    }
}

# Build instance-level export data
$instanceExportData = @()
foreach ($devId in ($deviceInstances.Keys | Sort-Object)) {
    $info = $deviceInfo[$devId]
    foreach ($instEntry in $deviceInstances[$devId]) {
        $instName = $instEntry.Instance
        $row = [ordered]@{
            DeviceId = $devId
            Host     = $info.Name
            IP       = $info.IP
            Instance = $instName
            Type     = 'Instance'
        }
        $snap = $null
        if ($deviceInstanceStats.ContainsKey($devId) -and $deviceInstanceStats[$devId].ContainsKey($instName)) {
            $snap = $deviceInstanceStats[$devId][$instName]
        }
        foreach ($ck in $activeInstanceCounters.Keys) {
            $label = $activeInstanceCounters[$ck].Label
            if ($snap -and $snap.Contains($ck)) {
                $row[$label] = $snap[$ck]
            }
            else {
                $row[$label] = $null
            }
        }
        $instanceExportData += [PSCustomObject]$row
    }
}

foreach ($currentChoice in $actionsToRun) {
switch ($currentChoice) {
    '1' {
        # PushToWUG
        if ($standaloneMode) {
            Write-Warning "Push to WUG requires devices resolved from WhatsUp Gold. Omit -Target or use -DeviceId."
            continue
        }

        Write-Host "Checking existing performance monitors in WUG library..." -ForegroundColor Cyan

        $existingMonitors = @(Get-WUGPerformanceMonitor -Search $NamePrefix -View 'info')
        $existingLookup = @{}
        foreach ($mon in $existingMonitors) {
            $existingLookup[$mon.Name] = $mon
        }

        Write-Host " Found $($existingMonitors.Count) existing monitor(s) matching prefix '$NamePrefix'." -ForegroundColor Gray

        # Pre-fetch existing monitor assignments per device
        Write-Host "Checking existing device monitor assignments..." -ForegroundColor Cyan
        $deviceExistingMonitors = @{}
        foreach ($devId in $deviceDatabases.Keys) {
            $deviceExistingMonitors[$devId] = @{}
            try {
                $assigned = @(Get-WUGPerformanceMonitor -DeviceId $devId -Search $NamePrefix -View 'basic')
                foreach ($a in $assigned) {
                    $typeId = if ($a.MonitorTypeId) { $a.MonitorTypeId } elseif ($a.monitorTypeId) { $a.monitorTypeId } else { $null }
                    if ($typeId) { $deviceExistingMonitors[$devId]["$typeId"] = $true }
                }
            }
            catch {
                Write-Verbose " Could not check existing monitors for device ${devId}: $_"
            }
        }
        Write-Host " Done." -ForegroundColor Gray
        Write-Host ""

        # --- Create library monitors and assign to devices --------------------
        $createdCount         = 0
        $skippedCount         = 0
        $assignedCount        = 0
        $alreadyAssignedCount = 0
        $failedCount          = 0

        $monitorIdCache = @{}
        foreach ($mon in $existingMonitors) {
            $monitorIdCache[$mon.Name] = if ($mon.MonitorId) { $mon.MonitorId } else { $mon.Id }
        }

        $totalSteps = ($sortedDbKeys.Count * $activeCounters.Count) + ($sortedInstanceKeys.Count * $activeInstanceCounters.Count)
        $stepIndex  = 0

        foreach ($dbKey in $sortedDbKeys) {
            # Resolve perfmon object path and instance (database name) for this dbKey
            $parts = $dbKey -split '\\', 2
            $instanceName = $parts[0]
            $databaseName = $parts[1]

            # Get perfmon object from first matching device entry
            $perfObject = $null
            foreach ($devId2 in $deviceDatabases.Keys) {
                $matchEntry = $deviceDatabases[$devId2] | Where-Object { $_.DbKey -eq $dbKey } | Select-Object -First 1
                if ($matchEntry) { $perfObject = $matchEntry.PerfObject; break }
            }

            foreach ($counterKey in $activeCounters.Keys) {
                $stepIndex++
                $def     = $activeCounters[$counterKey]
                $monName = "${NamePrefix} - ${dbKey} - $($def.Label)"

                $pct = [Math]::Round(($stepIndex / $totalSteps) * 100)
                Write-Progress -Activity "Creating SQL Server monitors" `
                    -Status "$monName [$stepIndex of $totalSteps]" `
                    -PercentComplete $pct

                if ($monitorIdCache.ContainsKey($monName)) {
                    $libId = $monitorIdCache[$monName]
                    Write-Verbose "Monitor '$monName' already exists (ID: $libId). Skipping creation."
                    $skippedCount++
                }
                else {
                    Write-Host " Creating: $monName" -ForegroundColor White
                    try {
                        $result = Add-WUGPerformanceMonitor `
                            -Type WmiFormatted `
                            -Name $monName `
                            -WmiFormattedRelativePath $perfObject `
                            -WmiFormattedPropertyName $def.PerfCounter `
                            -WmiFormattedDisplayname "$($def.PerfCounter) ($databaseName)" `
                            -WmiFormattedInstanceName $databaseName `
                            -WmiFormattedTimeout $WmiTimeout

                        if ($result -and $result.data -and $result.data.idMap) {
                            $libId = $result.data.idMap.resultId
                        }
                        elseif ($result -match 'library ID:\s*(\d+)') {
                            $libId = $Matches[1]
                        }
                        else {
                            $refetch = @(Get-WUGPerformanceMonitor -Search $monName -View 'info')
                            $match = $refetch | Where-Object { $_.Name -eq $monName } | Select-Object -First 1
                            if ($match) {
                                $libId = if ($match.MonitorId) { $match.MonitorId } else { $match.Id }
                            }
                            else {
                                Write-Warning " Could not determine library ID for '$monName'. Skipping."
                                $failedCount++
                                continue
                            }
                        }

                        $monitorIdCache[$monName] = $libId
                        $createdCount++
                        Write-Host " Created (ID: $libId)" -ForegroundColor Green
                    }
                    catch {
                        Write-Error " Failed to create '$monName': $_"
                        $failedCount++
                        continue
                    }
                }

                # Assign to devices that have this database
                $libId = $monitorIdCache[$monName]
                foreach ($devId in $deviceDatabases.Keys) {
                    $match = $deviceDatabases[$devId] | Where-Object { $_.DbKey -eq $dbKey }
                    if ($match) {
                        $info = $deviceInfo[$devId]

                        if ($deviceExistingMonitors.ContainsKey($devId) -and $deviceExistingMonitors[$devId].ContainsKey("$libId")) {
                            Write-Verbose " '$monName' already assigned to $($info.Name). Skipping."
                            $alreadyAssignedCount++
                            continue
                        }

                        Write-Verbose " Assigning '$monName' to $($info.Name) (ID: $devId)"
                        try {
                            Add-WUGPerformanceMonitorToDevice `
                                -DeviceId $devId `
                                -MonitorId $libId `
                                -PollingIntervalMinutes $PollingIntervalMinutes `
                                -Enabled "true"
                            $assignedCount++
                            if ($deviceExistingMonitors.ContainsKey($devId)) {
                                $deviceExistingMonitors[$devId]["$libId"] = $true
                            }
                        }
                        catch {
                            $errMsg = $_.Exception.Message
                            if ($errMsg -match '409|already assigned|already exists|duplicate') {
                                Write-Host " Already assigned: $monName -> $($info.Name)" -ForegroundColor DarkGray
                                $alreadyAssignedCount++
                            }
                            else {
                                Write-Warning " Failed to assign '$monName' to $($info.Name): $errMsg"
                                $failedCount++
                            }
                        }
                    }
                }
            }
        }

        # --- Create and assign instance-level monitors ---
        foreach ($instKey in $sortedInstanceKeys) {
            foreach ($counterKey in $activeInstanceCounters.Keys) {
                $stepIndex++
                $def     = $activeInstanceCounters[$counterKey]
                $catName = $def.Category
                $catDef  = $script:InstanceCounterCategories[$catName]
                $monName = "${NamePrefix} - ${instKey} - $($def.Label)"

                # Build the PerfObject path for WUG
                if ($instKey -eq 'MSSQLSERVER') {
                    $instPerfObject = "SQLServer:$($catDef.PerfObjectName)"
                }
                else {
                    $instPerfObject = 'MSSQL$' + $instKey + ':' + $catDef.PerfObjectName
                }

                # Determine WUG instance name (empty for non-instanced objects)
                if ($catDef.HasInstances) {
                    $wugInstanceName = $catDef.DefaultInstance
                }
                else {
                    $wugInstanceName = ''
                }

                $pct = [Math]::Round(($stepIndex / $totalSteps) * 100)
                Write-Progress -Activity "Creating SQL Server monitors" `
                    -Status "$monName [$stepIndex of $totalSteps]" `
                    -PercentComplete $pct

                if ($monitorIdCache.ContainsKey($monName)) {
                    $libId = $monitorIdCache[$monName]
                    Write-Verbose "Monitor '$monName' already exists (ID: $libId). Skipping creation."
                    $skippedCount++
                }
                else {
                    Write-Host " Creating: $monName" -ForegroundColor White
                    $displayLabel = if ($wugInstanceName) { "$($def.PerfCounter) ($wugInstanceName)" } else { $def.PerfCounter }
                    try {
                        $result = Add-WUGPerformanceMonitor `
                            -Type WmiFormatted `
                            -Name $monName `
                            -WmiFormattedRelativePath $instPerfObject `
                            -WmiFormattedPropertyName $def.PerfCounter `
                            -WmiFormattedDisplayname $displayLabel `
                            -WmiFormattedInstanceName $wugInstanceName `
                            -WmiFormattedTimeout $WmiTimeout

                        if ($result -and $result.data -and $result.data.idMap) {
                            $libId = $result.data.idMap.resultId
                        }
                        elseif ($result -match 'library ID:\s*(\d+)') {
                            $libId = $Matches[1]
                        }
                        else {
                            $refetch = @(Get-WUGPerformanceMonitor -Search $monName -View 'info')
                            $matchMon = $refetch | Where-Object { $_.Name -eq $monName } | Select-Object -First 1
                            if ($matchMon) {
                                $libId = if ($matchMon.MonitorId) { $matchMon.MonitorId } else { $matchMon.Id }
                            }
                            else {
                                Write-Warning " Could not determine library ID for '$monName'. Skipping."
                                $failedCount++
                                continue
                            }
                        }

                        $monitorIdCache[$monName] = $libId
                        $createdCount++
                        Write-Host " Created (ID: $libId)" -ForegroundColor Green
                    }
                    catch {
                        Write-Error " Failed to create '$monName': $_"
                        $failedCount++
                        continue
                    }
                }

                # Assign to devices that have this SQL instance
                $libId = $monitorIdCache[$monName]
                foreach ($devId in $deviceInstances.Keys) {
                    $instMatch = $deviceInstances[$devId] | Where-Object { $_.Instance -eq $instKey }
                    if ($instMatch) {
                        $info = $deviceInfo[$devId]

                        if ($deviceExistingMonitors.ContainsKey($devId) -and $deviceExistingMonitors[$devId].ContainsKey("$libId")) {
                            Write-Verbose " '$monName' already assigned to $($info.Name). Skipping."
                            $alreadyAssignedCount++
                            continue
                        }

                        Write-Verbose " Assigning '$monName' to $($info.Name) (ID: $devId)"
                        try {
                            Add-WUGPerformanceMonitorToDevice `
                                -DeviceId $devId `
                                -MonitorId $libId `
                                -PollingIntervalMinutes $PollingIntervalMinutes `
                                -Enabled "true"
                            $assignedCount++
                            if ($deviceExistingMonitors.ContainsKey($devId)) {
                                $deviceExistingMonitors[$devId]["$libId"] = $true
                            }
                        }
                        catch {
                            $errMsg = $_.Exception.Message
                            if ($errMsg -match '409|already assigned|already exists|duplicate') {
                                Write-Host " Already assigned: $monName -> $($info.Name)" -ForegroundColor DarkGray
                                $alreadyAssignedCount++
                            }
                            else {
                                Write-Warning " Failed to assign '$monName' to $($info.Name): $errMsg"
                                $failedCount++
                            }
                        }
                    }
                }
            }
        }

        Write-Progress -Activity "Creating SQL Server monitors" -Completed

        Write-Host ""
        Write-Host "=== SQL Server Push Complete ===" -ForegroundColor Cyan
        Write-Host " Monitors created: $createdCount" -ForegroundColor Green
        Write-Host " Monitors reused: $skippedCount" -ForegroundColor Gray
        Write-Host " Assignments made: $assignedCount" -ForegroundColor Green
        Write-Host " Already assigned: $alreadyAssignedCount" -ForegroundColor Gray
        Write-Host " Failures: $failedCount" -ForegroundColor $(if ($failedCount -gt 0) { 'Red' } else { 'Gray' })
        Write-Host ""
    }
    '2' {
        # ExportJSON
        $jsonPath = Join-Path $OutputDir "MSSQL-Discovery-$(Get-Date -Format 'yyyyMMdd-HHmmss').json"
        $allExport = @{ Databases = $exportData; Instances = $instanceExportData }
        $allExport | ConvertTo-Json -Depth 5 | Set-Content -Path $jsonPath -Encoding UTF8
        Write-Host "Exported to: $jsonPath" -ForegroundColor Green
    }
    '3' {
        # ExportCSV
        $csvPath = Join-Path $OutputDir "MSSQL-Discovery-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
        $exportData | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
        Write-Host "Exported to: $csvPath" -ForegroundColor Green
        if ($instanceExportData.Count -gt 0) {
            $instCsvPath = Join-Path $OutputDir "MSSQL-InstanceCounters-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
            $instanceExportData | Export-Csv -Path $instCsvPath -NoTypeInformation -Encoding UTF8
            Write-Host "Instance counters exported to: $instCsvPath" -ForegroundColor Green
        }
    }
    '4' {
        # ShowTable
        if ($exportData.Count -gt 0) {
            Write-Host "--- Database Counters ---" -ForegroundColor Cyan
            $exportData | Format-Table -AutoSize
        }
        if ($instanceExportData.Count -gt 0) {
            Write-Host "--- Instance Counters ---" -ForegroundColor Cyan
            $instanceExportData | Format-Table -AutoSize
        }
    }
    '5' {
        # Dashboard
        $dashPath = Join-Path $OutputDir 'MSSQL-Dashboard.html'

        if (Get-Command -Name 'Export-DynamicDashboardHtml' -ErrorAction SilentlyContinue) {
            $thresholds = @()
            if ($activeCounters.Contains('PercentLogUsed')) {
                $thresholds += @{ Field = 'Percent Log Used'; Warning = 70; Critical = 90 }
            }
            if ($activeCounters.Contains('ActiveTransactions')) {
                $thresholds += @{ Field = 'Active Transactions'; Warning = 50; Critical = 100 }
            }
            if ($activeCounters.Contains('LogFlushWaitTime')) {
                $thresholds += @{ Field = 'Log Flush Wait Time (ms)'; Warning = 50; Critical = 200 }
            }
            if ($activeCounters.Contains('LogGrowths')) {
                $thresholds += @{ Field = 'Log Growths'; Warning = 1; Critical = 5 }
            }
            if ($activeCounters.Contains('LogCacheHitRatio')) {
                $thresholds += @{ Field = 'Log Cache Hit Ratio'; Warning = 90; Critical = 80; Invert = $true }
            }

            $dashSplat = @{
                Data         = $exportData
                OutputPath   = $dashPath
                ReportTitle  = 'MS SQL Server Database Inventory'
                CardField    = @('Host', 'Instance')
                ExportPrefix = 'MSSQL'
            }
            if ($thresholds.Count -gt 0) { $dashSplat.ThresholdField = $thresholds }

            Export-DynamicDashboardHtml @dashSplat

            # Generate instance-level dashboard if we have instance counters
            if ($instanceExportData.Count -gt 0) {
                $instDashPath = Join-Path $OutputDir 'MSSQL-Instance-Dashboard.html'
                $instThresholds = @()
                if ($activeInstanceCounters.Contains('PageLifeExpectancy')) {
                    $instThresholds += @{ Field = 'Page Life Expectancy (s)'; Warning = 300; Critical = 120; Invert = $true }
                }
                if ($activeInstanceCounters.Contains('BufferCacheHitRatio')) {
                    $instThresholds += @{ Field = 'Buffer Cache Hit Ratio (%)'; Warning = 95; Critical = 90; Invert = $true }
                }
                if ($activeInstanceCounters.Contains('MemoryGrantsPending')) {
                    $instThresholds += @{ Field = 'Memory Grants Pending'; Warning = 1; Critical = 3 }
                }
                if ($activeInstanceCounters.Contains('ProcessesBlocked')) {
                    $instThresholds += @{ Field = 'Processes Blocked'; Warning = 1; Critical = 5 }
                }
                if ($activeInstanceCounters.Contains('NumberOfDeadlocksPersec')) {
                    $instThresholds += @{ Field = 'Deadlocks/sec'; Warning = 1; Critical = 5 }
                }
                if ($activeInstanceCounters.Contains('LazyWritesPersec')) {
                    $instThresholds += @{ Field = 'Lazy Writes/sec'; Warning = 20; Critical = 50 }
                }

                $instDashSplat = @{
                    Data         = $instanceExportData
                    OutputPath   = $instDashPath
                    ReportTitle  = 'MS SQL Server Instance Performance'
                    CardField    = @('Host', 'Instance')
                    ExportPrefix = 'MSSQL-Instance'
                }
                if ($instThresholds.Count -gt 0) { $instDashSplat.ThresholdField = $instThresholds }

                Export-DynamicDashboardHtml @instDashSplat
                Write-Host "Instance dashboard generated: $instDashPath" -ForegroundColor Green
            }
        }
        else {
            Write-Warning "No dashboard function available. Exporting as JSON instead."
            $jsonPath = Join-Path $OutputDir "MSSQL-Discovery-$(Get-Date -Format 'yyyyMMdd-HHmmss').json"
            $exportData | ConvertTo-Json -Depth 5 | Set-Content -Path $jsonPath -Encoding UTF8
            Write-Host "JSON exported: $jsonPath" -ForegroundColor Green
            break
        }

        Write-Host "Dashboard generated: $dashPath" -ForegroundColor Green
        $dashSummary = " Devices: $($deviceDatabases.Count) | Databases: $($sortedDbKeys.Count) | DB Counters: $($activeCounters.Count)"
        if ($activeInstanceCounters.Count -gt 0) {
            $dashSummary += " | Instance Counters: $($activeInstanceCounters.Count)"
        }
        Write-Host $dashSummary -ForegroundColor Gray

        # 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 'MSSQL-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/MSSQL-Dashboard.html" -ForegroundColor Cyan
                if ($instanceExportData.Count -gt 0) {
                    $instDashPath2 = Join-Path $OutputDir 'MSSQL-Instance-Dashboard.html'
                    if (Test-Path $instDashPath2) {
                        $wugInstDashPath = Join-Path $wugDashDir 'MSSQL-Instance-Dashboard.html'
                        Copy-Item -Path $instDashPath2 -Destination $wugInstDashPath -Force
                        Write-Host "Copied to WUG: $wugInstDashPath" -ForegroundColor Green
                        Write-Host " Access via WUG web UI: /NmConsole/dashboards/MSSQL-Instance-Dashboard.html" -ForegroundColor Cyan
                    }
                }
            }
            catch {
                Write-Warning "Could not copy to NmConsole (run as admin?): $_"
            }
            Deploy-DashboardWebConfig -Path $wugDashDir
        }
    }
    '6' {
        Write-Host "No action taken." -ForegroundColor Gray
    }
    default {
        Write-Warning "Invalid choice '$currentChoice'."
    }
}
} # end foreach actionsToRun

Write-Host ""
Write-Host "=== SQL Server Discovery Complete ===" -ForegroundColor Cyan
Write-Host ""

# SIG # Begin signature block
# MIIr+wYJKoZIhvcNAQcCoIIr7DCCK+gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAvAGs7S74Rh2fV
# 6GqJLaF/prdaLiAgMHlGXid3AF1jxaCCJQ0wggVvMIIEV6ADAgECAhBI/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
# BCCIxdNK5tHHZ50zPCP2/b5xQms4NlHFATphu2SPbGJF0TANBgkqhkiG9w0BAQEF
# AASCAgDE/BSTYQbk6Bse833Jb02rdN5ZZQxSKc6f4DfgAGdGxwla9m7iufXjyqVw
# xNnIlloecE/OihYvowa4R/AQb7FXkQ9na+g4loQVM7qoYxV1D8EFiQsrxakV2SZl
# JyNmjVUa26Vx7oEKubLciMhG4fGeY8ndxpAVTruoNMybRMowucF4RvDbA1uoUVkA
# QDoN/b4MozfWaJMQUYZTfdnyqCjJynTkMv02MW33BENo3kxJm9qCiPhwKNy/IggD
# EuYdBeKL92fFRsmaZvykridmw7E109qx35QswuyofIP0tMpERSVc1Dmi3/KE9F83
# bRzE9Xh/rTnh/DOH3Bh7czwDvqLswfiyb711M2Artx9EejU5yClMS/Qp7kGirhTY
# weetvLqMU625qtOEt3pq58fMySMx5y/yE08fMCpTqjnmnSxl6NEwFlFPedAoF5sp
# RxPaSMaNsFRLZOa0nrX5xPNHJE8GZDoToKW64wc36oIGnw4iMsd5mkeQm1tInYPP
# iuuyAtCCl+nKTsjdJn4PzDJvk5X5ZQRhzfkGkDHZJMbfsnAPgrZ2Jng4wFqCdUgN
# 3r/dAFM77WqjXWRDCu8gOe6JXchV90YgpzfiC+QTpzsxCUHbI6cxP363nKsQbqPl
# 4ivNvSNKMVV506NJ35CXUgdG5IGN8UXViSFKj4rs4lFxvY2Yp6GCAyYwggMiBgkq
# hkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E
# aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1l
# U3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeV
# dGgwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNjA3MDcxNzIzMTdaMC8GCSqGSIb3DQEJBDEiBCBspjJK
# GsTfJu6IWsxwgSYBOnrSVU1kc+9BtijYrvpPojANBgkqhkiG9w0BAQEFAASCAgCZ
# LURLnaFDTWAeujwh2uOYK7a8Xs0P45wDDXqmn0V4glAp89IBukalT4et4sW1ON6C
# JKqEeZoMVyYpF/8yQ2fccbje9/6RUko3sAq0QHdfoPbIFcZ0/cA8+NoNRoemXeLa
# 9O29D9aFfksL8QQGfLq6shT3aWoGKt2DyBv7g9Gr3Nd+i/nuqk+qNiOfeL3v6efa
# CUt71/7C+zrarQCm4EeaQEInqYMk/GRUhEUJFLSV2CWnfKgDIJaF+tEzBRzYxFjj
# 7j32ASYuk+ATjRSzbLx+Ztz3NywXvakeBx7e6kgykAvV6vaL2WVjFc42LPRriT4q
# v2nzRrP+xxD16UTagRu5IwBrc/IIwBcuirSwOte8vAF0ddIDW6UUHWKFFidZxHKc
# UMTWR+2upFMj/zM6LU5zWlYQwMDVw/yB5URExFqcNmi8rDmbnG/tniBl68X1SD/u
# tu3y6LmaeVOueqFZ5EqJFkfuo/Ztfg90OQDz1M9+85KBLJZD/6vLk4r2pqOG3TY8
# IhnRpaSumDVDvY4J+wRe4bqG8OBeIjStbtVel5CnA+387FM8YdoO76xfOPQ+6T8D
# RJeMH6yZ7KciCiID2Tap+We/mYAiTFsuWNEnjhcRz2SiBM3anKImGBPKgZkjd1LD
# RrEBgFYpdI9EhB6RaciHIyzdtdJzmgj5WRJ44bYSDg==
# SIG # End signature block