src/Public/Collect/Invoke-FotonProcessCollection.ps1

# Invoke-FotonProcessCollection - volatile process state, modules, PS history.

function Invoke-FotonProcessCollection {
    Write-FotonStatus 'Collecting running processes, modules, and volatile state' 'Step'

    Invoke-FotonCollection -Category 'Processes' -Name 'running_processes' -Format 'Csv' -Description 'Processes with path, command line, parent, hash, signature' -Script {
        $procList = @{}
        try {
            foreach ($p in (Get-CimInstance Win32_Process -ErrorAction Stop)) { $procList[[int]$p.ProcessId] = $p }
        }
        catch { }

        $owners = @{}
        try {
            foreach ($p in $procList.Values) {
                try {
                    $o = Invoke-CimMethod -InputObject $p -MethodName GetOwner -ErrorAction Stop
                    if ($o -and $o.User) { $owners[[int]$p.ProcessId] = "$($o.Domain)\$($o.User)" }
                }
                catch { }
            }
        }
        catch { }

        $rows = @()
        foreach ($p in ($procList.Values | Sort-Object ProcessId)) {
            $path = $p.ExecutablePath
            $sig = Get-FotonSignatureInfo -Path $path
            $parentName = ''
            if ($procList.ContainsKey([int]$p.ParentProcessId)) { $parentName = $procList[[int]$p.ParentProcessId].Name }
            $rows += [PSCustomObject]@{
                PID               = $p.ProcessId
                Name              = $p.Name
                ParentPID         = $p.ParentProcessId
                ParentName        = $parentName
                Owner             = if ($owners.ContainsKey([int]$p.ProcessId)) { $owners[[int]$p.ProcessId] } else { '' }
                Path              = $path
                CommandLine       = $p.CommandLine
                CreationTimeUtc   = if ($p.CreationDate) { $p.CreationDate.ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss') } else { '' }
                SessionId         = $p.SessionId
                SignatureStatus   = $sig.Status
                Signer            = $sig.Signer
                IsOSBinary        = $sig.IsOSBinary
                SHA256            = (Get-FotonSafeHash -Path $path)
                UserWritablePath  = (Test-FotonUserWritablePath -Path $path)
                # Column retained for schema continuity; Analyze now derives the hits
                # from CommandLine. See Build-Standalone.ps1 for why the patterns
                # cannot ship inside the endpoint flatten.
                SuspiciousCmdHits = ''
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Processes' -Name 'process_tree' -Format 'Txt' -Description 'Parent/child process tree rendering' -Script {
        $procs = @{}
        try { foreach ($p in (Get-CimInstance Win32_Process -ErrorAction Stop)) { $procs[[int]$p.ProcessId] = $p } } catch { return 'Unavailable' }
        $children = @{}
        foreach ($p in $procs.Values) {
            $ppid = [int]$p.ParentProcessId
            if (-not $children.ContainsKey($ppid)) { $children[$ppid] = @() }
            $children[$ppid] += $p
        }
        $lines = New-Object System.Collections.ArrayList
        function Write-FotonProcessBranch {
            param($ProcId, $Depth, $ChildMap, $ProcMap, $Sink)
            if ($Depth -gt 12) { return }
            foreach ($child in ($ChildMap[$ProcId] | Sort-Object ProcessId)) {
                $indent = ' ' * $Depth
                $null = $Sink.Add(("{0}[{1}] {2} {3}" -f $indent, $child.ProcessId, $child.Name, $child.ExecutablePath))
                if ($ChildMap.ContainsKey([int]$child.ProcessId)) {
                    Write-FotonProcessBranch -ProcId ([int]$child.ProcessId) -Depth ($Depth + 1) -ChildMap $ChildMap -ProcMap $ProcMap -Sink $Sink
                }
            }
        }
        foreach ($rootPid in ($children.Keys | Sort-Object)) {
            if (-not $procs.ContainsKey($rootPid)) {
                $null = $lines.Add("[$rootPid] (parent no longer running)")
                Write-FotonProcessBranch -ProcId $rootPid -Depth 1 -ChildMap $children -ProcMap $procs -Sink $lines
            }
        }
        $null = $lines.Add('')
        $null = $lines.Add('--- Tree from System (PID 4) ---')
        Write-FotonProcessBranch -ProcId 4 -Depth 1 -ChildMap $children -ProcMap $procs -Sink $lines
        $lines
    } | Out-Null

    Invoke-FotonCollection -Category 'Processes' -Name 'loaded_modules_nonstandard' -Format 'Csv' -Description 'Loaded DLLs outside System32/Program Files' -Script {
        $rows = @()
        foreach ($proc in (Get-Process -ErrorAction SilentlyContinue)) {
            try {
                foreach ($m in $proc.Modules) {
                    $fn = $m.FileName
                    if ($fn -match '\\Windows\\System32\\' -or $fn -match '\\Windows\\SysWOW64\\' -or
                        $fn -match '\\Windows\\WinSxS\\' -or $fn -match '\\Program Files') { continue }
                    $rows += [PSCustomObject]@{
                        ProcessName = $proc.ProcessName
                        PID         = $proc.Id
                        Module      = $m.ModuleName
                        Path        = $fn
                        Company     = $m.FileVersionInfo.CompanyName
                        SHA256      = (Get-FotonSafeHash -Path $fn)
                    }
                }
            }
            catch { }
        }
        $rows | Sort-Object Path -Unique
    } | Out-Null

    Invoke-FotonCollection -Category 'Processes' -Name 'powershell_console_history' -Format 'Csv' -Description 'PSReadLine history for every profile (analyst-critical)' -Script {
        $rows = @()
        foreach ($userProfile in (Get-FotonUserProfile)) {
            $hist = Join-Path $userProfile.ProfilePath 'AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt'
            if (-not (Test-FotonPathAccessible -Path $hist)) { continue }
            $lineNo = 0
            try {
                foreach ($line in (Get-Content -LiteralPath $hist -ErrorAction Stop)) {
                    $lineNo++
                    $rows += [PSCustomObject]@{
                        User           = $userProfile.UserName
                        HistoryFile    = $hist
                        LineNumber     = $lineNo
                        Command        = $line
                        SuspiciousHits = ''
                    }
                }
            }
            catch { }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Processes' -Name 'clipboard_and_recent_docs' -Format 'Txt' -Description 'Recent document MRU keys' -Script {
        $out = @()
        foreach ($hiveRoot in (Get-FotonUserHiveRoot)) {
            $mru = "$($hiveRoot.Root)\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs"
            if (Test-FotonPathAccessible -Path $mru) {
                $out += "=== RecentDocs for $($hiveRoot.UserName) ==="
                $out += (Get-ChildItem -LiteralPath $mru -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name | Out-String)
            }
        }
        if ($out.Count -eq 0) { $out += 'No RecentDocs keys accessible.' }
        $out
    } | Out-Null

    Invoke-FotonCollection -Category 'Processes' -Name 'named_pipes' -Format 'Csv' -Description 'Named pipe names (volatile; Collect only)' -Script {
        $rows = @()
        $max = 2000
        try {
            $names = @([System.IO.Directory]::GetFiles('\\.\pipe\'))
            foreach ($p in ($names | Sort-Object)) {
                if ($rows.Count -ge $max) { break }
                $leaf = [IO.Path]::GetFileName($p)
                if (-not $leaf) { $leaf = [string]$p }
                $rows += [PSCustomObject]@{ Name = $leaf }
            }
        }
        catch { }
        $rows
    } | Out-Null
}