modules/PerformanceSnapshot/PerformanceSnapshot.psm1

<#
    PerformanceSnapshot.psm1 - lightweight point-in-time performance (read-only).
    Produces: PerformanceSnapshot. NOT trend data.
#>


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

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

function Invoke-DiscoveryCollection {
    param([object]$Context)
    $cpu = $null; $memPct = $null; $pagefilePct = $null; $uptimeHours = $null
    $topCpu = ''; $topMem = ''; $diskFree = ''

    try {
        $cpu = $null
        try { $cpu = [math]::Round(((Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction Stop).CounterSamples[0].CookedValue),1) } catch { }
        if ($null -eq $cpu) { $lp = (Invoke-CimSafe -ClassName 'Win32_Processor' | Measure-Object -Property LoadPercentage -Average).Average; if ($lp) { $cpu = [math]::Round([double]$lp,1) } }
    } catch { }

    try {
        $os = Invoke-CimSafe -ClassName 'Win32_OperatingSystem' | Select-Object -First 1
        if ($os) {
            $total = [double]$os.TotalVisibleMemorySize; $free = [double]$os.FreePhysicalMemory
            if ($total -gt 0) { $memPct = [math]::Round((($total - $free) / $total) * 100, 1) }
            try { $uptimeHours = [math]::Round(((Get-Date) - ([Management.ManagementDateTimeConverter]::ToDateTime($os.LastBootUpTime))).TotalHours, 1) } catch { }
        }
    } catch { }

    try {
        $pf = Invoke-CimSafe -ClassName 'Win32_PageFileUsage' | Select-Object -First 1
        if ($pf -and $pf.AllocatedBaseSize -gt 0) { $pagefilePct = [math]::Round(($pf.CurrentUsage / $pf.AllocatedBaseSize) * 100, 1) }
    } catch { }

    try { $topCpu = ((Get-Process -ErrorAction SilentlyContinue | Sort-Object CPU -Descending | Select-Object -First 5 | ForEach-Object { $_.Name }) -join ', ') } catch { }
    try { $topMem = ((Get-Process -ErrorAction SilentlyContinue | Sort-Object WorkingSet64 -Descending | Select-Object -First 5 | ForEach-Object { "{0}({1}MB)" -f $_.Name, [math]::Round($_.WorkingSet64/1MB,0) }) -join ', ') } catch { }
    try {
        if ($Context.DataSets.Contains('Volumes')) { $diskFree = (($Context.DataSets['Volumes'].Rows | ForEach-Object { "{0}:{1}%" -f $_.DriveLetter, $_.PercentFree }) -join ', ') }
    } catch { }

    $row = [pscustomobject]@{
        CpuPercent=$cpu; MemoryUsedPercent=$memPct; PageFileUsagePercent=$pagefilePct; UptimeHours=$uptimeHours
        TopCpuProcesses=$topCpu; TopMemoryProcesses=$topMem; DiskFreeSummary=$diskFree
        Note='Point-in-time snapshot only, not trend data.'
    }
    if ($null -eq $cpu) { Add-Limitation -Context $Context -Module 'PerformanceSnapshot' -Message 'CPU counter unavailable; used fallback or none.' | Out-Null }
    return ,@{ PerformanceSnapshot=@($row) }
}

function ConvertTo-DiscoveryDatasets {
    param([object]$Context, $RawData)
    $rows = @(if ($RawData -and $RawData.PerformanceSnapshot) { @($RawData.PerformanceSnapshot) } else { @() })
    Add-DataSet -Context $Context -Name 'PerformanceSnapshot' -Description 'Point-in-time CPU/memory/disk snapshot.' -Rows $rows -Visibility 'Internal' -SourceModule 'PerformanceSnapshot' | Out-Null
}

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