using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using Hardcodet.Wpf.TaskbarNotification; using LD_SysInfo.Models; using LD_SysInfo.Services; using MaterialDesignColors; using MaterialDesignThemes.Wpf; using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Application = System.Windows.Application; namespace LD_SysInfo { public partial class MainWindow : Window { private static readonly HttpClient httpClient = new HttpClient(); private static AppConfig _config = null!; // Initialized in LoadConfig private static string? jwtToken = null; // 🔹 Store the JWT token globally in the app private readonly DispatcherTimer messageClearTimer = null!; // Initialized in constructor private readonly DispatcherTimer postTimer = null!; // Initialized in constructor private readonly DispatcherTimer keepAliveTimer = null!; // Initialized in constructor private readonly DispatcherTimer tokenRefreshTimer = null!; // Initialized in constructor private readonly Uri LightThemeUri = new Uri("pack://application:,,,/Themes/LightTheme.xaml", UriKind.Absolute); private readonly Uri DarkThemeUri = new Uri("pack://application:,,,/Themes/DarkTheme.xaml", UriKind.Absolute); private bool isDarkTheme = false; private SystemInfo _cachedSystemInfo = null!; // Initialized in DisplaySystemInfo private static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.json"); // Event to signal when system info loading is complete public event EventHandler? SystemInfoLoaded; public MainWindow() { // ⚠️ Temporary SSL Certificate Bypass - For Testing Only System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; InitializeComponent(); var exePath = Assembly.GetEntryAssembly()?.Location; if (string.IsNullOrEmpty(exePath)) { exePath = Process.GetCurrentProcess().MainModule?.FileName; } if (!string.IsNullOrEmpty(exePath)) { var fvi = FileVersionInfo.GetVersionInfo(exePath); var productVersion = fvi.ProductVersion ?? "0.0.0"; var cleanVersion = productVersion.Split('+')[0]; VersionTextBlock.Text = $"v{cleanVersion}"; } else { VersionTextBlock.Text = "v0.0.0"; } LoadConfig(); // Load system info asynchronously to avoid blocking UI Task.Run(async () => await DisplaySystemInfoAsync()); AutoLogin(); // 🔍 Perform initial connectivity check Task.Run(async () => await InitialCheckConnectivity()); // Initialize the system tray icon event TrayIcon.TrayMouseDoubleClick += TrayIcon_DoubleClick; // 🔥 Initialize the DispatcherTimer for fading out messages (statically set to 5 seconds) messageClearTimer = new DispatcherTimer(); messageClearTimer.Interval = TimeSpan.FromSeconds(5); // Set to 5 seconds (static) messageClearTimer.Tick += MessageClearTimer_Tick; // 🔥 Initialize the DispatcherTimer for periodic POSTs using _config.PollingInterval postTimer = new DispatcherTimer(); postTimer.Interval = TimeSpan.FromSeconds(_config.SystemInfoInterval); postTimer.Tick += PostTimer_Tick; postTimer.Start(); // 🔁 Initialize the KeepAlive timer using KeepAlivePeriod from config keepAliveTimer = new DispatcherTimer(); keepAliveTimer.Interval = TimeSpan.FromSeconds(_config.KeepAlivePeriod); // Set from config keepAliveTimer.Tick += KeepAliveTimer_Tick; keepAliveTimer.Start(); // 🔄 Initialize the Token Refresh timer to refresh token proactively before expiration // Tokens expire in 60 minutes, so refresh at 50 minutes to be safe tokenRefreshTimer = new DispatcherTimer(); tokenRefreshTimer.Interval = TimeSpan.FromMinutes(50); tokenRefreshTimer.Tick += TokenRefreshTimer_Tick; tokenRefreshTimer.Start(); // Ensure initial refresh occurs after UI is ready this.Loaded += async (s, e) => { // Small delay so UI visuals finish initializing await Task.Delay(50); await RefreshSystemInfoAsync(); }; } private async void RunOsqueryQuery_Click(object sender, RoutedEventArgs e) { OsqueryOutputBox.Text = "Running query..."; string sql = OsqueryQueryBox.Text.Trim(); try { var result = await Task.Run(() => OsqueryService.Query(sql)); string prettyJson = System.Text.Json.JsonSerializer.Serialize(result, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); OsqueryOutputBox.Text = prettyJson; } catch (Exception ex) { OsqueryOutputBox.Text = $"❌ Error running query:\n{ex}"; } } private void LoadConfig() { try { //System.Windows.MessageBox.Show($"DEBUG: AppContext.BaseDirectory = {AppContext.BaseDirectory}"); //System.Windows.MessageBox.Show($"DEBUG: ConfigPath = {ConfigPath}"); if (!File.Exists(ConfigPath)) { System.Windows.MessageBox.Show("❌ config.json not found at resolved path!"); } _config = ConfigManager.LoadConfig(ConfigPath); if (_config == null || string.IsNullOrWhiteSpace(_config.ClientIdentifier)) { throw new Exception("❌ Invalid or missing ClientIdentifier in config.json."); } } catch (Exception ex) { System.Windows.MessageBox.Show($"❌ Error loading config: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); _config = new AppConfig(); // Prevent application from crashing } } private async Task InitialCheckConnectivity() { var apiClient = new ApiClient(_config); bool isConnected = await apiClient.CheckConnectivity(); // You must update the UI from the UI thread Dispatcher.Invoke(() => { if (isConnected) { ConnectionStatusTextBlock.Text = "Connected to Server"; ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green); } else { ConnectionStatusTextBlock.Text = "No Connection"; ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red); } }); } private async void AutoLogin() { if (_config == null || _config.Auth == null || string.IsNullOrEmpty(_config.Auth.Username) || string.IsNullOrEmpty(_config.Auth.Password)) { UpdateStatusUI(false); return; } var apiClient = new ApiClient(_config); LoginResponse loginResponse = await apiClient.AuthenticateAsync(_config.Auth.Username, _config.Auth.Password); if (loginResponse != null && !string.IsNullOrEmpty(loginResponse.Token)) { ApiClient.SetJwtToken(loginResponse.Token); UpdateStatusUI(true); } else { UpdateStatusUI(false); } } private async void AuthenticateButton_Click(object sender, RoutedEventArgs e) { var apiClient = new ApiClient(_config); LoginResponse loginResponse = await apiClient.AuthenticateAsync(txtUsername.Text, pwdPassword.Password); if (loginResponse != null && !string.IsNullOrEmpty(loginResponse.Token)) { ApiClient.SetJwtToken(loginResponse.Token); UpdateStatusUI(true); // 🔥 Successfully authenticated } else { UpdateStatusUI(false); } } private async void CheckConnectivityButton_Click(object sender, RoutedEventArgs e) { var apiClient = new ApiClient(_config); bool isConnected = await apiClient.CheckConnectivity(); if (isConnected) { ConnectionStatusTextBlock.Text = "Connected to Server"; ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green); } else { ConnectionStatusTextBlock.Text = "No Connection"; ConnectionStatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red); } } private void GenerateLogsButton_Click(object sender, RoutedEventArgs e) { try { string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LD_SysInfo", "Logs"); Directory.CreateDirectory(logDir); // Ensure directory exists string logFile = Path.Combine(logDir, $"log_{DateTime.Now:yyyyMMdd_HHmms}.txt"); File.WriteAllText(logFile, "=== LD SysInfo Diagnostic Log ===\n"); File.AppendAllText(logFile, $"Timestamp: {DateTime.Now}\n"); File.AppendAllText(logFile, $"Hostname: {Environment.MachineName}\n"); File.AppendAllText(logFile, $"OS: {Environment.OSVersion}\n"); File.AppendAllText(logFile, $"Server Status: {ConnectionStatusTextBlock.Text}\n"); System.Windows.MessageBox.Show($"Logs generated successfully!\nPath: {logFile}", "Log Generated", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { System.Windows.MessageBox.Show($"Failed to generate logs.\nError: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } private async Task DisplaySystemInfoAsync() { // Run system info collection on background thread var sysInfo = await Task.Run(() => SystemInfo.GetSystemInfo()); // Update UI on UI thread await Dispatcher.InvokeAsync(() => { _cachedSystemInfo = sysInfo; UpdateSysInfoUI(_cachedSystemInfo); // Notify that system info has loaded SystemInfoLoaded?.Invoke(this, EventArgs.Empty); }); } private void DisplaySystemInfo() { _cachedSystemInfo = SystemInfo.GetSystemInfo(); UpdateSysInfoUI(_cachedSystemInfo); } private void LoadInstalledAppsButton_Click(object sender, RoutedEventArgs e) { var sysInfo = SystemInfo.GetSystemInfo(); InstalledAppsListBox.ItemsSource = sysInfo.InstalledApplications; } private async void StoreSystemInfoButton_Click(object sender, RoutedEventArgs e) { if (string.IsNullOrEmpty(ApiClient.GetJwtToken())) // Check token via ApiClient { System.Windows.MessageBox.Show("❌ Please log in first.", "Not Authenticated", MessageBoxButton.OK, MessageBoxImage.Warning); return; } try { var sysInfo = SystemInfo.GetSystemInfo(); var applications = sysInfo.InstalledApplications; var formattedApplications = new List(); foreach (var app in applications) { formattedApplications.Add(new { app_name = app.Name, app_version = app.Version, publisher = app.Publisher }); } var formattedUserApplications = new List(); foreach (var app in sysInfo.UserInstalledApplications) { formattedUserApplications.Add(new { app_name = app.Name, app_version = app.Version, publisher = app.Publisher }); } var formattedWindowsUpdates = new List(); foreach (var update in sysInfo.WindowsUpdates) { formattedWindowsUpdates.Add(new { hotFixID = update.HotFixID, description = update.Description, installedOn = update.InstalledOn, installedBy = update.InstalledBy }); } var formattedAppXPackages = new List(); foreach (var pkg in sysInfo.AppXPackages) { formattedAppXPackages.Add(new { name = pkg.Name, version = pkg.Version, publisher = pkg.Publisher, packageFullName = pkg.PackageFullName }); } var payload = new { clientIdentifier = _config.ClientIdentifier, hostname = sysInfo.Hostname, osName = sysInfo.OSName, osVersion = sysInfo.OSVersion, windowsVersion = sysInfo.WindowsVersion, windowsBuild = sysInfo.WindowsBuild, osArchitecture = sysInfo.OSArchitecture, processorName = sysInfo.ProcessorName, processorArchitecture = sysInfo.ProcessorArchitecture, gpuName = sysInfo.GpuNames, totalMemory = sysInfo.TotalMemory, ipAddresses = sysInfo.IpAddresses, lastBootTime = sysInfo.LastBootTime, drives = sysInfo.Drives, installedApplications = formattedApplications, windowsUpdates = formattedWindowsUpdates, appXPackages = formattedAppXPackages }; var apiClient = new ApiClient(_config); string jsonPayload = JsonConvert.SerializeObject(payload); Console.WriteLine("🔍 [DEBUG] Raw JSON Payload:\n" + jsonPayload); string encryptedPayload = EncryptionHelper.EncryptData(jsonPayload); string response = await apiClient.StoreSystemInfoAsync(encryptedPayload); if (response.StartsWith("Error")) { StatusTextBlock.Text = $"❌ {response}"; StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red); } else { StatusTextBlock.Text = "✅ System info stored successfully!"; StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green); // 🔥 Trigger Fade Out Animation messageClearTimer.Stop(); // Stop existing timer FadeOutStatusMessage(); // Start the fade-out animation } } catch (Exception ex) { StatusTextBlock.Text = $"❌ Error: {ex.Message}"; StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red); } } private void LoadThemeResources(string resourcePath) { var dictionaries = Application.Current.Resources.MergedDictionaries; dictionaries.Clear(); // Add required MaterialDesign v5 resource dictionaries dictionaries.Add(new ResourceDictionary { Source = new Uri("pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml", UriKind.Absolute) }); dictionaries.Add(new ResourceDictionary { Source = new Uri(isDarkTheme ? "pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Dark.xaml" : "pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Light.xaml", UriKind.Absolute) }); // Optional: Your custom theme overrides (LightTheme.xaml or DarkTheme.xaml) dictionaries.Add(new ResourceDictionary { Source = new Uri(resourcePath, UriKind.RelativeOrAbsolute) }); } private void ThemeToggleButton_Click(object sender, RoutedEventArgs e) { isDarkTheme = !isDarkTheme; LoadThemeResources(isDarkTheme ? "Themes/DarkTheme.xaml" : "Themes/LightTheme.xaml"); } private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) { this.DragMove(); } } private void MinimizeWindow_Click(object sender, RoutedEventArgs e) { WindowState = WindowState.Minimized; } private void MaximizeRestoreWindow_Click(object sender, RoutedEventArgs e) { if (WindowState == WindowState.Maximized) WindowState = WindowState.Normal; else WindowState = WindowState.Maximized; } private void CloseWindow_Click(object sender, RoutedEventArgs e) { Close(); } private void ExportToCsvButton_Click(object sender, RoutedEventArgs e) { var saveFileDialog = new Microsoft.Win32.SaveFileDialog { Filter = "CSV files (*.csv)|*.csv", Title = "Save System Info as CSV", FileName = "SystemInfo.csv" }; if (saveFileDialog.ShowDialog() == true) { var sysInfo = SystemInfo.GetSystemInfo(); try { using (var writer = new StreamWriter(saveFileDialog.FileName)) { writer.WriteLine("Property,Value"); writer.WriteLine($"Hostname,{sysInfo.Hostname}"); writer.WriteLine($"OS Name,{sysInfo.OSName}"); writer.WriteLine($"OS Version,{sysInfo.OSVersion}"); writer.WriteLine($"Windows Version,{sysInfo.WindowsVersion}"); writer.WriteLine($"Windows Build,{sysInfo.WindowsBuild}"); writer.WriteLine($"OS Architecture,{sysInfo.OSArchitecture}"); writer.WriteLine($"Processor Name,{sysInfo.ProcessorName}"); writer.WriteLine($"Processor Architecture,{sysInfo.ProcessorArchitecture}"); if (IncludeInstalledAppsCheckBox.IsChecked == true) { writer.WriteLine(); writer.WriteLine("Installed Applications:"); writer.WriteLine("Name,Version,Publisher"); var applications = sysInfo.InstalledApplications; foreach (var app in applications) { writer.WriteLine($"{app.Name},{app.Version},{app.Publisher}"); } } } StatusTextBlock.Text = "System info exported successfully!"; messageClearTimer.Start(); // Start the timer to clear the message after 5 seconds } catch (Exception ex) { StatusTextBlock.Text = $"Error saving file: {ex.Message}"; } } } private async void UpdateStatusUI(bool isAuthenticated) { // Ensure we're on the UI thread before updating UI elements Dispatcher.Invoke(() => { if (isAuthenticated) { txtUsername.Visibility = Visibility.Collapsed; pwdPassword.Visibility = Visibility.Collapsed; AuthenticateButton.Visibility = Visibility.Collapsed; StatusTextBlock.Text = "✅ Authenticated - Connected to Server"; StatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); // Use your custom brush // 🔍 Check connectivity to the server var apiClient = new ApiClient(_config); Task.Run(async () => { bool isConnected = await apiClient.CheckConnectivity(); // Update connection status on the UI thread Dispatcher.Invoke(() => { if (isConnected) { ConnectionStatusTextBlock.Text = "Connected to Server"; ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); // Use your custom brush } else { ConnectionStatusTextBlock.Text = "No Connection"; ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); // Use your custom error color } }); }); // 🔥 Start the timer to clear the message after a delay messageClearTimer.Stop(); // Stop any running timer first messageClearTimer.Start(); } else { txtUsername.Visibility = Visibility.Visible; pwdPassword.Visibility = Visibility.Visible; AuthenticateButton.Visibility = Visibility.Visible; StatusTextBlock.Text = "❌ Not Authenticated - Please Log In"; StatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); // Use your custom error color ConnectionStatusTextBlock.Text = "No Connection"; ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); // Use your custom error color // Stop the timer if the user is not authenticated messageClearTimer.Stop(); } }); } private void FadeOutStatusMessage() { var fadeOutAnimation = new DoubleAnimation { From = 1.0, To = 0.0, Duration = TimeSpan.FromSeconds(3), AutoReverse = false }; fadeOutAnimation.Completed += (s, e) => { StatusTextBlock.Text = string.Empty; // Clear the text after fading StatusTextBlock.Opacity = 1.0; // Reset the opacity for future use }; StatusTextBlock.BeginAnimation(OpacityProperty, fadeOutAnimation); messageClearTimer.Stop(); } private void OpenOsqueryConsole_Click(object sender, RoutedEventArgs e) { var console = new OsqueryConsole { Owner = this }; console.ShowDialog(); } private async void PostTimer_Tick(object? sender, EventArgs e) { if (string.IsNullOrEmpty(ApiClient.GetJwtToken())) // Ensure authentication is active { StatusTextBlock.Text = "❌ Not authenticated. Please log in first."; return; } var apiClient = new ApiClient(_config); // Create an instance of ApiClient _cachedSystemInfo = SystemInfo.GetSystemInfo(); var sysInfo = _cachedSystemInfo; var applications = sysInfo.InstalledApplications; var formattedApplications = new List(); foreach (var app in applications) { formattedApplications.Add(new { app_name = app.Name, app_version = app.Version, publisher = app.Publisher }); } var formattedUserApplications = new List(); foreach (var app in sysInfo.UserInstalledApplications) { formattedUserApplications.Add(new { app_name = app.Name, app_version = app.Version, publisher = app.Publisher }); } var formattedWindowsUpdates = new List(); foreach (var update in sysInfo.WindowsUpdates) { formattedWindowsUpdates.Add(new { hotFixID = update.HotFixID, description = update.Description, installedOn = update.InstalledOn, installedBy = update.InstalledBy }); } var formattedAppXPackages = new List(); foreach (var pkg in sysInfo.AppXPackages) { formattedAppXPackages.Add(new { name = pkg.Name, version = pkg.Version, publisher = pkg.Publisher, packageFullName = pkg.PackageFullName }); } var payload = new { clientIdentifier = _config.ClientIdentifier, hostname = sysInfo.Hostname, osName = sysInfo.OSName, osVersion = sysInfo.OSVersion, windowsVersion = sysInfo.WindowsVersion, windowsBuild = sysInfo.WindowsBuild, osArchitecture = sysInfo.OSArchitecture, processorName = sysInfo.ProcessorName, processorArchitecture = sysInfo.ProcessorArchitecture, gpuName = sysInfo.GpuNames, totalMemory = sysInfo.TotalMemory, ipAddresses = sysInfo.IpAddresses, lastBootTime = sysInfo.LastBootTime, drives = sysInfo.Drives, installedApplications = formattedApplications, userInstalledApplications = formattedUserApplications, windowsUpdates = formattedWindowsUpdates, appXPackages = formattedAppXPackages }; string jsonPayload = JsonConvert.SerializeObject(payload); Console.WriteLine("🔍 [DEBUG] Raw JSON Payload:\n" + jsonPayload); string encryptedPayload = EncryptionHelper.EncryptData(jsonPayload); string response = await apiClient.StoreSystemInfoAsync(encryptedPayload); if (response.StartsWith("Error")) { StatusTextBlock.Text = $"❌ {response}"; StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red); } else { StatusTextBlock.Text = "✅ System info stored successfully!"; StatusTextBlock.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green); messageClearTimer.Stop(); FadeOutStatusMessage(); } } private async void KeepAliveTimer_Tick(object? sender, EventArgs e) { var apiClient = new ApiClient(_config); bool isConnected = await apiClient.CheckConnectivity(); Dispatcher.Invoke(() => { if (isConnected) { ConnectionStatusTextBlock.Text = "Connected to Server (Ping)"; // Use the custom gold color for connection status ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); } else { ConnectionStatusTextBlock.Text = "No Connection"; // Use the custom error color for no connection ConnectionStatusTextBlock.Foreground = (SolidColorBrush)FindResource("ErrorBrush"); } }); } private void MessageClearTimer_Tick(object? sender, EventArgs e) { FadeOutStatusMessage(); // Trigger the fade-out effect } private async void TokenRefreshTimer_Tick(object? sender, EventArgs e) { if (string.IsNullOrEmpty(ApiClient.GetJwtToken())) { Console.WriteLine("⚠️ No token to refresh - skipping proactive refresh."); return; } var apiClient = new ApiClient(_config); bool refreshed = await apiClient.RefreshTokenAsync(); if (refreshed) { Console.WriteLine("✅ Proactive token refresh successful."); } else { Console.WriteLine("⚠️ Proactive token refresh failed - will retry on next request."); } } private void TrayIcon_DoubleClick(object sender, RoutedEventArgs e) { ShowWindow(); } private void ShowWindow_Click(object sender, RoutedEventArgs e) { ShowWindow(); } private void ShowWindow() { this.Show(); this.WindowState = WindowState.Normal; } private void Exit_Click(object sender, RoutedEventArgs e) { TrayIcon.Dispose(); System.Windows.Application.Current.Shutdown(); } // Minimize to system tray instead of closing protected override void OnStateChanged(EventArgs e) { if (WindowState == WindowState.Minimized) { this.Hide(); // Hide the window when minimized } base.OnStateChanged(e); } private void UpdateSysInfoUI(SystemInfo sysInfo) { if (sysInfo == null) return; HostnameTextBlock.Text = $"Hostname: {(string.IsNullOrEmpty(sysInfo.Hostname) ? "N/A" : sysInfo.Hostname)}"; OSNameTextBlock.Text = $"OS Name: {(string.IsNullOrEmpty(sysInfo.OSName) ? "N/A" : sysInfo.OSName)}"; OSVersionTextBlock.Text = $"OS Version: {(string.IsNullOrEmpty(sysInfo.OSVersion) ? "N/A" : sysInfo.OSVersion)}"; WindowsVersionTextBlock.Text = $"Windows Version: {(string.IsNullOrEmpty(sysInfo.WindowsVersion) ? "N/A" : sysInfo.WindowsVersion)}"; WindowsBuildTextBlock.Text = $"Windows Build: {(string.IsNullOrEmpty(sysInfo.WindowsBuild) ? "N/A" : sysInfo.WindowsBuild)}"; OSArchitectureTextBlock.Text = $"OS Architecture: {(string.IsNullOrEmpty(sysInfo.OSArchitecture) ? "N/A" : sysInfo.OSArchitecture)}"; ProcessorNameTextBlock.Text = $"Processor Name: {(string.IsNullOrEmpty(sysInfo.ProcessorName) ? "N/A" : sysInfo.ProcessorName)}"; ProcessorArchitectureTextBlock.Text = $"Processor Architecture: {(string.IsNullOrEmpty(sysInfo.ProcessorArchitecture) ? "N/A" : sysInfo.ProcessorArchitecture)}"; // Memory MemoryTextBlock.Text = $"Total Memory: {(string.IsNullOrEmpty(sysInfo.TotalMemory) ? "N/A" : sysInfo.TotalMemory)}"; // GPUs (join multiple models) GpuTextBlock.Text = (sysInfo.GpuNames == null || sysInfo.GpuNames.Count == 0) ? "GPU(s): N/A" : $"GPU(s): {string.Join(", ", sysInfo.GpuNames)}"; // IP addresses - format each on its own line for readability if (sysInfo.IpAddresses == null || sysInfo.IpAddresses.Count == 0) { IpTextBlock.Text = "IP Addresses: N/A"; } else { var parts = new List(); foreach (var ni in sysInfo.IpAddresses) { var ip = string.IsNullOrEmpty(ni.IpAddress) ? "N/A" : ni.IpAddress; parts.Add($" • {ip}"); } IpTextBlock.Text = $"IP Addresses:\n{string.Join("\n", parts)}"; } // CollectedAt / Last updated CollectedAtTextBlock.Text = !string.IsNullOrEmpty(sysInfo.CollectedAt) ? $"Last updated: {sysInfo.CollectedAt}" : $"Last updated: {DateTimeOffset.Now:g}"; } private async Task RefreshSystemInfoAsync() { try { // Show immediate feedback Dispatcher.Invoke(() => { StatusTextBlock.Text = "Collecting system info..."; try { StatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); } catch { StatusTextBlock.Foreground = new SolidColorBrush(Colors.Black); } }); // Run osquery work off the UI thread var info = await Task.Run(() => SystemInfo.GetSystemInfo()); _cachedSystemInfo = info; // Update UI on UI thread Dispatcher.Invoke(() => { UpdateSysInfoUI(info); CollectedAtTextBlock.Text = !string.IsNullOrEmpty(info.CollectedAt) ? $"Last updated: {info.CollectedAt}" : $"Last updated: {DateTimeOffset.Now:g}"; StatusTextBlock.Text = "✅ System info refreshed"; try { StatusTextBlock.Foreground = (SolidColorBrush)FindResource("StatusConnectedBrush"); } catch { StatusTextBlock.Foreground = new SolidColorBrush(Colors.Green); } }); } catch (Exception ex) { Dispatcher.Invoke(() => { StatusTextBlock.Text = $"❌ Refresh failed: {ex.Message}"; StatusTextBlock.Foreground = new SolidColorBrush(Colors.Red); }); } } private async void RefreshButton_Click(object sender, RoutedEventArgs e) { // Disable the button while running (UI lookup) if (sender is System.Windows.Controls.Button b) { b.IsEnabled = false; } await RefreshSystemInfoAsync(); if (sender is System.Windows.Controls.Button b2) { b2.IsEnabled = true; } } // ============================ // Service Tab Event Handlers // ============================ private void RefreshServiceStatus_Click(object sender, RoutedEventArgs e) { try { var serviceName = "PSG-Oversight"; var service = System.ServiceProcess.ServiceController.GetServices() .FirstOrDefault(s => s.ServiceName == serviceName); if (service != null) { ServiceStatusText.Text = service.Status.ToString(); ServiceStatusText.Foreground = service.Status == System.ServiceProcess.ServiceControllerStatus.Running ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red); } else { ServiceStatusText.Text = "Not Installed"; ServiceStatusText.Foreground = new SolidColorBrush(Colors.Orange); } ServiceLastCheckText.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } catch (Exception ex) { ServiceStatusText.Text = $"Error: {ex.Message}"; ServiceStatusText.Foreground = new SolidColorBrush(Colors.Red); } } private async void TestPatchCompliance_Click(object sender, RoutedEventArgs e) { ServiceLogOutput.Text = "Running Patch Compliance check...\n\n"; try { // Create instances of services var apiClient = new Services.ApiClient(_config); var patchTask = new Services.PatchComplianceTask(_config, apiClient); // Execute the task await patchTask.ExecuteAsync(); ServiceLogOutput.Text += $"[{DateTime.Now}] Patch compliance check completed successfully.\n"; ServiceLogOutput.Text += "Check %LOCALAPPDATA%\\PSG-Oversight\\patch_compliance.log for details.\n"; } catch (Exception ex) { ServiceLogOutput.Text += $"[{DateTime.Now}] ERROR: {ex.Message}\n"; ServiceLogOutput.Text += $"Stack Trace:\n{ex.StackTrace}\n"; } } private void ViewSchedulerState_Click(object sender, RoutedEventArgs e) { try { var stateFilePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PSG-Oversight", "task_scheduler_state.json" ); if (File.Exists(stateFilePath)) { var json = File.ReadAllText(stateFilePath); var formatted = JsonConvert.SerializeObject( JsonConvert.DeserializeObject(json), Formatting.Indented ); ServiceLogOutput.Text = $"Task Scheduler State:\n\n{formatted}"; } else { ServiceLogOutput.Text = "Task scheduler state file not found.\nThe service may not have run any tasks yet."; } } catch (Exception ex) { ServiceLogOutput.Text = $"Error reading scheduler state:\n{ex.Message}"; } } private async void TestAPIConnection_Click(object sender, RoutedEventArgs e) { ServiceLogOutput.Text = "Testing API connection...\n\n"; try { var apiClient = new Services.ApiClient(_config); // Test connectivity bool isConnected = await apiClient.CheckConnectivity(); if (isConnected) { ServiceLogOutput.Text += $"[{DateTime.Now}] ✅ API is reachable!\n"; ServiceLogOutput.Text += $"Server URL: {_config.ServerUrl}\n"; } else { ServiceLogOutput.Text += $"[{DateTime.Now}] ❌ API is NOT reachable!\n"; ServiceLogOutput.Text += $"Server URL: {_config.ServerUrl}\n"; } } catch (Exception ex) { ServiceLogOutput.Text += $"[{DateTime.Now}] ERROR: {ex.Message}\n"; } } private void InstallService_Click(object sender, RoutedEventArgs e) { ServiceLogOutput.Text = "Installing PSG-Oversight service...\n\n"; try { // Get the path to OversightService.exe var exePath = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "OversightService", "bin", "Debug", "net8.0", "OversightService.exe" ); exePath = Path.GetFullPath(exePath); if (!File.Exists(exePath)) { ServiceLogOutput.Text += $"ERROR: OversightService.exe not found at:\n{exePath}\n\n"; ServiceLogOutput.Text += "Please build the OversightService project first."; return; } // Create the service using sc.exe var startInfo = new System.Diagnostics.ProcessStartInfo { FileName = "sc.exe", Arguments = $"create PSG-Oversight binPath=\"{exePath}\" start=auto DisplayName=\"PSG Oversight Service\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, Verb = "runas" // Request admin elevation }; var process = System.Diagnostics.Process.Start(startInfo); process.WaitForExit(); var output = process.StandardOutput.ReadToEnd(); var error = process.StandardError.ReadToEnd(); ServiceLogOutput.Text += output; if (!string.IsNullOrEmpty(error)) { ServiceLogOutput.Text += $"ERROR: {error}\n"; } if (process.ExitCode == 0) { ServiceLogOutput.Text += "\n✅ Service installed successfully!\n"; RefreshServiceStatus_Click(sender, e); } else { ServiceLogOutput.Text += $"\n❌ Service installation failed with exit code: {process.ExitCode}\n"; } } catch (Exception ex) { ServiceLogOutput.Text += $"ERROR: {ex.Message}\n"; ServiceLogOutput.Text += "\nNote: This operation requires administrator privileges.\n"; } } private void UninstallService_Click(object sender, RoutedEventArgs e) { ServiceLogOutput.Text = "Uninstalling PSG-Oversight service...\n\n"; try { // Stop the service first if it's running try { var service = System.ServiceProcess.ServiceController.GetServices() .FirstOrDefault(s => s.ServiceName == "PSG-Oversight"); if (service != null && service.Status == System.ServiceProcess.ServiceControllerStatus.Running) { ServiceLogOutput.Text += "Stopping service first...\n"; service.Stop(); service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); ServiceLogOutput.Text += "Service stopped.\n\n"; } } catch { } // Delete the service using sc.exe var startInfo = new System.Diagnostics.ProcessStartInfo { FileName = "sc.exe", Arguments = "delete PSG-Oversight", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, Verb = "runas" // Request admin elevation }; var process = System.Diagnostics.Process.Start(startInfo); process.WaitForExit(); var output = process.StandardOutput.ReadToEnd(); var error = process.StandardError.ReadToEnd(); ServiceLogOutput.Text += output; if (!string.IsNullOrEmpty(error)) { ServiceLogOutput.Text += $"ERROR: {error}\n"; } if (process.ExitCode == 0) { ServiceLogOutput.Text += "\n✅ Service uninstalled successfully!\n"; RefreshServiceStatus_Click(sender, e); } else { ServiceLogOutput.Text += $"\n❌ Service uninstallation failed with exit code: {process.ExitCode}\n"; } } catch (Exception ex) { ServiceLogOutput.Text += $"ERROR: {ex.Message}\n"; ServiceLogOutput.Text += "\nNote: This operation requires administrator privileges.\n"; } } private void StartService_Click(object sender, RoutedEventArgs e) { ServiceLogOutput.Text = "Starting PSG-Oversight service...\n\n"; try { var service = System.ServiceProcess.ServiceController.GetServices() .FirstOrDefault(s => s.ServiceName == "PSG-Oversight"); if (service == null) { ServiceLogOutput.Text += "ERROR: Service is not installed.\n"; return; } if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running) { ServiceLogOutput.Text += "Service is already running.\n"; return; } service.Start(); service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); ServiceLogOutput.Text += "✅ Service started successfully!\n"; RefreshServiceStatus_Click(sender, e); } catch (Exception ex) { ServiceLogOutput.Text += $"ERROR: {ex.Message}\n"; ServiceLogOutput.Text += "\nNote: This operation may require administrator privileges.\n"; } } private void StopService_Click(object sender, RoutedEventArgs e) { ServiceLogOutput.Text = "Stopping PSG-Oversight service...\n\n"; try { var service = System.ServiceProcess.ServiceController.GetServices() .FirstOrDefault(s => s.ServiceName == "PSG-Oversight"); if (service == null) { ServiceLogOutput.Text += "ERROR: Service is not installed.\n"; return; } if (service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped) { ServiceLogOutput.Text += "Service is already stopped.\n"; return; } service.Stop(); service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); ServiceLogOutput.Text += "✅ Service stopped successfully!\n"; RefreshServiceStatus_Click(sender, e); } catch (Exception ex) { ServiceLogOutput.Text += $"ERROR: {ex.Message}\n"; ServiceLogOutput.Text += "\nNote: This operation may require administrator privileges.\n"; } } } }