src/Public/Invoke-FotonTriage.ps1

function Invoke-FotonTriage {
    <#
    .SYNOPSIS
        Live collect → analyze → HTML report → hashed package (v1 WinFoton behavior).
 
    .DESCRIPTION
        Runs on a Windows endpoint. Administrator is preferred; standard-user coverage
        is supported. Writes a Foton_<host>_<stamp>Z case folder. Does not remediate.
 
        This is the Collect orchestrator. On an engagement, prefer dist/Foton-Collect.ps1
        (single file on the endpoint). Loading the Foton module and calling this command
        is for lab/dev on a workstation.
 
    .EXAMPLE
        Invoke-FotonTriage -OutputPath C:\FotonCases -CaseNumber 'LAB-1' -NoZip
 
        Collect with the current token into C:\FotonCases.
    #>

    [CmdletBinding()]
    param(
        [string]$OutputPath = "$env:USERPROFILE\Desktop",
        [string]$CaseNumber = '',
        [string]$Examiner = "$env:USERNAME",
        [int]$EventLogDays = 14,
        [int]$MaxEventsPerLog = 3000,
        [switch]$SkipEvtxExport,
        [switch]$SkipHashing,
        [switch]$LoadUserHives,
        [switch]$IncludeFileTimeline,
        [switch]$IncludeUsnJournal,
        [switch]$Deep,
        [switch]$NoZip,
        [switch]$RequireAdministrator
    )

    if ($env:OS -ne 'Windows_NT') {
        Write-Host ' [x] Foton Collect runs on Windows only.' -ForegroundColor Red
        return
    }

    # -Deep forces the existing depth switches; they still work alone without -Deep.
    if ($Deep) {
        $IncludeFileTimeline = $true
        $IncludeUsnJournal = $true
    }

    Initialize-FotonSession -SkipHashing:$SkipHashing -EventLogDays $EventLogDays `
        -MaxEventsPerLog $MaxEventsPerLog -SkipEvtxExport:$SkipEvtxExport `
        -IncludeFileTimeline:$IncludeFileTimeline -IncludeUsnJournal:$IncludeUsnJournal `
        -Deep:$Deep

    if (-not $script:FotonSession.Version) {
        $ver = 'unknown'
        $psd1 = $null
        if ($script:FotonModuleRoot) { $psd1 = Join-Path $script:FotonModuleRoot 'Foton.psd1' }
        if ($psd1 -and (Test-Path -LiteralPath $psd1)) {
            try { $ver = [string](Import-PowerShellDataFile -LiteralPath $psd1).ModuleVersion } catch { }
        }
        $script:FotonSession.Version = $ver
    }
    $script:FotonSession.StartTimeUtc = (Get-Date).ToUniversalTime()
    $script:FotonSession.IsElevated = Test-FotonElevated
    $script:FotonSession.Package = @{
        CaseNumber            = $CaseNumber
        Examiner              = $Examiner
        RequireAdministrator  = [bool]$RequireAdministrator
        LoadUserHives         = [bool]$LoadUserHives
        OutputPath            = $OutputPath
    }

    Write-FotonBanner -Kind 'Collect'

    if ($RequireAdministrator -and -not $script:FotonSession.IsElevated) {
        Write-Host ' [x] -RequireAdministrator was set, but this process is not elevated.' -ForegroundColor Red
        Write-Host ' Re-run from an elevated PowerShell, or omit -RequireAdministrator' -ForegroundColor Red
        Write-Host ' to collect with standard-user coverage.' -ForegroundColor Red
        return
    }

    if (-not $script:FotonSession.IsElevated) {
        Write-Host ' [!] Standard-user coverage (not Administrator).' -ForegroundColor Yellow
        Write-Host ' Still collected: this user''s processes, readable network state,' -ForegroundColor Yellow
        Write-Host ' HKCU persistence, profile files, and any logs the token can read.' -ForegroundColor Yellow
        Write-Host ' Skipped: Security log, Prefetch copies, Amcache, SRUM, VSS, auditpol/secedit, other users.' -ForegroundColor Yellow
        Write-Host ''
    }

    $caseRoot = Initialize-FotonCase -OutputPath $OutputPath
    Write-FotonStatus "Case folder: $caseRoot" 'Good'
    $covLabel = 'StandardUser'
    if ($script:FotonSession.IsElevated) { $covLabel = 'Elevated' }
    Write-FotonStatus "Examiner: $Examiner | Case: $(if ($CaseNumber) { $CaseNumber } else { 'not supplied' }) | Coverage: $covLabel" 'Info'
    Write-FotonChainOfCustody -OutputPath $OutputPath -CaseNumber $CaseNumber -Examiner $Examiner `
        -LoadUserHives:$LoadUserHives -NoZip:$NoZip -RequireAdministrator:$RequireAdministrator

    if ($LoadUserHives) {
        if (-not $script:FotonSession.IsElevated) {
            Write-FotonStatus 'LoadUserHives ignored: mounting NTUSER.DAT requires elevation' 'Warn'
        }
        else {
            Write-FotonStatus 'Mounting offline user registry hives' 'Step'
            $mounted = Mount-FotonOfflineUserHive
            $script:FotonSession.LoadUserHivesActive = $true
            Write-FotonStatus ("Mounted {0} offline hive(s)" -f @($mounted).Count) 'Info'
        }
    }

    try {
        Invoke-FotonProcessCollection
        Invoke-FotonNetworkCollection
        Invoke-FotonUserCollection
        Invoke-FotonSystemCollection
        Invoke-FotonPersistenceCollection
        Invoke-FotonSecurityCollection
        Invoke-FotonSoftwareCollection
        Invoke-FotonEventLogCollection
        Invoke-FotonFilesystemCollection

        Write-FotonCollectionCoverage
        Invoke-FotonAnalysis
    }
    finally {
        if ($script:FotonSession.LoadUserHivesActive) {
            Write-FotonStatus 'Unmounting offline user registry hives' 'Step'
            Dismount-FotonOfflineUserHive
        }
    }

    Write-FotonManifestFiles
    $reportPath = Join-Path $script:FotonSession.CaseRoot 'Foton_Report.html'
    New-FotonReport -Path $reportPath | Out-Null
    $artifactCount = Write-FotonArtifactHashes

    $zipPath = $null
    if (-not $NoZip) {
        $zipPath = Compress-FotonCasePackage
    }

    $zipHash = ''
    $zipSizeMb = 0
    if ($zipPath -and (Test-Path -LiteralPath $zipPath)) {
        try { $zipHash = Get-FotonSha256Hex -LiteralPath $zipPath } catch { $zipHash = 'HASH_ERROR' }
        $zipSizeMb = [math]::Round((Get-Item -LiteralPath $zipPath).Length / 1MB, 2)
        $stamp = @()
        $stamp += 'Foton package integrity record'
        $stamp += ''
        $stamp += "Package : $(Split-Path $zipPath -Leaf)"
        $stamp += "SHA-256 : $zipHash"
        $stamp += "Size (MB) : $zipSizeMb"
        $stamp += "Created UTC : $((Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss'))"
        $stamp += "Host : $env:COMPUTERNAME"
        $stamp += "Examiner : $Examiner"
        $stamp += "Case : $CaseNumber"
        $stamp += ''
        $stamp += 'Record this hash on receipt and verify it at every custody transfer:'
        $stamp += ' Get-FileHash -Algorithm SHA256 <package.zip>'
        $stamp | Out-File -FilePath ("$($script:FotonSession.CaseRoot)" + '_PACKAGE_HASH.txt') -Encoding UTF8
    }

    $sevCounts = @{ High = 0; Medium = 0; Low = 0; Info = 0 }
    foreach ($f in $script:FotonSession.Findings) { $sevCounts[$f.Severity] = $sevCounts[$f.Severity] + 1 }

    Write-Host ''
    Write-Host ' ============================================================' -ForegroundColor Cyan
    Write-Host ' COLLECTION COMPLETE' -ForegroundColor Cyan
    Write-Host ' ============================================================' -ForegroundColor Cyan
    Write-Host (" Artifacts collected : {0} files across {1} categories" -f $artifactCount, $script:FotonSession.Categories.Count)
    Write-Host (" Collection errors : {0}" -f $script:FotonSession.Errors.Count)
    Write-Host (" Observations : {0} high, {1} medium, {2} low, {3} info" -f `
            $sevCounts['High'], $sevCounts['Medium'], $sevCounts['Low'], $sevCounts['Info'])
    Write-Host (" Duration : {0} min" -f [math]::Round(((Get-Date).ToUniversalTime() - $script:FotonSession.StartTimeUtc).TotalMinutes, 1))
    Write-Host ''
    Write-Host " Case folder : $($script:FotonSession.CaseRoot)" -ForegroundColor White
    Write-Host " Report : $reportPath" -ForegroundColor White
    if ($zipPath) {
        Write-Host " Package : $zipPath ($zipSizeMb MB)" -ForegroundColor White
        Write-Host " SHA-256 : $zipHash" -ForegroundColor Green
    }
    Write-Host ''
    Write-Host ' Open Foton_Report.html from inside the extracted folder so' -ForegroundColor DarkGray
    Write-Host ' the artifact links resolve. Findings are automated observations,' -ForegroundColor DarkGray
    Write-Host ' not conclusions - verify each one against its source artifact.' -ForegroundColor DarkGray
    Write-Host ''
}