using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; namespace OversightService.Services { /// /// Manages scheduled osquery-based tasks /// Designed to be portable to Windows Service (OversightService) later /// public class OsqueryTaskScheduler { private readonly List _tasks = new(); private readonly Timer _timer; private readonly string _stateFilePath; private bool _isRunning; public OsqueryTaskScheduler() { // Store task state in AppData to persist last run times var appDataDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PSG-Oversight" ); Directory.CreateDirectory(appDataDir); _stateFilePath = Path.Combine(appDataDir, "task_scheduler_state.json"); // Check every minute for tasks that need to run _timer = new Timer(CheckAndRunTasks, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); } /// /// Register a task with the scheduler /// public void RegisterTask(IScheduledTask task) { _tasks.Add(task); LoadTaskState(task); } /// /// Start the scheduler /// public void Start() { _isRunning = true; } /// /// Stop the scheduler /// public void Stop() { _isRunning = false; _timer?.Change(Timeout.Infinite, Timeout.Infinite); } private async void CheckAndRunTasks(object? state) { if (!_isRunning) return; foreach (var task in _tasks.Where(t => t.ShouldRun())) { try { await task.ExecuteAsync(); task.LastRun = DateTime.Now; SaveTaskState(task); } catch (Exception ex) { LogError($"Task '{task.TaskName}' failed: {ex.Message}"); } } } private void LoadTaskState(IScheduledTask task) { try { if (!File.Exists(_stateFilePath)) return; var json = File.ReadAllText(_stateFilePath); var states = JsonConvert.DeserializeObject>(json); if (states != null && states.TryGetValue(task.TaskName, out var lastRun)) { task.LastRun = lastRun; } } catch (Exception ex) { LogError($"Failed to load task state: {ex.Message}"); } } private void SaveTaskState(IScheduledTask task) { try { Dictionary states; if (File.Exists(_stateFilePath)) { var json = File.ReadAllText(_stateFilePath); states = JsonConvert.DeserializeObject>(json) ?? new Dictionary(); } else { states = new Dictionary(); } states[task.TaskName] = task.LastRun ?? DateTime.Now; var updatedJson = JsonConvert.SerializeObject(states, Formatting.Indented); File.WriteAllText(_stateFilePath, updatedJson); } catch (Exception ex) { LogError($"Failed to save task state: {ex.Message}"); } } private void LogError(string message) { try { var logDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PSG-Oversight" ); var logPath = Path.Combine(logDir, "scheduler_error.log"); File.AppendAllText(logPath, $"[{DateTime.Now}] {message}\n"); } catch { // Silently fail if we can't write logs } } public void Dispose() { Stop(); _timer?.Dispose(); } } }