src/Public/Collect/Invoke-FotonPersistenceCollection.ps1

# Invoke-FotonPersistenceCollection
# Live facts only. Called from Invoke-FotonTriage when that exists.
# Endpoint collect. Rebuild dist/Foton-Collect.ps1 after edits.

function Invoke-FotonPersistenceCollection {
    Write-FotonStatus 'Collecting persistence mechanisms (autoruns, tasks, services, WMI)' 'Step'

    Invoke-FotonCollection -Category 'Persistence' -Name 'autorun_registry_keys' -Format 'Csv' -Description 'Run/RunOnce and policy autostart keys, machine and per-user' -Script {
        $rows = @()
        $machineKeys = @(
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce',
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx',
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices',
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServicesOnce',
            'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run',
            'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce',
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run',
            'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',
            'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components'
        )
        foreach ($k in $machineKeys) {
            foreach ($v in (Get-FotonRegValueList -Path $k)) {
                $rows += [PSCustomObject]@{
                    Scope = 'Machine'; User = 'N/A'; Key = $v.Key; ValueName = $v.Name; Command = $v.Value
                    # Analyze derives this from Command; patterns cannot ship in the flatten.
                    SuspiciousHits = ''
                    UserWritableTarget = (Test-FotonUserWritablePath -Path $v.Value)
                }
            }
        }
        $userSubKeys = @(
            'Software\Microsoft\Windows\CurrentVersion\Run',
            'Software\Microsoft\Windows\CurrentVersion\RunOnce',
            'Software\Microsoft\Windows\CurrentVersion\RunServices',
            'Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run',
            'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run'
        )
        foreach ($hiveRoot in (Get-FotonUserHiveRoot)) {
            foreach ($sub in $userSubKeys) {
                $full = Join-Path $hiveRoot.Root $sub
                foreach ($v in (Get-FotonRegValueList -Path $full)) {
                    $rows += [PSCustomObject]@{
                        Scope = 'User'; User = $hiveRoot.UserName; Key = $v.Key; ValueName = $v.Name; Command = $v.Value
                        SuspiciousHits = ''
                        UserWritableTarget = (Test-FotonUserWritablePath -Path $v.Value)
                    }
                }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'startup_folder_items' -Format 'Csv' -Description 'Files in machine and per-user Startup folders' -Script {
        $rows = @()
        $paths = @("$env:ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp")
        foreach ($userProfile in (Get-FotonUserProfile)) {
            $paths += (Join-Path $userProfile.ProfilePath 'AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup')
        }
        foreach ($p in $paths) {
            if (-not (Test-FotonPathAccessible -Path $p)) { continue }
            foreach ($f in (Get-ChildItem -LiteralPath $p -Force -ErrorAction SilentlyContinue)) {
                if ($f.Name -eq 'desktop.ini') { continue }
                $sig = Get-FotonSignatureInfo -Path $f.FullName
                $rows += [PSCustomObject]@{
                    StartupFolder   = $p
                    Name            = $f.Name
                    FullName        = $f.FullName
                    SizeBytes       = $f.Length
                    CreatedUtc      = $f.CreationTimeUtc.ToString('yyyy-MM-dd HH:mm:ss')
                    ModifiedUtc     = $f.LastWriteTimeUtc.ToString('yyyy-MM-dd HH:mm:ss')
                    SignatureStatus = $sig.Status
                    SHA256          = (Get-FotonSafeHash -Path $f.FullName)
                }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'startup_approved' -Format 'Csv' -Description 'Task Manager / Settings StartupApproved enable-disable bits (not the Startup folder)' -Script {
        Get-FotonStartupApprovedRows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'scheduled_tasks' -Format 'Csv' -Description 'Scheduled tasks with actions, principals, and last run state' -Script {
        $rows = @()
        try {
            foreach ($t in (Get-ScheduledTask -ErrorAction Stop)) {
                $info = $null
                try { $info = Get-ScheduledTaskInfo -TaskName $t.TaskName -TaskPath $t.TaskPath -ErrorAction Stop } catch { }
                $actions = @()
                foreach ($a in $t.Actions) {
                    if ($a.Execute) { $actions += ("{0} {1}" -f $a.Execute, $a.Arguments) }
                    elseif ($a.ClassId) { $actions += ("COM:{0}" -f $a.ClassId) }
                }
                $actionText = ($actions -join ' | ')
                $exePath = ''
                if ($t.Actions -and $t.Actions[0].Execute) {
                    $exePath = [Environment]::ExpandEnvironmentVariables($t.Actions[0].Execute.Trim('"'))
                }
                # Probe Execute + Arguments + WorkingDirectory. Interpreter-only checks
                # miss powershell.exe -File C:\Temp\... (user-writable payload).
                $writableProbe = @($exePath, $actionText)
                foreach ($a in @($t.Actions)) {
                    if ($a.Arguments) { $writableProbe += [string]$a.Arguments }
                    if ($a.WorkingDirectory) { $writableProbe += [string]$a.WorkingDirectory }
                }
                $userWritable = $false
                foreach ($probe in $writableProbe) {
                    if (Test-FotonUserWritablePath -Path $probe) { $userWritable = $true; break }
                }
                $triggers = @()
                foreach ($tr in $t.Triggers) { $triggers += $tr.CimClass.CimClassName }
                $rows += [PSCustomObject]@{
                    TaskPath       = $t.TaskPath
                    TaskName       = $t.TaskName
                    State          = [string]$t.State
                    Author         = $t.Author
                    RunAsUser      = $t.Principal.UserId
                    RunLevel       = [string]$t.Principal.RunLevel
                    Triggers       = ($triggers -join ', ')
                    Actions        = $actionText
                    LastRunTime    = if ($info) { [string]$info.LastRunTime } else { '' }
                    LastResult     = if ($info) { $info.LastTaskResult } else { '' }
                    NextRunTime    = if ($info) { [string]$info.NextRunTime } else { '' }
                    Description    = $t.Description
                    SuspiciousHits = ''
                    UserWritableTarget = $userWritable
                    SHA256         = (Get-FotonSafeHash -Path $exePath)
                }
            }
        }
        catch {
            $rows += [PSCustomObject]@{ TaskPath = 'ERROR'; TaskName = $_.Exception.Message; State = ''; Author = ''; RunAsUser = ''; RunLevel = ''; Triggers = ''; Actions = ''; LastRunTime = ''; LastResult = ''; NextRunTime = ''; Description = ''; SuspiciousHits = ''; UserWritableTarget = $false; SHA256 = '' }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'scheduled_task_files' -Format 'Csv' -Description 'Task XML files on disk with timestamps (creation-time pivot)' -Script {
        $base = "$env:SystemRoot\System32\Tasks"
        if (-not (Test-Path -LiteralPath $base)) { return @() }
        Get-ChildItem -LiteralPath $base -Recurse -File -Force -ErrorAction SilentlyContinue |
            Select-Object FullName,
            @{n = 'CreatedUtc'; e = { $_.CreationTimeUtc.ToString('yyyy-MM-dd HH:mm:ss') } },
            @{n = 'ModifiedUtc'; e = { $_.LastWriteTimeUtc.ToString('yyyy-MM-dd HH:mm:ss') } },
            Length |
            Sort-Object ModifiedUtc -Descending
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'services' -Format 'Csv' -Description 'All services with binary path, signature, hash, unquoted-path flag, and ServiceDll for svchost-hosted services' -Script {
        $rows = @()
        foreach ($s in (Get-CimInstance Win32_Service -ErrorAction SilentlyContinue)) {
            $pathName = $s.PathName
            $exe = ''
            if ($pathName) {
                if ($pathName.StartsWith('"')) { $exe = $pathName.Split('"')[1] }
                else {
                    $m = [regex]::Match($pathName, '^(.*?\.exe)', 'IgnoreCase')
                    if ($m.Success) { $exe = $m.Groups[1].Value } else { $exe = $pathName.Split(' ')[0] }
                }
                $exe = [Environment]::ExpandEnvironmentVariables($exe)
            }
            $unquoted = $false
            if ($pathName -and -not $pathName.StartsWith('"') -and $pathName -match ' ' -and $exe -match ' ') { $unquoted = $true }
            $sig = Get-FotonSignatureInfo -Path $exe
            # svchost-hosted services all share one image; the code that runs is the
            # ServiceDll under Parameters (older services set it on the service key).
            # Per-user instances (Name_<hex>) carry no ServiceDll of their own; svchost
            # loads the template service's, and templates are not listed by Win32_Service.
            $dll = ''
            $dllSource = ''
            $dllSig = [PSCustomObject]@{ Status = ''; Signer = '' }
            $dllHash = ''
            if ($exe -match '(?i)\\svchost\.exe$') {
                $svcRoot = 'HKLM:\SYSTEM\CurrentControlSet\Services\'
                $dll = [string](Get-FotonRegValue -Path ($svcRoot + $s.Name + '\Parameters') -Name 'ServiceDll')
                if ($dll) { $dllSource = 'Parameters' }
                else {
                    $dll = [string](Get-FotonRegValue -Path ($svcRoot + $s.Name) -Name 'ServiceDll')
                    if ($dll) { $dllSource = 'ServiceKey' }
                }
                $inst = [regex]::Match([string]$s.Name, '^(?<base>.+)_[0-9a-fA-F]{4,8}$')
                if (-not $dll -and $inst.Success) {
                    $base = $inst.Groups['base'].Value
                    $dll = [string](Get-FotonRegValue -Path ($svcRoot + $base + '\Parameters') -Name 'ServiceDll')
                    if (-not $dll) { $dll = [string](Get-FotonRegValue -Path ($svcRoot + $base) -Name 'ServiceDll') }
                    if ($dll) { $dllSource = 'Template:' + $base }
                }
                if ($dll) {
                    $dll = [Environment]::ExpandEnvironmentVariables($dll)
                    $dllSig = Get-FotonSignatureInfo -Path $dll
                    $dllHash = Get-FotonSafeHash -Path $dll
                }
            }
            $rows += [PSCustomObject]@{
                Name            = $s.Name
                DisplayName     = $s.DisplayName
                State           = $s.State
                StartMode       = $s.StartMode
                StartName       = $s.StartName
                ProcessId       = $s.ProcessId
                PathName        = $pathName
                ResolvedExe     = $exe
                ServiceType     = $s.ServiceType
                UnquotedPath    = $unquoted
                UserWritablePath = (Test-FotonUserWritablePath -Path $exe)
                SignatureStatus = $sig.Status
                Signer          = $sig.Signer
                SHA256          = (Get-FotonSafeHash -Path $exe)
                ServiceDll      = $dll
                ServiceDllSource = $dllSource
                ServiceDllUserWritablePath = $(if ($dll) { Test-FotonUserWritablePath -Path $dll } else { $false })
                ServiceDllSignatureStatus  = $dllSig.Status
                ServiceDllSigner           = $dllSig.Signer
                ServiceDllSHA256           = $dllHash
                Description     = $s.Description
            }
        }
        $rows | Sort-Object Name
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'wmi_event_subscriptions' -Format 'Txt' -Description 'WMI filters, consumers, and bindings (fileless persistence)' -Script {
        $out = @()
        foreach ($class in @('__EventFilter', '__EventConsumer', '__FilterToConsumerBinding')) {
            $out += "=== $class ==="
            try {
                $items = Get-CimInstance -Namespace 'root\subscription' -ClassName $class -ErrorAction Stop
                if ($items) { $out += ($items | Format-List * | Out-String -Width 400) }
                else { $out += '(none)' }
            }
            catch { $out += "Error: $($_.Exception.Message)" }
        }
        $out
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'winlogon_lsa_and_dll_hijacks' -Format 'Csv' -Description 'Winlogon Shell/Userinit, AppInit_DLLs, LSA packages, IFEO debuggers' -Script {
        $rows = @()
        $winlogon = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
        foreach ($n in @('Shell', 'Userinit', 'Taskman', 'AppSetup', 'GinaDLL', 'UserInitMprLogonScript')) {
            $val = Get-FotonRegValue -Path $winlogon -Name $n
            if ($val) { $rows += [PSCustomObject]@{ Mechanism = 'Winlogon'; Location = $winlogon; Name = $n; Value = [string]$val } }
        }
        foreach ($k in @('HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows')) {
            foreach ($n in @('AppInit_DLLs', 'LoadAppInit_DLLs', 'Load', 'Run', 'IconServiceLib')) {
                $val = Get-FotonRegValue -Path $k -Name $n
                if ($null -ne $val -and "$val" -ne '') { $rows += [PSCustomObject]@{ Mechanism = 'AppInit/Windows'; Location = $k; Name = $n; Value = [string]$val } }
            }
        }
        $lsa = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa'
        foreach ($n in @('Authentication Packages', 'Notification Packages', 'Security Packages')) {
            $val = Get-FotonRegValue -Path $lsa -Name $n
            if ($val) { $rows += [PSCustomObject]@{ Mechanism = 'LSA Package'; Location = $lsa; Name = $n; Value = ($val -join ', ') } }
        }
        $val = Get-FotonRegValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders' -Name 'SecurityProviders'
        if ($val) { $rows += [PSCustomObject]@{ Mechanism = 'SecurityProviders'; Location = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders'; Name = 'SecurityProviders'; Value = [string]$val } }

        $ifeo = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options'
        if (Test-Path -LiteralPath $ifeo) {
            foreach ($sub in (Get-ChildItem -LiteralPath $ifeo -ErrorAction SilentlyContinue)) {
                foreach ($n in @('Debugger', 'GlobalFlag', 'MonitorProcess')) {
                    $v = Get-FotonRegValue -Path $sub.PSPath -Name $n
                    if ($v) { $rows += [PSCustomObject]@{ Mechanism = 'IFEO'; Location = $sub.Name; Name = $n; Value = [string]$v } }
                }
            }
        }
        foreach ($n in @('AppCertDlls')) {
            foreach ($v in (Get-FotonRegValueList -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDlls')) {
                $rows += [PSCustomObject]@{ Mechanism = 'AppCertDlls'; Location = $v.Key; Name = $v.Name; Value = $v.Value }
            }
        }
        foreach ($v in (Get-FotonRegValueList -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager')) {
            if ($v.Name -in @('BootExecute', 'SetupExecute', 'Execute', 'S0InitialCommand', 'PendingFileRenameOperations')) {
                $rows += [PSCustomObject]@{ Mechanism = 'SessionManager'; Location = $v.Key; Name = $v.Name; Value = $v.Value }
            }
        }
        foreach ($v in (Get-FotonRegValueList -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors')) {
            $rows += [PSCustomObject]@{ Mechanism = 'PrintMonitor'; Location = $v.Key; Name = $v.Name; Value = $v.Value }
        }
        foreach ($v in (Get-FotonRegValueList -Path 'HKLM:\SOFTWARE\Microsoft\Netsh')) {
            $rows += [PSCustomObject]@{ Mechanism = 'NetshHelper'; Location = $v.Key; Name = $v.Name; Value = $v.Value }
        }
        foreach ($hiveRoot in (Get-FotonUserHiveRoot)) {
            $env_ = Join-Path $hiveRoot.Root 'Environment'
            foreach ($n in @('UserInitMprLogonScript')) {
                $v = Get-FotonRegValue -Path $env_ -Name $n
                if ($v) { $rows += [PSCustomObject]@{ Mechanism = 'LogonScript'; Location = "$($hiveRoot.UserName): $env_"; Name = $n; Value = [string]$v } }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'accessibility_binary_integrity' -Format 'Csv' -Description 'Sticky-keys style backdoor check on accessibility binaries' -Script {
        $targets = @('sethc.exe', 'utilman.exe', 'osk.exe', 'Magnify.exe', 'Narrator.exe', 'DisplaySwitch.exe', 'AtBroker.exe')
        $rows = @()
        foreach ($t in $targets) {
            $p = Join-Path "$env:SystemRoot\System32" $t
            if (-not (Test-Path -LiteralPath $p)) { continue }
            $item = Get-Item -LiteralPath $p -Force -ErrorAction SilentlyContinue
            $sig = Get-FotonSignatureInfo -Path $p
            $ifeoDebugger = Get-FotonRegValue -Path ("HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\$t") -Name 'Debugger'
            $rows += [PSCustomObject]@{
                Binary          = $t
                Path            = $p
                SizeBytes       = $item.Length
                ModifiedUtc     = $item.LastWriteTimeUtc.ToString('yyyy-MM-dd HH:mm:ss')
                SignatureStatus = $sig.Status
                IsOSBinary      = $sig.IsOSBinary
                IFEODebugger    = [string]$ifeoDebugger
                SHA256          = (Get-FotonSafeHash -Path $p)
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'cmd_autorun' -Format 'Csv' -Description 'cmd.exe Command Processor AutoRun (HKLM and loaded HKCU)' -Script {
        $rows = @()
        foreach ($rel in @('SOFTWARE\Microsoft\Command Processor', 'SOFTWARE\Wow6432Node\Microsoft\Command Processor')) {
            $path = 'HKLM:\' + $rel
            if ($rel -like '*Wow6432Node*' -and -not (Test-Path -LiteralPath 'HKLM:\SOFTWARE\Wow6432Node')) { continue }
            $val = Get-FotonRegValue -Path $path -Name 'AutoRun'
            $shown = '(not set)'
            if ($null -ne $val -and [string]$val -ne '') { $shown = [string]$val }
            $rows += [PSCustomObject]@{ Scope = 'HKLM'; Key = $rel; Command = $shown }
        }
        foreach ($h in @(Get-FotonUserHiveRoot)) {
            foreach ($rel in @('Software\Microsoft\Command Processor', 'Software\Wow6432Node\Microsoft\Command Processor')) {
                $val = Get-FotonRegValue -Path (Join-Path $h.Root $rel) -Name 'AutoRun'
                if ($null -eq $val -or [string]$val -eq '') { continue }
                $rows += [PSCustomObject]@{ Scope = $h.UserName; Key = $rel; Command = [string]$val }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'session_manager_boot' -Format 'Csv' -Description 'Session Manager BootExecute family (includes PFRO as a live fact)' -Script {
        $rows = @()
        $sm = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
        foreach ($n in @('BootExecute', 'BootExecuteNoPnpSync', 'SetupExecute', 'Execute', 'S0InitialCommand', 'PendingFileRenameOperations')) {
            $val = Get-FotonRegValue -Path $sm -Name $n
            $shown = '(not set)'
            if ($null -ne $val) {
                $parts = @()
                foreach ($x in @($val)) { if ([string]$x) { $parts += [string]$x } }
                if ($parts.Count -gt 0) { $shown = ($parts -join ' ') }
            }
            $rows += [PSCustomObject]@{ Name = $n; Value = $shown }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'known_dlls' -Format 'Csv' -Description 'Session Manager KnownDLLs search-order map' -Script {
        $rows = @()
        foreach ($v in (Get-FotonRegValueList -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs')) {
            $rows += [PSCustomObject]@{ Name = $v.Name; MappedFile = $v.Value }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'winsock_lsp' -Format 'Csv' -Description 'Winsock protocol and namespace catalog LibraryPath' -Script {
        $rows = @()
        $protoRoot = 'HKLM:\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9'
        foreach ($leaf in @('Catalog_Entries', 'Catalog_Entries64')) {
            $root = Join-Path $protoRoot $leaf
            if (-not (Test-Path -LiteralPath $root)) { continue }
            $kind = 'Protocol'
            if ($leaf -eq 'Catalog_Entries64') { $kind = 'Protocol64' }
            foreach ($sub in (Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) {
                $packed = Get-FotonRegValue -Path $sub.PSPath -Name 'PackedCatalogItem'
                $rows += [PSCustomObject]@{
                    Kind        = $kind
                    Entry       = $sub.PSChildName
                    LibraryPath = Get-FotonWinsockPackedPath -Bytes $packed
                }
            }
        }
        $ns = 'HKLM:\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\NameSpace_Catalog5\Catalog_Entries'
        foreach ($sub in (Get-ChildItem -LiteralPath $ns -ErrorAction SilentlyContinue)) {
            $lib = Get-FotonRegValue -Path $sub.PSPath -Name 'LibraryPath'
            $shown = '(not set)'
            if ($null -ne $lib -and [string]$lib -ne '') { $shown = [string]$lib }
            $rows += [PSCustomObject]@{ Kind = 'Namespace'; Entry = $sub.PSChildName; LibraryPath = $shown }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'active_setup' -Format 'Csv' -Description 'HKLM Active Setup Installed Components StubPath' -Script {
        $rows = @()
        foreach ($root in @(
                'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components',
                'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Active Setup\Installed Components'
            )) {
            if (-not (Test-Path -LiteralPath $root)) { continue }
            foreach ($sub in (Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) {
                $stub = Get-FotonRegValue -Path $sub.PSPath -Name 'StubPath'
                $ver = Get-FotonRegValue -Path $sub.PSPath -Name 'Version'
                $rows += [PSCustomObject]@{
                    Key      = $root
                    Guid     = $sub.PSChildName
                    StubPath = $(if ($null -eq $stub -or [string]$stub -eq '') { '(not set)' } else { [string]$stub })
                    Version  = $(if ($null -eq $ver -or [string]$ver -eq '') { '(not set)' } else { [string]$ver })
                }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'silent_process_exit' -Format 'Csv' -Description 'SilentProcessExit MonitorProcess/ReportingMode (and IFEO GlobalFlag when present)' -Script {
        $rows = @()
        $root = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit'
        $ifeo = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options'
        if (Test-Path -LiteralPath $root) {
            foreach ($sub in (Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) {
                $name = [string]$sub.PSChildName
                $gflag = Get-FotonRegValue -Path (Join-Path $ifeo $name) -Name 'GlobalFlag'
                $gShown = '(not set)'
                if ($null -ne $gflag -and [string]$gflag -ne '') { $gShown = [string]$gflag }
                $mon = Get-FotonRegValue -Path $sub.PSPath -Name 'MonitorProcess'
                $mode = Get-FotonRegValue -Path $sub.PSPath -Name 'ReportingMode'
                $dump = Get-FotonRegValue -Path $sub.PSPath -Name 'LocalDumpFolder'
                $monS = '(not set)'; if ($null -ne $mon -and [string]$mon -ne '') { $monS = [string]$mon }
                $modeS = '(not set)'; if ($null -ne $mode -and [string]$mode -ne '') { $modeS = [string]$mode }
                $dumpS = '(not set)'; if ($null -ne $dump -and [string]$dump -ne '') { $dumpS = [string]$dump }
                $rows += [PSCustomObject]@{
                    ImageName       = $name
                    MonitorProcess  = $monS
                    ReportingMode   = $modeS
                    LocalDumpFolder = $dumpS
                    IfeoGlobalFlag  = $gShown
                }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'netsh_helpers' -Format 'Csv' -Description 'Netsh helper DLL registrations (HKLM NetSh)' -Script {
        $rows = @()
        foreach ($root in @('HKLM:\SOFTWARE\Microsoft\NetSh', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\NetSh')) {
            foreach ($v in (Get-FotonRegValueList -Path $root)) {
                if (-not $v.Name -or $v.Name -eq '(default)') { continue }
                $rows += [PSCustomObject]@{ Key = $root; Name = $v.Name; Dll = $v.Value }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'time_providers' -Format 'Csv' -Description 'W32Time TimeProviders DllName (not NTP peer churn)' -Script {
        $rows = @()
        $root = 'HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders'
        if (Test-Path -LiteralPath $root) {
            foreach ($sub in (Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue)) {
                $dll = Get-FotonRegValue -Path $sub.PSPath -Name 'DllName'
                $en = Get-FotonRegValue -Path $sub.PSPath -Name 'Enabled'
                $inp = Get-FotonRegValue -Path $sub.PSPath -Name 'InputProvider'
                $dllS = '(not set)'; if ($null -ne $dll -and [string]$dll -ne '') { $dllS = [string]$dll }
                $enS = '(not set)'; if ($null -ne $en -and [string]$en -ne '') { $enS = [string]$en }
                $inpS = '(not set)'; if ($null -ne $inp -and [string]$inp -ne '') { $inpS = [string]$inp }
                $rows += [PSCustomObject]@{
                    Name          = $sub.PSChildName
                    DllName       = $dllS
                    Enabled       = $enS
                    InputProvider = $inpS
                }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'screensaver' -Format 'Csv' -Description 'Screensaver executable from Desktop and policy keys (loaded hives). User churn; not a baseline.' -Script {
        $rows = @()
        $names = @('SCRNSAVE.EXE', 'ScreenSaveActive', 'ScreenSaverIsSecure', 'ScreenSaveTimeOut')
        $emitKey = {
            param($Scope, $User, $Source, $Path)
            if (-not (Test-Path -LiteralPath $Path)) { return }
            $any = $false
            $got = @()
            foreach ($n in $names) {
                $val = Get-FotonRegValue -Path $Path -Name $n
                if ($null -eq $val -or [string]$val -eq '') { continue }
                $any = $true
                $got += [PSCustomObject]@{ Scope = $Scope; User = $User; Source = $Source; ValueName = $n; Value = [string]$val }
            }
            if ($any) { $got }
        }
        foreach ($r in @(& $emitKey 'Machine' 'N/A' 'Policy' 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Control Panel\Desktop')) {
            $rows += $r
        }
        foreach ($h in @(Get-FotonUserHiveRoot)) {
            $desktop = Join-Path $h.Root 'Control Panel\Desktop'
            $policy = Join-Path $h.Root 'Software\Policies\Microsoft\Windows\Control Panel\Desktop'
            foreach ($r in @(& $emitKey 'User' $h.UserName 'Desktop' $desktop)) { $rows += $r }
            foreach ($r in @(& $emitKey 'User' $h.UserName 'Policy' $policy)) { $rows += $r }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'com_hijack_candidates' -Format 'Csv' -Description 'Per-user COM CLSID overrides pointing to user-writable DLLs' -Script {
        $rows = @()
        foreach ($hiveRoot in (Get-FotonUserHiveRoot)) {
            $clsidRoot = Join-Path $hiveRoot.Root 'Software\Classes\CLSID'
            if (-not (Test-Path -LiteralPath $clsidRoot)) { continue }
            foreach ($clsid in (Get-ChildItem -LiteralPath $clsidRoot -ErrorAction SilentlyContinue)) {
                foreach ($server in @('InprocServer32', 'LocalServer32', 'InprocHandler32')) {
                    $sp = Join-Path $clsid.PSPath $server
                    if (-not (Test-Path -LiteralPath $sp)) { continue }
                    $default = Get-FotonRegValue -Path $sp -Name '(default)'
                    if (-not $default) { continue }
                    $rows += [PSCustomObject]@{
                        User        = $hiveRoot.UserName
                        CLSID       = (Split-Path $clsid.Name -Leaf)
                        ServerType  = $server
                        Target      = [string]$default
                        UserWritableTarget = (Test-FotonUserWritablePath -Path ([string]$default))
                    }
                }
            }
        }
        $rows
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'bits_jobs' -Format 'Txt' -Description 'Background Intelligent Transfer jobs (download persistence)' -Script {
        $out = @()
        try {
            $jobs = Get-BitsTransfer -AllUsers -ErrorAction Stop
            if ($jobs) { $out += ($jobs | Select-Object JobId, DisplayName, JobState, OwnerAccount, TransferType, CreationTime, FileList | Format-List | Out-String -Width 400) }
            else { $out += '(no BITS jobs)' }
        }
        catch { $out += "Get-BitsTransfer unavailable: $($_.Exception.Message)" }
        $out += '--- bitsadmin /list /allusers /verbose ---'
        $out += (& bitsadmin.exe /list /allusers /verbose 2>&1 | Out-String)
        $out
    } | Out-Null

    Invoke-FotonCollection -Category 'Persistence' -Name 'office_addins_and_templates' -Format 'Csv' -Description 'Office add-ins and startup template locations' -Script {
        $rows = @()
        foreach ($hiveRoot in (Get-FotonUserHiveRoot)) {
            $base = Join-Path $hiveRoot.Root 'Software\Microsoft\Office'
            if (-not (Test-Path -LiteralPath $base)) { continue }
            foreach ($addin in (Get-ChildItem -LiteralPath $base -Recurse -ErrorAction SilentlyContinue |
                        Where-Object { $_.Name -match 'Addins|Resiliency|Security\\Trusted Locations' })) {
                foreach ($v in (Get-FotonRegValueList -Path $addin.PSPath)) {
                    $rows += [PSCustomObject]@{ User = $hiveRoot.UserName; Key = $v.Key; Name = $v.Name; Value = $v.Value }
                }
            }
        }
        foreach ($userProfile in (Get-FotonUserProfile)) {
            $startup = Join-Path $userProfile.ProfilePath 'AppData\Roaming\Microsoft\Word\STARTUP'
            if (Test-Path -LiteralPath $startup) {
                foreach ($f in (Get-ChildItem -LiteralPath $startup -Force -ErrorAction SilentlyContinue)) {
                    $rows += [PSCustomObject]@{ User = $userProfile.UserName; Key = 'Word STARTUP folder'; Name = $f.Name; Value = $f.FullName }
                }
            }
        }
        $rows
    } | Out-Null
}