modules/VendorAgents/VendorAgents.psm1

<#
    VendorAgents.psm1 - vendor / RMM / EDR / backup / monitoring / hardware agents (read-only).
    Produces: VendorAgents. NEVER collects SNMP community strings.
#>


function Get-DiscoveryModuleMetadata {
    [pscustomobject]@{
        ModuleName='VendorAgents'; DisplayName='Vendor / RMM / Security Agents'; Category='Vendor'; Version='1.0.0'
        DefaultInFast=$true; DefaultInDeep=$true; RequiresAdmin=$false; RequiresDomainContext=$false
        RequiresRole=$null; EstimatedImpact='Low'; CanRunAsSystem=$true
        ProducesDatasets=@('VendorAgents')
        ProducesRisks=$true; ProducesFollowUpQuestions=$true; SupportsDeepMode=$true; SupportsComplianceLens=$true
    }
}

function Test-DiscoveryPrerequisites {
    param([object]$Context)
    [pscustomobject]@{ ModuleName='VendorAgents'; CanRun=$true; Status='Ready'; Reason=''; Limitations=@() }
}

function Invoke-DiscoveryCollection {
    param([object]$Context)
    $rows=[System.Collections.Generic.List[object]]::new()
    $catalog = @(
        @{ Name='Datto RMM'; Cat='RMM'; Pattern='(?i)Datto RMM|CentraStage|AEM Agent' },
        @{ Name='ConnectWise Automate'; Cat='RMM'; Pattern='(?i)ConnectWise Automate|LabTech' },
        @{ Name='ConnectWise ScreenConnect/Control'; Cat='RemoteAccess'; Pattern='(?i)ScreenConnect|ConnectWise Control' },
        @{ Name='Auvik'; Cat='Network'; Pattern='(?i)Auvik' },
        @{ Name='Huntress'; Cat='EDR/AV'; Pattern='(?i)Huntress' },
        @{ Name='SentinelOne'; Cat='EDR/AV'; Pattern='(?i)SentinelOne|Sentinel Agent' },
        @{ Name='CrowdStrike Falcon'; Cat='EDR/AV'; Pattern='(?i)CrowdStrike|CSFalcon|CSAgent' },
        @{ Name='Sophos'; Cat='EDR/AV'; Pattern='(?i)Sophos' },
        @{ Name='Bitdefender'; Cat='EDR/AV'; Pattern='(?i)Bitdefender' },
        @{ Name='Webroot'; Cat='EDR/AV'; Pattern='(?i)Webroot' },
        @{ Name='Defender for Endpoint'; Cat='EDR/AV'; Pattern='(?i)Windows Defender Advanced Threat|Sense$|MsSense' },
        @{ Name='Veeam'; Cat='Backup'; Pattern='(?i)Veeam' },
        @{ Name='Acronis'; Cat='Backup'; Pattern='(?i)Acronis' },
        @{ Name='Azure Arc'; Cat='CloudMgmt'; Pattern='(?i)Azure Connected Machine|himds|GCArcService' },
        @{ Name='Azure Monitor Agent'; Cat='Monitoring'; Pattern='(?i)Azure Monitor Agent|AzureMonitorAgent' },
        @{ Name='Log Analytics / MMA'; Cat='Monitoring'; Pattern='(?i)Microsoft Monitoring Agent|HealthService' },
        @{ Name='Windows Admin Center'; Cat='CloudMgmt'; Pattern='(?i)Windows Admin Center|ServerManagementGateway' },
        @{ Name='SNMP Service'; Cat='Network'; Pattern='(?i)^SNMP$|SNMP Service' },
        @{ Name='APC PowerChute'; Cat='UPS'; Pattern='(?i)PowerChute|APC' },
        @{ Name='Eaton IPP'; Cat='UPS'; Pattern='(?i)Eaton|Intelligent Power' },
        @{ Name='Dell OpenManage'; Cat='Hardware'; Pattern='(?i)OpenManage|Dell EMC' },
        @{ Name='HPE Management'; Cat='Hardware'; Pattern='(?i)HP(E)? (Insight|System|iLO|Smart)' },
        @{ Name='Lenovo XClarity/OneCLI'; Cat='Hardware'; Pattern='(?i)XClarity|OneCLI|ThinkSystem' }
    )
    try {
        $svcRows = @(if ($Context.DataSets.Contains('Services')) { @($Context.DataSets['Services'].Rows) } else { @() })
        $appRows = @(if ($Context.DataSets.Contains('InstalledApplications')) { @($Context.DataSets['InstalledApplications'].Rows) } else { @() })
        foreach ($c in $catalog) {
            $ev=@()
            $sh = @($svcRows | Where-Object { $_.DisplayName -match $c.Pattern -or $_.Name -match $c.Pattern })
            $ah = @($appRows | Where-Object { $_.DisplayName -match $c.Pattern })
            if ($sh.Count) { $ev += ('service:' + $sh[0].Name) }
            if ($ah.Count) { $ev += ('app:' + $ah[0].DisplayName) }
            if ($ev.Count) {
                $rows.Add([pscustomobject]@{ AgentName=$c.Name; Category=$c.Cat; Evidence=($ev -join '; '); Confidence='Likely' })
                if ($c.Name -eq 'SNMP Service') { Add-Unknown -Context $Context -Unknown 'SNMP is configured; community string is intentionally not collected.' -WhyItMatters 'SNMP community/monitoring config must be re-coordinated on migration.' -Module 'VendorAgents' | Out-Null }
            }
        }
    } catch { Add-Limitation -Context $Context -Module 'VendorAgents' -Message 'Vendor agent detection failed.' -Reason $_.Exception.Message | Out-Null }
    return ,@{ VendorAgents=@($rows) }
}

function ConvertTo-DiscoveryDatasets {
    param([object]$Context, $RawData)
    $rows = @(if ($RawData -and $RawData.VendorAgents) { @($RawData.VendorAgents) } else { @() })
    Add-DataSet -Context $Context -Name 'VendorAgents' -Description 'Detected vendor/RMM/EDR/backup/monitoring/hardware agents.' -Rows $rows -Visibility 'Both' -SourceModule 'VendorAgents' | Out-Null
}

function Get-DiscoveryFollowUpQuestions {
    param([object]$Context)
    try {
        if ($Context.DataSets.Contains('VendorAgents')) {
            $edr = @($Context.DataSets['VendorAgents'].Rows | Where-Object { $_.Category -eq 'EDR/AV' })
            if ($edr.Count -gt 0) { Add-FollowUpQuestion -Context $Context -Category 'Vendor support' -Module 'VendorAgents' -Audience 'Both' -Question ('Security agents are present ({0}). Discovery activity may raise alerts - who manages these consoles, and how are the agents handled during migration/decommission?' -f (($edr | ForEach-Object { $_.AgentName }) -join ', ')) | Out-Null }
        }
    } catch { }
}

Export-ModuleMember -Function 'Get-DiscoveryModuleMetadata','Test-DiscoveryPrerequisites','Invoke-DiscoveryCollection','ConvertTo-DiscoveryDatasets','Get-DiscoveryFollowUpQuestions'