helpers/discovery/Initialize-WUGDiscoveryVault.ps1

<#
.SYNOPSIS
    Populate the WhatsUpGoldPS DPAPI vault with provider credentials and
    optionally schedule recurring discovery tasks — all in one command.
 
.DESCRIPTION
    Run this script ONCE from an elevated (Run as Administrator) PowerShell
    prompt. It will:
 
      1. Set the vault to LocalMachine scope (SYSTEM-accessible) by default.
      2. Guide you through credential entry for each selected provider.
      3. Save every credential encrypted into the DPAPI vault.
      4. Print (or execute) the Register-DiscoveryScheduledTask.ps1 commands
         needed to schedule each provider's discovery run.
 
    After this script completes, ALL future discovery runs (scheduled or
    manual with -NonInteractive) will read credentials from the vault
    automatically — no password prompts.
 
    LocalMachine vs CurrentUser vault:
      LocalMachine (default) — any administrator or SYSTEM process on THIS
        machine can decrypt. Required for scheduled tasks running as SYSTEM.
      CurrentUser — only YOUR user account on this machine can decrypt.
        Suitable when scheduling tasks as yourself (not SYSTEM).
 
.PARAMETER VaultScope
    DPAPI scope: 'LocalMachine' (default, SYSTEM-accessible) or 'CurrentUser'.
 
.PARAMETER Providers
    Which providers to configure. If omitted, an interactive menu is shown.
    Valid: Azure, AWS, Proxmox, LoadMaster, Windows, CiscoWLC, CUCM
 
.PARAMETER Action
    Discovery action each scheduled task will perform.
    Default: Dashboard. Options: Dashboard, PushToWUG, ExportJSON, None.
 
.PARAMETER TriggerType
    Schedule frequency: Daily (default) or Hourly.
 
.PARAMETER TimeOfDay
    Time for daily runs (HH:mm). Default: '02:00'.
 
.PARAMETER RepeatIntervalMinutes
    For Hourly trigger: interval in minutes. Default: 60.
 
.PARAMETER OutputPath
    Base directory for dashboards and logs.
    Default: C:\ProgramData\WhatsUpGoldPS\Output (LocalMachine scope)
             %LOCALAPPDATA%\WhatsUpGoldPS\DiscoveryHelpers\Output (CurrentUser)
 
.PARAMETER Schedule
    Register scheduled tasks immediately after vault population.
    If omitted, the commands to schedule are printed instead.
 
.PARAMETER RunNow
    Start each scheduled task immediately after registering it.
 
.PARAMETER Force
    Overwrite existing vault entries without prompting.
 
.EXAMPLE
    # Full guided experience — interactive prompts for providers + credentials
    .\Initialize-WUGDiscoveryVault.ps1
 
.EXAMPLE
    # Pre-select providers; still prompts for credentials
    .\Initialize-WUGDiscoveryVault.ps1 -Providers Azure,Proxmox,Windows
 
.EXAMPLE
    # Configure + schedule + run immediately (dashboard-only)
    .\Initialize-WUGDiscoveryVault.ps1 -Providers Proxmox -Schedule -RunNow -Action Dashboard
 
.EXAMPLE
    # CurrentUser vault for tasks that run as yourself
    .\Initialize-WUGDiscoveryVault.ps1 -VaultScope CurrentUser -Providers Azure
 
.NOTES
    Author : jason@wug.ninja
    Requires: PowerShell 5.1+, Administrator rights (for LocalMachine vault)
    Encoding: UTF-8 BOM
#>

[CmdletBinding(SupportsShouldProcess)]
param(
    [ValidateSet('LocalMachine', 'CurrentUser')]
    [string]$VaultScope = 'LocalMachine',

    [ValidateSet('Azure', 'AWS', 'Proxmox', 'LoadMaster', 'Windows', 'CiscoWLC', 'CUCM')]
    [string[]]$Providers,

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

    [ValidateSet('Daily', 'Hourly')]
    [string]$TriggerType = 'Daily',

    [string]$TimeOfDay = '02:00',

    [int]$RepeatIntervalMinutes = 60,

    [string]$OutputPath,

    [switch]$Schedule,

    [switch]$RunNow,

    [switch]$Force
)

$ErrorActionPreference = 'Stop'
$scriptDir    = Split-Path $MyInvocation.MyCommand.Path -Parent
$discoveryDir = $scriptDir   # This script lives in helpers/discovery/

# ============================================================================
# Admin check (required for LocalMachine vault)
# ============================================================================
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
    [Security.Principal.WindowsBuiltInRole]::Administrator)

if (-not $isAdmin -and $VaultScope -eq 'LocalMachine') {
    Write-Host ''
    Write-Host ' [ERROR] Administrator rights are required to write the LocalMachine vault.' -ForegroundColor Red
    Write-Host ' Please re-run from an elevated (Run as Administrator) PowerShell prompt.' -ForegroundColor Yellow
    Write-Host ''
    Write-Host ' Alternatively, use -VaultScope CurrentUser to use the per-user vault.' -ForegroundColor DarkGray
    Write-Host ' Note: CurrentUser vault cannot be read by SYSTEM scheduled tasks.' -ForegroundColor DarkGray
    Write-Host ''
    return
}

# ============================================================================
# Load DiscoveryHelpers + set vault scope
# ============================================================================
$helpersPath = Join-Path $discoveryDir 'DiscoveryHelpers.ps1'
if (-not (Test-Path $helpersPath)) {
    Write-Error "DiscoveryHelpers.ps1 not found at '$helpersPath'. Cannot continue."
    return
}
. $helpersPath
Set-DiscoveryVaultScope -Scope $VaultScope

# ============================================================================
# Output path
# ============================================================================
if (-not $OutputPath) {
    if ($VaultScope -eq 'LocalMachine') {
        $OutputPath = Join-Path $env:ProgramData 'WhatsUpGoldPS\Output'
    }
    else {
        $OutputPath = Join-Path $env:LOCALAPPDATA 'WhatsUpGoldPS\DiscoveryHelpers\Output'
    }
}
if (-not (Test-Path $OutputPath)) {
    New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
}

# ============================================================================
# Helper functions
# ============================================================================
function Write-Banner {
    param([string]$Title)
    Write-Host ''
    Write-Host ' =================================================================' -ForegroundColor DarkCyan
    Write-Host " $Title" -ForegroundColor Cyan
    Write-Host ' =================================================================' -ForegroundColor DarkCyan
    Write-Host ''
}

function Write-VaultSaved {
    param([string]$Name, [string]$Description)
    Write-Host " [OK] $Description" -ForegroundColor Green
    Write-Host " Vault key: $Name" -ForegroundColor DarkGray
}

function ToSS { param([string]$v)
    if ([string]::IsNullOrEmpty($v)) {
        $ss = New-Object System.Security.SecureString; $ss.MakeReadOnly(); return $ss
    }
    return (ConvertTo-SecureString -String $v -AsPlainText -Force)
}

function ReadSecure {
    param([string]$Prompt)
    $ss = Read-Host -AsSecureString -Prompt " $Prompt"
    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss)
    try { return [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) }
    finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
}

# Track what was saved (for scheduling output)
$script:SavedProviders = [System.Collections.Generic.List[hashtable]]::new()

# ============================================================================
# Banner
# ============================================================================
Clear-Host
Write-Host ''
Write-Host ' =================================================================' -ForegroundColor Cyan
Write-Host ' WhatsUpGoldPS Discovery - Vault Initialization' -ForegroundColor White
Write-Host ' =================================================================' -ForegroundColor Cyan
Write-Host ''
Write-Host " Vault Scope : $VaultScope" -ForegroundColor $(if ($VaultScope -eq 'LocalMachine') { 'Green' } else { 'Yellow' })
Write-Host " Vault Path : $script:DiscoveryVaultPath" -ForegroundColor White
Write-Host " Output Path : $OutputPath" -ForegroundColor White
Write-Host " Action : $Action" -ForegroundColor White
Write-Host " Schedule : $(if ($Schedule) { 'Yes — tasks will be registered after vault setup' } else { 'No — commands will be printed' })" -ForegroundColor White
Write-Host ''
if ($VaultScope -eq 'LocalMachine') {
    Write-Host ' Credentials will be encrypted using the MACHINE key.' -ForegroundColor Gray
    Write-Host ' Any administrator or SYSTEM process on this machine can use them.' -ForegroundColor Gray
}
else {
    Write-Host ' Credentials will be encrypted using YOUR USER key.' -ForegroundColor Yellow
    Write-Host ' Only you, on this machine, can decrypt them.' -ForegroundColor Yellow
    Write-Host ' Scheduled tasks running as SYSTEM will NOT be able to read these.' -ForegroundColor Yellow
}
Write-Host ''

# ============================================================================
# Provider selection
# ============================================================================
$allProviders = @('Azure', 'AWS', 'Proxmox', 'LoadMaster', 'Windows', 'CiscoWLC', 'CUCM')

if (-not $Providers) {
    Write-Host ' Which providers do you want to configure?' -ForegroundColor Cyan
    Write-Host ' Enter numbers separated by commas, or A for all.' -ForegroundColor Gray
    Write-Host ''
    $i = 1
    foreach ($p in $allProviders) {
        Write-Host " [$i] $p" -ForegroundColor White
        $i++
    }
    Write-Host ' [A] All providers' -ForegroundColor White
    Write-Host ''
    $sel = Read-Host -Prompt ' Selection'
    if ($sel -match '^[aA]$') {
        $Providers = $allProviders
    }
    else {
        $indices = $sel -split '[,\s]+' | ForEach-Object { [int]$_.Trim() - 1 } | Where-Object { $_ -ge 0 -and $_ -lt $allProviders.Count }
        $Providers = $indices | ForEach-Object { $allProviders[$_] }
    }
}

if (-not $Providers -or $Providers.Count -eq 0) {
    Write-Host ' No providers selected. Exiting.' -ForegroundColor Yellow
    return
}

Write-Host ''
Write-Host ' Selected providers: ' -NoNewline -ForegroundColor White
Write-Host ($Providers -join ', ') -ForegroundColor Cyan
Write-Host ''

# ============================================================================
# AZURE
# ============================================================================
if ($Providers -contains 'Azure') {
    Write-Banner -Title 'Azure - Service Principal Credentials'
    Write-Host ' Required: App Registration with Reader role on the subscription(s).' -ForegroundColor Gray
    Write-Host ' Find at: Azure Portal > App Registrations > your app > Certificates & Secrets' -ForegroundColor Gray
    Write-Host ''

    $tenantId     = Read-Host -Prompt ' Tenant ID (Directory ID)'
    $clientId     = Read-Host -Prompt ' Client ID (Application ID)'
    $clientSecret = ReadSecure -Prompt 'Client Secret (input hidden)'

    if ([string]::IsNullOrWhiteSpace($tenantId) -or [string]::IsNullOrWhiteSpace($clientId) -or [string]::IsNullOrWhiteSpace($clientSecret)) {
        Write-Host ' [SKIP] Azure — missing required values.' -ForegroundColor Yellow
    }
    else {
        $vaultName = "Azure.$($tenantId.Trim()).ServicePrincipal"
        $fields = [ordered]@{
            TenantId     = ToSS $tenantId.Trim()
            ClientId     = ToSS $clientId.Trim()
            ClientSecret = ToSS $clientSecret
        }
        Save-DiscoveryCredential -Name $vaultName -Fields $fields `
            -Description "Azure Service Principal for tenant $tenantId" -Force:$Force | Out-Null
        Write-VaultSaved -Name $vaultName -Description "Azure (TenantId=$tenantId, ClientId=$clientId)"
        $script:SavedProviders.Add(@{
            Provider = 'Azure'
            Target   = $tenantId.Trim()
        })
    }
}

# ============================================================================
# AWS
# ============================================================================
if ($Providers -contains 'AWS') {
    Write-Banner -Title 'AWS - IAM Access Key'
    Write-Host ' Required: IAM user with EC2/RDS/ELB read permissions.' -ForegroundColor Gray
    Write-Host ' Find at: AWS Console > IAM > Users > Security credentials' -ForegroundColor Gray
    Write-Host ''

    $awsAccessKey = Read-Host -Prompt ' Access Key ID'
    $awsSecretKey = ReadSecure -Prompt 'Secret Access Key (input hidden)'

    if ([string]::IsNullOrWhiteSpace($awsAccessKey) -or [string]::IsNullOrWhiteSpace($awsSecretKey)) {
        Write-Host ' [SKIP] AWS — missing required values.' -ForegroundColor Yellow
    }
    else {
        $fields = [ordered]@{
            AccessKey = ToSS $awsAccessKey.Trim()
            SecretKey = ToSS $awsSecretKey
        }
        Save-DiscoveryCredential -Name 'AWS.Credential' -Fields $fields `
            -Description "AWS IAM key $awsAccessKey" -Force:$Force | Out-Null
        Write-VaultSaved -Name 'AWS.Credential' -Description "AWS (AccessKey=$awsAccessKey)"

        $awsRegion = Read-Host -Prompt ' AWS Region(s) for scheduling (e.g., us-east-1 or all)'
        if ([string]::IsNullOrWhiteSpace($awsRegion)) { $awsRegion = 'all' }
        $script:SavedProviders.Add(@{
            Provider = 'AWS'
            Target   = $awsRegion.Trim()
        })
    }
}

# ============================================================================
# PROXMOX
# ============================================================================
if ($Providers -contains 'Proxmox') {
    Write-Banner -Title 'Proxmox VE - API Token or Password'
    Write-Host ' Recommended: API Token (Datacenter > Permissions > API Tokens).' -ForegroundColor Gray
    Write-Host ' Token format: user@realm!tokenname=<uuid>' -ForegroundColor Gray
    Write-Host ' Example: root@pam!discovery=6470f043-5899-4542-94da-f016f80bdd4f' -ForegroundColor DarkGray
    Write-Host ''

    $pveHost = Read-Host -Prompt ' Proxmox host (IP or FQDN)'
    if ([string]::IsNullOrWhiteSpace($pveHost)) {
        Write-Host ' [SKIP] Proxmox — host is required.' -ForegroundColor Yellow
    }
    else {
        $pveHost = $pveHost.Trim()
        Write-Host ''
        Write-Host ' [1] API Token (recommended)' -ForegroundColor White
        Write-Host ' [2] Username + Password' -ForegroundColor White
        $pveAuth = Read-Host -Prompt ' Auth method [1]'
        if ([string]::IsNullOrWhiteSpace($pveAuth)) { $pveAuth = '1' }

        if ($pveAuth -eq '1') {
            $pveToken = ReadSecure -Prompt 'API Token (user@realm!name=uuid, input hidden)'
            if ([string]::IsNullOrWhiteSpace($pveToken)) {
                Write-Host ' [SKIP] Proxmox — token is required.' -ForegroundColor Yellow
            }
            else {
                $vaultName = "Proxmox.$pveHost.Token"
                Save-DiscoveryCredential -Name $vaultName -SecureSecret (ToSS $pveToken) `
                    -Description "Proxmox API Token for $pveHost" -Force:$Force | Out-Null
                Write-VaultSaved -Name $vaultName -Description "Proxmox API Token ($pveHost)"
                $script:SavedProviders.Add(@{ Provider = 'Proxmox'; Target = $pveHost; AuthMethod = 'Token' })
            }
        }
        else {
            $pveUser = Read-Host -Prompt ' Username (e.g., root@pam)'
            $pvePass = ReadSecure -Prompt 'Password (input hidden)'
            if ([string]::IsNullOrWhiteSpace($pveUser) -or [string]::IsNullOrWhiteSpace($pvePass)) {
                Write-Host ' [SKIP] Proxmox — username and password required.' -ForegroundColor Yellow
            }
            else {
                $vaultName = "Proxmox.$pveHost.Credential"
                $fields = [ordered]@{
                    Username = ToSS $pveUser.Trim()
                    Password = ToSS $pvePass
                }
                Save-DiscoveryCredential -Name $vaultName -Fields $fields `
                    -Description "Proxmox credentials for $pveHost" -Force:$Force | Out-Null
                Write-VaultSaved -Name $vaultName -Description "Proxmox Password ($pveHost, $pveUser)"
                $script:SavedProviders.Add(@{ Provider = 'Proxmox'; Target = $pveHost; AuthMethod = 'Password' })
            }
        }
    }
}

# ============================================================================
# LOADMASTER
# ============================================================================
if ($Providers -contains 'LoadMaster') {
    Write-Banner -Title 'Kemp LoadMaster - API Key or Password'
    Write-Host ' Recommended: API Key (LoadMaster > System > Certificates & Security > API Security).' -ForegroundColor Gray
    Write-Host ''

    $lmHosts = Read-Host -Prompt ' LoadMaster host(s) - IP or FQDN (comma-separated)'
    if ([string]::IsNullOrWhiteSpace($lmHosts)) {
        Write-Host ' [SKIP] LoadMaster — host is required.' -ForegroundColor Yellow
    }
    else {
        $lmHostArr  = @($lmHosts -split '[,\s]+' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
        $primaryHost = $lmHostArr[0]

        Write-Host ''
        Write-Host ' [1] API Key (recommended)' -ForegroundColor White
        Write-Host ' [2] bal:password' -ForegroundColor White
        $lmAuth = Read-Host -Prompt ' Auth method [1]'
        if ([string]::IsNullOrWhiteSpace($lmAuth)) { $lmAuth = '1' }

        if ($lmAuth -eq '1') {
            $lmKey = ReadSecure -Prompt 'API Key (input hidden)'
            if ([string]::IsNullOrWhiteSpace($lmKey)) {
                Write-Host ' [SKIP] LoadMaster — API key required.' -ForegroundColor Yellow
            }
            else {
                $vaultName = "LoadMaster.$primaryHost.ApiKey"
                Save-DiscoveryCredential -Name $vaultName -SecureSecret (ToSS $lmKey) `
                    -Description "LoadMaster API Key for $primaryHost" -Force:$Force | Out-Null
                Write-VaultSaved -Name $vaultName -Description "LoadMaster API Key ($primaryHost)"
                $script:SavedProviders.Add(@{ Provider = 'LoadMaster'; Target = $lmHostArr -join ','; AuthMethod = 'ApiKey' })
            }
        }
        else {
            $lmUser = Read-Host -Prompt ' Username (usually "bal")'
            $lmPass = ReadSecure -Prompt 'Password (input hidden)'
            $vaultName = "LoadMaster.$primaryHost.Credential"
            $fields = [ordered]@{
                Username = ToSS ($lmUser.Trim())
                Password = ToSS $lmPass
            }
            Save-DiscoveryCredential -Name $vaultName -Fields $fields `
                -Description "LoadMaster credentials for $primaryHost" -Force:$Force | Out-Null
            Write-VaultSaved -Name $vaultName -Description "LoadMaster Password ($primaryHost, $lmUser)"
            $script:SavedProviders.Add(@{ Provider = 'LoadMaster'; Target = $lmHostArr -join ','; AuthMethod = 'Password' })
        }
    }
}

# ============================================================================
# WINDOWS (WMI)
# ============================================================================
if ($Providers -contains 'Windows') {
    Write-Banner -Title 'Windows Attributes / Disk IO - WMI Credentials'
    Write-Host ' Requires local administrator or domain admin on the target hosts.' -ForegroundColor Gray
    Write-Host ' You can enter multiple credentials (tried in order until one works).' -ForegroundColor Gray
    Write-Host ''

    $winHosts = Read-Host -Prompt ' Windows host(s) - IP or FQDN (comma-separated)'
    if ([string]::IsNullOrWhiteSpace($winHosts)) {
        Write-Host ' [SKIP] Windows — host is required.' -ForegroundColor Yellow
    }
    else {
        $winHostArr = @($winHosts -split '[,\s]+' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
        $credIndex  = 1
        $addMore    = $true

        while ($addMore) {
            Write-Host ''
            Write-Host " Credential #$credIndex" -ForegroundColor Cyan
            $winUser = Read-Host -Prompt " Username (e.g., DOMAIN\User or .\Administrator)"
            if ([string]::IsNullOrWhiteSpace($winUser)) {
                Write-Host ' Skipping blank credential.' -ForegroundColor DarkGray
                break
            }
            $winPass = ReadSecure -Prompt 'Password (input hidden)'
            $vaultName = "Windows.WMI.Credential.$credIndex"
            $fields = [ordered]@{
                Username = ToSS $winUser.Trim()
                Password = ToSS $winPass
            }
            Save-DiscoveryCredential -Name $vaultName -Fields $fields `
                -Description "Windows WMI credential #$credIndex ($winUser)" -Force:$Force | Out-Null
            Write-VaultSaved -Name $vaultName -Description "WMI Credential #$credIndex ($winUser)"
            $credIndex++

            Write-Host ''
            $moreInput = Read-Host -Prompt ' Add another Windows credential? [y/N]'
            $addMore = ($moreInput -match '^[yY]')
        }

        if ($credIndex -gt 1) {
            $script:SavedProviders.Add(@{ Provider = 'Windows'; Target = $winHostArr -join ',' })
        }
    }
}

# ============================================================================
# CISCO WLC (SNMP)
# ============================================================================
if ($Providers -contains 'CiscoWLC') {
    Write-Banner -Title 'Cisco WLC - SNMP Credentials'
    Write-Host ' SNMP is used to walk the Cisco WLC MIB for wireless device discovery.' -ForegroundColor Gray
    Write-Host ''

    $wlcHosts = Read-Host -Prompt ' WLC host(s) - IP or FQDN (comma-separated)'
    if ([string]::IsNullOrWhiteSpace($wlcHosts)) {
        Write-Host ' [SKIP] CiscoWLC — host is required.' -ForegroundColor Yellow
    }
    else {
        $wlcHostArr = @($wlcHosts -split '[,\s]+' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
        Write-Host ''
        Write-Host ' [1] SNMPv2c (community string)' -ForegroundColor White
        Write-Host ' [2] SNMPv3 (username + auth + privacy)' -ForegroundColor White
        $wlcSnmpVer = Read-Host -Prompt ' SNMP version [1]'
        $wlcVer = if ($wlcSnmpVer -eq '2') { 3 } else { 2 }   # 1=SNMPv2c, 2=SNMPv3

        $wlcFields = [ordered]@{}

        if ($wlcSnmpVer -eq '2') {
            # SNMPv3
            $wlcVer = 3
            $wlcUser    = Read-Host -Prompt ' Username'
            $wlcCtx     = Read-Host -Prompt ' Context (blank = none)'
            Write-Host ' Auth protocols: 0=None 1=MD5 2=SHA(SHA1) 3=SHA256 4=SHA384 5=SHA512'
            $wlcAuthP   = Read-Host -Prompt ' Auth protocol [2=SHA]'
            if ([string]::IsNullOrWhiteSpace($wlcAuthP)) { $wlcAuthP = '2' }
            $wlcAuthPwd = ReadSecure -Prompt 'Auth password'
            Write-Host ' Privacy protocols: 0=None 1=DES 2=AES128 3=AES192 4=AES256'
            $wlcPrivP   = Read-Host -Prompt ' Privacy protocol [2=AES128]'
            if ([string]::IsNullOrWhiteSpace($wlcPrivP)) { $wlcPrivP = '2' }
            $wlcPrivPwd = ReadSecure -Prompt 'Privacy password'

            $wlcFields['SnmpVersion']    = ToSS '3'
            if (-not [string]::IsNullOrWhiteSpace($wlcUser))    { $wlcFields['Username']        = ToSS $wlcUser }
            if (-not [string]::IsNullOrWhiteSpace($wlcCtx))     { $wlcFields['Context']         = ToSS $wlcCtx }
            # Map numeric input to named protocol string expected by Setup scripts and SNMP module
            $authProtoMap = @{ '0'='None'; '1'='MD5'; '2'='SHA'; '3'='SHA256'; '4'='SHA384'; '5'='SHA512' }
            $privProtoMap = @{ '0'='None'; '1'='DES'; '2'='AES128'; '3'='AES192'; '4'='AES256' }
            $wlcAuthName = if ($authProtoMap.ContainsKey($wlcAuthP)) { $authProtoMap[$wlcAuthP] } else { $wlcAuthP }
            $wlcPrivName = if ($privProtoMap.ContainsKey($wlcPrivP)) { $privProtoMap[$wlcPrivP] } else { $wlcPrivP }
            $wlcFields['AuthProtocol']   = ToSS $wlcAuthName
            $wlcFields['PrivacyProtocol']= ToSS $wlcPrivName
            if (-not [string]::IsNullOrWhiteSpace($wlcAuthPwd)) { $wlcFields['AuthPassword']    = ToSS $wlcAuthPwd }
            if (-not [string]::IsNullOrWhiteSpace($wlcPrivPwd)) { $wlcFields['PrivacyPassword'] = ToSS $wlcPrivPwd }
        }
        else {
            # SNMPv2c
            $wlcVer = 2
            $wlcComm = Read-Host -Prompt ' Community string [public]'
            if ([string]::IsNullOrWhiteSpace($wlcComm)) { $wlcComm = 'public' }
            $wlcFields['SnmpVersion'] = ToSS '2'
            $wlcFields['Community']   = ToSS $wlcComm
        }

        Save-DiscoveryCredential -Name 'CiscoWLC.Snmp' -Fields $wlcFields `
            -Description 'CiscoWLC SNMP settings' -Force:$Force | Out-Null
        Write-VaultSaved -Name 'CiscoWLC.Snmp' -Description "CiscoWLC SNMP v$wlcVer ($($wlcHostArr -join ','))"
        $script:SavedProviders.Add(@{ Provider = 'CiscoWLC'; Target = $wlcHostArr -join ',' })
    }
}

# ============================================================================
# CUCM (SNMP)
# ============================================================================
if ($Providers -contains 'CUCM') {
    Write-Banner -Title 'Cisco CUCM - SNMP Credentials'
    Write-Host ' SNMP is used to walk the ccmPhoneTable MIB on the CUCM publisher.' -ForegroundColor Gray
    Write-Host ''

    $cucmHosts = Read-Host -Prompt ' CUCM host(s) - IP or FQDN (comma-separated)'
    if ([string]::IsNullOrWhiteSpace($cucmHosts)) {
        Write-Host ' [SKIP] CUCM — host is required.' -ForegroundColor Yellow
    }
    else {
        $cucmHostArr = @($cucmHosts -split '[,\s]+' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
        Write-Host ''
        Write-Host ' [1] SNMPv2c (community string)' -ForegroundColor White
        Write-Host ' [2] SNMPv3 (username + auth + privacy)' -ForegroundColor White
        $cucmSnmpVer = Read-Host -Prompt ' SNMP version [1]'

        $cucmFields = [ordered]@{}

        if ($cucmSnmpVer -eq '2') {
            # SNMPv3
            $cucmUser    = Read-Host -Prompt ' Username'
            $cucmCtx     = Read-Host -Prompt ' Context (blank = none)'
            Write-Host ' Auth protocols: 0=None 1=MD5 2=SHA(SHA1) 3=SHA256 4=SHA384 5=SHA512'
            $cucmAuthP   = Read-Host -Prompt ' Auth protocol [2=SHA]'
            if ([string]::IsNullOrWhiteSpace($cucmAuthP)) { $cucmAuthP = '2' }
            $cucmAuthPwd = ReadSecure -Prompt 'Auth password'
            Write-Host ' Privacy protocols: 0=None 1=DES 2=AES128 3=AES192 4=AES256'
            $cucmPrivP   = Read-Host -Prompt ' Privacy protocol [2=AES128]'
            if ([string]::IsNullOrWhiteSpace($cucmPrivP)) { $cucmPrivP = '2' }
            $cucmPrivPwd = ReadSecure -Prompt 'Privacy password'

            $cucmFields['SnmpVersion']    = ToSS '3'
            if (-not [string]::IsNullOrWhiteSpace($cucmUser))    { $cucmFields['Username']        = ToSS $cucmUser }
            if (-not [string]::IsNullOrWhiteSpace($cucmCtx))     { $cucmFields['Context']         = ToSS $cucmCtx }
            # Map numeric input to named protocol string expected by Setup scripts and SNMP module
            $authProtoMap = @{ '0'='None'; '1'='MD5'; '2'='SHA'; '3'='SHA256'; '4'='SHA384'; '5'='SHA512' }
            $privProtoMap = @{ '0'='None'; '1'='DES'; '2'='AES128'; '3'='AES192'; '4'='AES256' }
            $cucmAuthName = if ($authProtoMap.ContainsKey($cucmAuthP)) { $authProtoMap[$cucmAuthP] } else { $cucmAuthP }
            $cucmPrivName = if ($privProtoMap.ContainsKey($cucmPrivP)) { $privProtoMap[$cucmPrivP] } else { $cucmPrivP }
            $cucmFields['AuthProtocol']   = ToSS $cucmAuthName
            $cucmFields['PrivacyProtocol']= ToSS $cucmPrivName
            if (-not [string]::IsNullOrWhiteSpace($cucmAuthPwd)) { $cucmFields['AuthPassword']    = ToSS $cucmAuthPwd }
            if (-not [string]::IsNullOrWhiteSpace($cucmPrivPwd)) { $cucmFields['PrivacyPassword'] = ToSS $cucmPrivPwd }
        }
        else {
            $cucmComm = Read-Host -Prompt ' Community string [public]'
            if ([string]::IsNullOrWhiteSpace($cucmComm)) { $cucmComm = 'public' }
            $cucmFields['SnmpVersion'] = ToSS '2'
            $cucmFields['Community']   = ToSS $cucmComm
        }

        Save-DiscoveryCredential -Name 'CUCM.Snmp' -Fields $cucmFields `
            -Description 'CUCM SNMP settings' -Force:$Force | Out-Null
        Write-VaultSaved -Name 'CUCM.Snmp' -Description "CUCM SNMP ($($cucmHostArr -join ','))"
        $script:SavedProviders.Add(@{ Provider = 'CUCM'; Target = $cucmHostArr -join ',' })
    }
}

# ============================================================================
# Summary + Scheduling
# ============================================================================
Write-Host ''
Write-Host ' =================================================================' -ForegroundColor DarkCyan
Write-Host ' Vault Setup Complete' -ForegroundColor Green
Write-Host ' =================================================================' -ForegroundColor DarkCyan
Write-Host ''
Write-Host " Vault scope : $VaultScope" -ForegroundColor White
Write-Host " Vault path : $script:DiscoveryVaultPath" -ForegroundColor White
Write-Host " Providers : $($script:SavedProviders.Count) configured" -ForegroundColor White
Write-Host ''

if ($script:SavedProviders.Count -eq 0) {
    Write-Host ' No credentials were saved. Nothing to schedule.' -ForegroundColor Yellow
    return
}

# Build list of Register-DiscoveryScheduledTask.ps1 commands
$registerScript = Join-Path $discoveryDir 'Register-DiscoveryScheduledTask.ps1'
$scheduleCommands = [System.Collections.Generic.List[string]]::new()

foreach ($entry in $script:SavedProviders) {
    $p      = $entry.Provider
    $target = $entry.Target

    $providerName = if ($p -eq 'Windows') { 'WindowsAttributes' } else { $p }
    $cmd = ".\Register-DiscoveryScheduledTask.ps1 -Mode Provider -Provider $providerName"

    switch ($p) {
        'Azure' {
            $cmd += " -Target '$target'"
        }
        'AWS' {
            $cmd += " -Target '$target'"
        }
        'Proxmox' {
            $cmd += " -Target '$target'"
            if ($entry.AuthMethod) { $cmd += " -AuthMethod $($entry.AuthMethod)" }
        }
        'LoadMaster' {
            $targets = $target -split ',' | ForEach-Object { "'$($_.Trim())'" }
            $cmd += " -Target $($targets -join ',')"
            if ($entry.AuthMethod) { $cmd += " -AuthMethod $($entry.AuthMethod)" }
        }
        'Windows' {
            $targets = $target -split ',' | ForEach-Object { "'$($_.Trim())'" }
            $cmd += " -Target $($targets -join ',')"
        }
        'CiscoWLC' {
            $targets = $target -split ',' | ForEach-Object { "'$($_.Trim())'" }
            $cmd += " -Target $($targets -join ',')"
        }
        'CUCM' {
            $targets = $target -split ',' | ForEach-Object { "'$($_.Trim())'" }
            $cmd += " -Target $($targets -join ',')"
        }
    }

    $cmd += " -Action $Action"
    $cmd += " -TriggerType $TriggerType"
    if ($TriggerType -eq 'Daily') {
        $cmd += " -TimeOfDay '$TimeOfDay'"
    }
    else {
        $cmd += " -RepeatIntervalMinutes $RepeatIntervalMinutes"
    }
    $cmd += " -OutputPath '$OutputPath'"
    if ($VaultScope -eq 'LocalMachine') {
        $cmd += ' -UseSystemVault -SkipVaultPopulate'
    }
    if ($RunNow) { $cmd += ' -RunNow' }

    $scheduleCommands.Add($cmd)
}

if ($Schedule) {
    Write-Host ' Registering scheduled tasks...' -ForegroundColor Cyan
    Write-Host ''
    foreach ($cmd in $scheduleCommands) {
        Write-Host " > $cmd" -ForegroundColor DarkGray
        Write-Host ''
        if ($PSCmdlet.ShouldProcess($cmd, 'Register scheduled task')) {
            try {
                $expression = "& '$registerScript' " + ($cmd -replace '^[^-]+-', '')
                # Run via the script path directly
                $parts = $cmd -replace "^\.\\Register-DiscoveryScheduledTask\.ps1\s*", ''
                & $registerScript ($parts -split '\s+(?=-)' | Where-Object { $_ })
            }
            catch {
                Write-Warning "Failed to register task: $_"
            }
        }
    }
}
else {
    Write-Host ' To schedule each provider, run the following from an ELEVATED PowerShell prompt:' -ForegroundColor Cyan
    Write-Host " (cd to the discovery folder first: cd '$discoveryDir')" -ForegroundColor DarkGray
    Write-Host ''
    foreach ($cmd in $scheduleCommands) {
        Write-Host " $cmd" -ForegroundColor Yellow
        Write-Host ''
    }
    Write-Host ' Or run all at once by piping to Invoke-Expression, or re-run this script with -Schedule.' -ForegroundColor Gray
}

Write-Host ''
Write-Host ' Dashboard output directory: ' -ForegroundColor White -NoNewline
Write-Host $OutputPath -ForegroundColor Cyan
Write-Host ''
Write-Host ' Next steps:' -ForegroundColor Cyan
Write-Host ' 1. Verify vault contents: Get-DiscoveryCredential -Name <vaultkey> -ShowRedacted' -ForegroundColor White
Write-Host ' 2. Test a provider: .\Setup-Azure-Discovery.ps1 -Action Dashboard -NonInteractive' -ForegroundColor White
Write-Host ' 3. View scheduled tasks: .\Register-DiscoveryScheduledTask.ps1 -Show' -ForegroundColor White
Write-Host ''

# SIG # Begin signature block
# MIIr+wYJKoZIhvcNAQcCoIIr7DCCK+gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAZhCFSSxhriaH+
# MS1/5zhYUe6u2IvjmIPry9v/lExaOqCCJQ0wggVvMIIEV6ADAgECAhBI/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
# BCCeoQvrcEhlbXiPqp7oFKgACsWHf6Fe0YxqBcqrqTQkyjANBgkqhkiG9w0BAQEF
# AASCAgAa3QCgXBgdI85vjGj/yP/YDD6V1nIGMk4xjwonQ1PgdC7iRNpWwxrzLmJG
# Ft0V3wT//YDY5s5IVWutezX4t2wdJPOGYqul02R4qayV8HnZ+Q0Tk71bwTTL32R3
# mOO2kkJDNFRnuKbe/9g/HN3EG1iojUPD1AyDAcWnpp85x5LfZY+j7BJMACGk7r98
# vcxnzKa5CYkuHeSjzHeqpRtjMTDPMx03JEHtgG4UcooISz4zyKaioqZqNOK/xYvD
# 7xut7dvYudenEkS/afZbeEkMkgL6PQvw9iq9hP3eKZFfIpN0hdynViKbxIGCktPs
# tXvWxWb7KfH7nUozhh+Y/ZTgvi9jvM79MYVUAcqVWCM1zlh+++8MYMqBqp0Beufy
# AS+uzIN6jvOf9Q/ILRXBuuIfKdVpsxcvtLB3hgbzF4EWSCAhkCiSKZqYP+qyE7jT
# 3096isTvorkGcIVQnpwRqGLEj7s01y2cnjTkR/laDBi+WhErAXKX3H9n5IOj6cOo
# BLfIywLCvIS+iE5P84cuGlG0FhvWVGjacF537B+Az2XzNDQ4+cfQsiEHHuii6aUu
# llQFN0b3bJUq2ECzMBsaLLIXIZJnvZl+SW4MpW7KCiEoSxcPyBX4gPSdxcVEaWjL
# 4by/Zm6zFu2ZAoHYx6xl0BOBqedCmpRcvDwVw3sYVwy/Ju8FgKGCAyYwggMiBgkq
# hkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E
# aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1l
# U3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeV
# dGgwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNjA3MDcxMTQ5MzNaMC8GCSqGSIb3DQEJBDEiBCBD5QKo
# CqxcanK4HHrn+1hdBoHfOVDYI0lDKzjNOfHegDANBgkqhkiG9w0BAQEFAASCAgDP
# C6rsp+kuCfVPyW7F5Hlk/icVp8YiJv3HF8qsVuZC65X/vqsELXsCj8TCOhc4vx/Q
# GBlqgmlmgQSohMi7hyGQUnr49iHvS7mRVWpq60BnOgJY2qNoNhacXN5YOlwM5AfB
# gZJW64tpGXgXjnjYf7/U6hYoR0R6zJ8JKMV4ChDEB3RGQQ9PCJ+VrbbdsmebHUoa
# zFj7sZbenfqQR0VYd4Ckcwc4/TMrb4oreKS4MaVm7R5dgR8dQxXO4n2sVYn4hAZf
# m6wOp5dh663wYZ7yAFt/6EjEJcVxu5SLAjpcA8E2qnBqzoMFxglSPm/UB6mg3+91
# yKFEiDz05jJi8oUqezbpNcmozb7/PVUOEw0qbkefgJwoYPL12i0Jzq0h/MPBeASI
# 34fZfUZ5EAfrpCm3k3UjvtbGFKuGoDw18bhO+Vv59gR5sVqjPNljj8s92ja9QVdD
# 7HTeAlhn2gcePMeuERfEc8UiDYvHVeN3Hs02Ka+24NgRzd7yLRMLqYEBzqSyikrc
# 1D8ALimq+cJjU9BLSf71+9Z6NkrMzYXERapCyEWa/5a0ZNZnTOfdrOBcifhivSg1
# Wc1S90IPVCNP7xOAEThQETz/NH5dZ5ZQKsMMO6yk41T8yQEH63MPPRKhL4003323
# 8nbwHZ0G0KbiLmy7+gkzAOvZO8kpOlt4DToF0VLHXQ==
# SIG # End signature block