5 Commits

Author SHA1 Message Date
dce789db94 Resolved device list and IP addresses not working via osqueryi.
Now also returning last boottime by doing bootup time and math from current time
2025-11-04 08:55:00 +08:00
cedf28199e Fix: Use AppData directory for log files to avoid permission errors
Issue: Application crashed when installed in Program Files due to
UnauthorizedAccessException when trying to write log files to the
installation directory.

Changes:
- SystemInfo.cs: Updated error logging to use LocalApplicationData/PSG-Oversight
- OsqueryService.cs: Added GetLogPath() helper method and updated all log writes
  to use user's AppData directory instead of current directory
- Added try-catch wrappers to silently handle any remaining logging failures

All log files now write to: %LOCALAPPDATA%\PSG-Oversight\

This fixes the startup crash reported in Event Viewer when running
the installed application.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 08:17:43 +08:00
037bd195db Release v1.2.0: Integrate osquery for enhanced system information
Major Features:
- Integrated osquery for comprehensive system information gathering
- Added OsqueryService for executing SQL queries against system tables
- Implemented Osquery Console tab for interactive SQL queries

System Info Improvements:
- Enhanced system info collection using osquery tables
- Added support for multiple GPU detection
- Improved memory detection with proper GB formatting
- Fixed OS Architecture detection (x64/x86)
- Better network interface detection (IPv4 only)
- Human-readable timestamp formatting

UI/UX Enhancements:
- Added window resizing with corner drag support
- Implemented dynamic window sizing (SizeToContent)
- Added ScrollViewer for content overflow
- Improved IP address formatting with bullet points
- Added field labels to all system info displays
- Set minimum/maximum window size constraints

Bug Fixes:
- Fixed XAML StackPanel Spacing property issue
- Merged duplicate MainWindow constructors
- Fixed non-nullable field warnings
- Fixed EventHandler nullability signatures
- Removed redundant hostname/OS name fields
- Fixed GPU registry query to detect all GPUs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 07:46:21 +08:00
fada3ace65 Add osquery submodule and link libosquery 2025-10-15 12:12:23 +08:00
ef67d76e54 Dropped the userInstalledApplications entirely. 2025-10-13 11:05:23 +08:00
12 changed files with 670 additions and 550 deletions

View File

@@ -1,7 +1,7 @@
{
"permissions": {
"allow": [
"Bash(dotnet build:*)"
"Bash(\"BuildDir\\bin\\Debug\\Assets\\osqueryi.exe\" --json \"SELECT datetime((SELECT CAST(unix_time AS INTEGER) FROM time) - total_seconds, ''unixepoch'') as boot_time FROM uptime;\")"
],
"deny": [],
"ask": []

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "deps/osquery"]
path = deps/osquery
url = https://github.com/osquery/osquery.git

Binary file not shown.

39
LD-SysInfo/Config.cs Normal file
View File

@@ -0,0 +1,39 @@
#nullable disable
using System;
using System.IO;
using Newtonsoft.Json;
namespace LD_SysInfo
{
public class AppConfig
{
[JsonProperty("ServerUrl")] public string ServerUrl { get; set; } = "https://yourserver.com/api/status";
[JsonProperty("EnableLogging")] public bool EnableLogging { get; set; } = true;
[JsonProperty("KeepAlivePeriod")] public int KeepAlivePeriod { get; set; } = 30;
[JsonProperty("SystemInfoInterval")] public int SystemInfoInterval { get; set; } = 60;
[JsonProperty("ClientIdentifier")] public string ClientIdentifier { get; set; } = "your-default-client-id";
[JsonProperty("Auth")] public AuthConfig Auth { get; set; } = new();
}
public class AuthConfig
{
[JsonProperty("Username")] public string Username { get; set; } = "testuser";
[JsonProperty("Password")] public string Password { get; set; } = "testpassword";
}
public static class ConfigManager
{
private static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.json");
public static AppConfig LoadConfig(string? pathOverride = null)
{
string path = pathOverride ?? ConfigPath;
if (!File.Exists(path))
throw new FileNotFoundException($"Config file not found at {path}");
string json = File.ReadAllText(path);
return JsonConvert.DeserializeObject<AppConfig>(json) ?? new AppConfig();
}
}
}

View File

@@ -9,13 +9,14 @@
<UseWPF>true</UseWPF>
<StartupObject>LD_SysInfo.App</StartupObject>
<ApplicationIcon>Assets\LDShortcut.ico</ApplicationIcon>
<OutputPath>C:\Users\Sonder\source\repos\psg-oversight-app\BuildDir\bin\Debug\net8.0-windows\</OutputPath>
<OutputPath>C:\Users\Sonder\source\repos\psg-oversight-app\BuildDir\bin\Debug\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<IncludeSatelliteAssembliesForPublish>false</IncludeSatelliteAssembliesForPublish>
<!-- 🔢 Version Info -->
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.3.0</FileVersion>
<Version>1.0.3</Version>
<AssemblyVersion>1.2.0.0</AssemblyVersion>
<FileVersion>1.2.0.0</FileVersion>
<Version>1.2.0</Version>
<!-- ✅ Enables source metadata and Git info -->
<Deterministic>true</Deterministic>
@@ -48,9 +49,13 @@
<ItemGroup>
<ApplicationDefinition Remove="App.xaml" />
<None Remove="Assets\osqueryi.exe" />
<None Remove="Assets\trayicon.ico" />
<None Remove="config.json" />
<Content Include="Assets\LDShortcut.ico" />
<Content Include="Assets\osqueryi.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Page Include="App.xaml" />
</ItemGroup>

View File

@@ -11,11 +11,15 @@
mc:Ignorable="d"
Title="PSG - Oversight"
Height="500"
Width="400"
SizeToContent="Height"
Width="420"
MinHeight="400"
MinWidth="350"
MaxHeight="800"
Icon="Assets/trayicon.ico"
WindowStyle="None"
AllowsTransparency="True"
ResizeMode="CanResizeWithGrip"
Background="Transparent"
Foreground="{DynamicResource TextDarkBrush}">
@@ -225,7 +229,8 @@
<TabItem Header="SysInfo" FontSize="16"/>
<TabItem Header="InstalledApps" FontSize="16"/>
<TabItem Header="Status" FontSize="16"/>
</TabControl>
<TabItem Header="Osquery" FontSize="16"/>
</TabControl>
<!--
<Button Grid.Column="1"
@@ -263,61 +268,65 @@
</TabControl.Resources>
<!-- 🔹 SysInfo Tab -->
<TabItem Header="SysInfo">
<StackPanel Margin="5,0,5,0" VerticalAlignment="Top">
<TextBlock x:Name="HostnameTextBlock" Text="Hostname" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSNameTextBlock" Text="OS Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSVersionTextBlock" Text="OS Version" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="WindowsVersionTextBlock" Text="Windows Version" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="WindowsBuildTextBlock" Text="Windows Build" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSArchitectureTextBlock" Text="OS Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="ProcessorNameTextBlock" Text="Processor Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="ProcessorArchitectureTextBlock" Text="Processor Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TabItem Header="SysInfo">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="5,0,5,0" VerticalAlignment="Top">
<TextBlock x:Name="HostnameTextBlock" Text="Hostname" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSNameTextBlock" Text="OS Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSVersionTextBlock" Text="OS Version" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="WindowsVersionTextBlock" Text="Windows Version" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="WindowsBuildTextBlock" Text="Windows Build" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="OSArchitectureTextBlock" Text="OS Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="ProcessorNameTextBlock" Text="Processor Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="ProcessorArchitectureTextBlock" Text="Processor Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<Button Content="Export to CSV"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="ExportToCsvButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
<!-- New fields -->
<TextBlock x:Name="MemoryTextBlock" Text="Total Memory" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<TextBlock x:Name="GpuTextBlock" Text="GPU(s)" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" TextWrapping="Wrap" />
<TextBlock x:Name="IpTextBlock" Text="IP Addresses" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" TextWrapping="Wrap" />
<TextBlock x:Name="CollectedAtTextBlock" Text="Last updated: N/A" FontSize="12" Margin="8,10,5,5" Foreground="{DynamicResource TextDarkBrush}" FontStyle="Italic" />
<Button Content="Save System Info"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="StoreSystemInfoButton_Click"
Width="200"
Margin="5"
HorizontalAlignment="Left" />
<StackPanel Orientation="Horizontal" Margin="5,8,5,5" HorizontalAlignment="Left">
<Button Content="Refresh now"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="RefreshButton_Click"
Width="120"
Margin="0,0,8,0" />
<Button Content="Export to CSV"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="ExportToCsvButton_Click"
Width="120"
Margin="0,0,8,0" />
<Button Content="Save System Info"
Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}"
Click="StoreSystemInfoButton_Click"
Width="120" />
</StackPanel>
<CheckBox
x:Name="IncludeInstalledAppsCheckBox"
Content="Include installed Apps?"
FontSize="14"
Margin="5"
Foreground="{DynamicResource TextDarkBrush}"
Style="{StaticResource MaterialDesignCheckBox}" />
<CheckBox x:Name="IncludeInstalledAppsCheckBox"
Content="Include installed Apps?"
FontSize="14"
Margin="5"
Foreground="{DynamicResource TextDarkBrush}"
Style="{StaticResource MaterialDesignCheckBox}" />
<TextBlock x:Name="StatusTextBlock"
FontSize="14"
FontWeight="SemiBold"
Margin="10,5"
Foreground="{DynamicResource ErrorBrush}" />
</StackPanel>
</ScrollViewer>
</TabItem>
<TextBlock x:Name="StatusTextBlock"
FontSize="14"
FontWeight="SemiBold"
Margin="10,5"
Foreground="{DynamicResource ErrorBrush}" />
</StackPanel>
</TabItem>
<!-- 🔹 InstalledApps Tab -->
<!-- 🔹 InstalledApps Tab -->
<TabItem Header="InstalledApps">
<StackPanel Margin="10">
<ListBox x:Name="InstalledAppsListBox" Height="300" Width="350" Margin="5">
@@ -401,7 +410,53 @@
</StackPanel>
</TabItem>
</TabControl>
<!-- 🔹 Osquery Console Tab -->
<TabItem Header="Osquery">
<Grid Background="{DynamicResource BackgroundDarkBrush}" Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Enter Osquery SQL:" FontWeight="Bold" Margin="0,0,0,4" Foreground="{DynamicResource TextLightBrush}"/>
<TextBox x:Name="OsqueryQueryBox"
Grid.Row="1"
Height="60"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Background="#252526"
Foreground="White"
FontFamily="Consolas"
FontSize="14"
AcceptsReturn="True"
Text="SELECT * FROM system_info;" />
<Button Grid.Row="1"
Content="Run Query"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Width="120"
Margin="0,4,0,0"
Click="RunOsqueryQuery_Click"/>
<TextBox x:Name="OsqueryOutputBox"
Grid.Row="2"
Margin="0,10,0,0"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Background="#1e1e1e"
Foreground="#dcdcdc"
FontFamily="Consolas"
FontSize="13"
AcceptsReturn="True"
IsReadOnly="True"
Text="Results will appear here..." />
</Grid>
</TabItem>
</TabControl>
</Border>
<!-- ✅ Tray Icon (not inside Grid) -->

View File

@@ -38,21 +38,23 @@ namespace LD_SysInfo
{
private static readonly HttpClient httpClient = new HttpClient();
private static AppConfig _config;
private static string jwtToken = null; // 🔹 Store the JWT token globally in the app
private readonly DispatcherTimer messageClearTimer;
private readonly DispatcherTimer postTimer;
private readonly DispatcherTimer keepAliveTimer;
private readonly DispatcherTimer tokenRefreshTimer;
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");
public MainWindow()
{
// ⚠️ Temporary SSL Certificate Bypass - For Testing Only
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
@@ -109,9 +111,35 @@ namespace LD_SysInfo
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()
{
@@ -120,7 +148,7 @@ namespace LD_SysInfo
//System.Windows.MessageBox.Show($"DEBUG: AppContext.BaseDirectory = {AppContext.BaseDirectory}");
//System.Windows.MessageBox.Show($"DEBUG: ConfigPath = {ConfigPath}");
if (!File.Exists((string?)ConfigPath))
if (!File.Exists(ConfigPath))
{
System.Windows.MessageBox.Show("❌ config.json not found at resolved path!");
}
@@ -226,7 +254,7 @@ namespace LD_SysInfo
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_HHmmss}.txt");
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");
@@ -244,21 +272,15 @@ namespace LD_SysInfo
private void DisplaySystemInfo()
{
var sysInfo = SystemInfo.GetSystemInfo();
OSNameTextBlock.Text = $"OS Name: {sysInfo.OSName}";
OSVersionTextBlock.Text = $"OS Version: {sysInfo.OSVersion}";
WindowsVersionTextBlock.Text = $"Windows Version: {sysInfo.WindowsVersion}";
WindowsBuildTextBlock.Text = $"Windows Build: {sysInfo.WindowsBuild}";
OSArchitectureTextBlock.Text = $"OS Architecture: {sysInfo.OSArchitecture}";
ProcessorNameTextBlock.Text = $"Processor Name: {sysInfo.ProcessorName}";
ProcessorArchitectureTextBlock.Text = $"Processor Architecture: {sysInfo.ProcessorArchitecture}";
HostnameTextBlock.Text = $"Hostname: {sysInfo.Hostname}";
_cachedSystemInfo = SystemInfo.GetSystemInfo();
UpdateSysInfoUI(_cachedSystemInfo);
}
private void LoadInstalledAppsButton_Click(object sender, RoutedEventArgs e)
{
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
InstalledAppsListBox.ItemsSource = applications;
var sysInfo = SystemInfo.GetSystemInfo();
InstalledAppsListBox.ItemsSource = sysInfo.InstalledApplications;
}
private async void StoreSystemInfoButton_Click(object sender, RoutedEventArgs e)
@@ -272,7 +294,7 @@ namespace LD_SysInfo
try
{
var sysInfo = SystemInfo.GetSystemInfo();
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
var applications = sysInfo.InstalledApplications;
var formattedApplications = new List<object>();
foreach (var app in applications)
@@ -337,7 +359,6 @@ namespace LD_SysInfo
lastBootTime = sysInfo.LastBootTime,
drives = sysInfo.Drives,
installedApplications = formattedApplications,
userInstalledApplications = formattedUserApplications,
windowsUpdates = formattedWindowsUpdates,
appXPackages = formattedAppXPackages
};
@@ -470,7 +491,7 @@ namespace LD_SysInfo
writer.WriteLine("Installed Applications:");
writer.WriteLine("Name,Version,Publisher");
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
var applications = sysInfo.InstalledApplications;
foreach (var app in applications)
{
writer.WriteLine($"{app.Name},{app.Version},{app.Publisher}");
@@ -566,7 +587,16 @@ namespace LD_SysInfo
messageClearTimer.Stop();
}
private async void PostTimer_Tick(object sender, EventArgs e)
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
{
@@ -576,8 +606,9 @@ namespace LD_SysInfo
var apiClient = new ApiClient(_config); // Create an instance of ApiClient
var sysInfo = SystemInfo.GetSystemInfo();
var applications = SystemInfo.GetInstalledApplicationsFromRegistry();
_cachedSystemInfo = SystemInfo.GetSystemInfo();
var sysInfo = _cachedSystemInfo;
var applications = sysInfo.InstalledApplications;
var formattedApplications = new List<object>();
foreach (var app in applications)
@@ -671,7 +702,7 @@ namespace LD_SysInfo
}
}
private async void KeepAliveTimer_Tick(object sender, EventArgs e)
private async void KeepAliveTimer_Tick(object? sender, EventArgs e)
{
var apiClient = new ApiClient(_config);
bool isConnected = await apiClient.CheckConnectivity();
@@ -694,12 +725,12 @@ namespace LD_SysInfo
}
private void MessageClearTimer_Tick(object sender, EventArgs e)
private void MessageClearTimer_Tick(object? sender, EventArgs e)
{
FadeOutStatusMessage(); // Trigger the fade-out effect
}
private async void TokenRefreshTimer_Tick(object sender, EventArgs e)
private async void TokenRefreshTimer_Tick(object? sender, EventArgs e)
{
if (string.IsNullOrEmpty(ApiClient.GetJwtToken()))
{
@@ -752,5 +783,101 @@ namespace LD_SysInfo
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<string>();
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;
}
}
}
}

View File

@@ -0,0 +1,50 @@
<Window x:Class="LD_SysInfo.OsqueryConsole"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Osquery Console" Height="600" Width="800"
Background="#1e1e1e" Foreground="White"
WindowStartupLocation="CenterOwner">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Enter Osquery SQL:" FontWeight="Bold" Margin="0,0,0,4"/>
<TextBox x:Name="QueryBox"
Grid.Row="1"
Height="60"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Background="#252526"
Foreground="White"
FontFamily="Consolas"
FontSize="14"
AcceptsReturn="True"
Text="SELECT * FROM system_info;" />
<Button Grid.Row="1"
Content="Run Query"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Width="120"
Margin="0,4,0,0"
Click="RunQuery_Click"/>
<TextBox x:Name="OutputBox"
Grid.Row="2"
Margin="0,10,0,0"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Background="#1e1e1e"
Foreground="#dcdcdc"
FontFamily="Consolas"
FontSize="13"
AcceptsReturn="True"
IsReadOnly="True"
Text="Results will appear here..." />
</Grid>
</Window>

View File

@@ -0,0 +1,37 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using LD_SysInfo.Services;
namespace LD_SysInfo
{
public partial class OsqueryConsole : Window
{
public OsqueryConsole()
{
InitializeComponent();
}
private async void RunQuery_Click(object sender, RoutedEventArgs e)
{
OutputBox.Text = "Running query...";
string sql = QueryBox.Text.Trim();
try
{
var result = await Task.Run(() => OsqueryService.Query(sql));
string prettyJson = JsonSerializer.Serialize(result,
new JsonSerializerOptions { WriteIndented = true });
OutputBox.Text = prettyJson;
}
catch (Exception ex)
{
OutputBox.Text = $"❌ Error running query:\n{ex}";
}
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Newtonsoft.Json.Linq;
namespace LD_SysInfo.Services
{
public static class OsqueryService
{
private static string GetLogPath(string filename)
{
string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PSG-Oversight");
Directory.CreateDirectory(logDir);
return Path.Combine(logDir, filename);
}
private static string GetOsqueryPath()
{
var baseDir = AppContext.BaseDirectory;
var path = Path.Combine(baseDir, "Assets", "osqueryi.exe");
if (!File.Exists(path))
{
try
{
File.AppendAllText(GetLogPath("osquery_error.log"), $"[{DateTime.Now}] ❌ osqueryi.exe not found at {path}\n");
}
catch { /* Silently fail if we can't write logs */ }
throw new FileNotFoundException("osqueryi.exe not found", path);
}
return path;
}
private static string RunQuery(string sql)
{
var processInfo = new ProcessStartInfo
{
FileName = GetOsqueryPath(),
Arguments = $"--json \"{sql}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(processInfo); // ✅ FIXED — actually start the process
if (process == null)
throw new Exception("Failed to start osquery process");
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
// Optional debug logging
try
{
File.AppendAllText(GetLogPath("osquery_debug.log"),
$"[{DateTime.Now}] Ran query: {sql}\nOutput length: {output.Length}\n");
}
catch { /* Silently fail if we can't write logs */ }
return output;
}
public static List<Dictionary<string, string>> Query(string sql)
{
string json = RunQuery(sql);
try
{
var jArray = JArray.Parse(json);
var results = new List<Dictionary<string, string>>();
foreach (var obj in jArray)
{
var dict = new Dictionary<string, string>();
foreach (var prop in (JObject)obj)
dict[prop.Key] = prop.Value?.ToString() ?? "";
results.Add(dict);
}
return results;
}
catch (Exception ex)
{
try
{
File.AppendAllText(GetLogPath("osquery_error.log"),
$"[{DateTime.Now}] ⚠️ JSON parse failed for query '{sql}': {ex.Message}\n");
}
catch { /* Silently fail if we can't write logs */ }
return new List<Dictionary<string, string>>();
}
}
}
}

View File

@@ -1,532 +1,238 @@
using Microsoft.Win32;
using System.IO;
using System.Management;
using System.Diagnostics;
using System.Net.NetworkInformation;
#nullable disable
using Newtonsoft.Json;
using System.Windows;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using LD_SysInfo.Services;
namespace LD_SysInfo
{
public class AppConfig
// Extension method for safe dictionary access
public static class DictionaryExtensions
{
[JsonProperty("ServerUrl")]
public string ServerUrl { get; set; } = "https://yourserver.com/api/status";
[JsonProperty("EnableLogging")]
public bool EnableLogging { get; set; } = true;
[JsonProperty("KeepAlivePeriod")]
public int KeepAlivePeriod { get; set; } = 30; // Ping/heartbeat every 30 seconds
[JsonProperty("SystemInfoInterval")]
public int SystemInfoInterval { get; set; } = 60; // Full system info POST every 60 seconds
[JsonProperty("ClientIdentifier")]
public string ClientIdentifier { get; set; } = "your-default-client-id";
[JsonProperty("Auth")]
public AuthConfig Auth { get; set; } = new AuthConfig();
}
public class AuthConfig
{
[JsonProperty("Username")]
public string Username { get; set; } = "testuser";
[JsonProperty("Password")]
public string Password { get; set; } = "testpassword";
}
public static class ConfigManager
{
private static readonly string ConfigPath =
Path.Combine(AppContext.BaseDirectory, "config.json");
public static AppConfig LoadConfig(string configPath)
public static string GetValueOrDefault(this Dictionary<string, string> dict, string key)
{
//System.Windows.MessageBox.Show($"[DEBUG] Calling LoadConfig from:\n{ConfigPath}");
if (File.Exists(ConfigPath))
{
string json = File.ReadAllText(ConfigPath);
return JsonConvert.DeserializeObject<AppConfig>(json);
}
throw new FileNotFoundException("❌ Config file not found at:\n" + ConfigPath);
return dict.ContainsKey(key) ? dict[key] : "";
}
}
public class InstalledApplication
{
public string Name { get; set; }
public string Version { get; set; }
public string Publisher { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("version")] public string Version { get; set; }
[JsonProperty("publisher")] public string Publisher { get; set; }
}
public class WindowsUpdate
{
[JsonProperty("hotFixID")]
public string HotFixID { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("installedOn")]
public string InstalledOn { get; set; }
[JsonProperty("installedBy")]
public string InstalledBy { get; set; }
[JsonProperty("hotFixID")] public string HotFixID { get; set; }
[JsonProperty("description")] public string Description { get; set; }
[JsonProperty("installedOn")] public string InstalledOn { get; set; }
[JsonProperty("installedBy")] public string InstalledBy { get; set; } // 🔹 added back for compatibility
}
public class AppXPackage
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("publisher")]
public string Publisher { get; set; }
[JsonProperty("packageFullName")]
public string PackageFullName { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("version")] public string Version { get; set; }
[JsonProperty("publisher")] public string Publisher { get; set; }
[JsonProperty("packageFullName")] public string PackageFullName { get; set; }
}
public class DriveInfoSummary
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("totalSizeGB")]
public double TotalSizeGB { get; set; }
[JsonProperty("freeSpaceGB")]
public double FreeSpaceGB { get; set; }
[JsonProperty("driveType")]
public string DriveType { get; set; }
[JsonProperty("name")] public string Name { get; set; }
[JsonProperty("totalSizeGB")] public double TotalSizeGB { get; set; }
[JsonProperty("freeSpaceGB")] public double FreeSpaceGB { get; set; }
[JsonProperty("driveType")] public string DriveType { get; set; }
}
public class NetworkInterfaceInfo
{
[JsonProperty("interfaceName")]
public string InterfaceName { get; set; }
[JsonProperty("ipAddress")]
public string IpAddress { get; set; }
[JsonProperty("macAddress")]
public string MacAddress { get; set; }
[JsonProperty("interfaceName")] public string InterfaceName { get; set; }
[JsonProperty("ipAddress")] public string IpAddress { get; set; }
[JsonProperty("macAddress")] public string MacAddress { get; set; }
}
public class SystemInfo
{
[JsonProperty("osName")]
public string OSName { get; set; }
[JsonProperty("hostname")] public string Hostname { get; set; }
[JsonProperty("osName")] public string OSName { get; set; }
[JsonProperty("osVersion")] public string OSVersion { get; set; }
[JsonProperty("windowsVersion")] public string WindowsVersion { get; set; }
[JsonProperty("windowsBuild")] public string WindowsBuild { get; set; }
[JsonProperty("osArchitecture")] public string OSArchitecture { get; set; }
[JsonProperty("processorName")] public string ProcessorName { get; set; }
[JsonProperty("processorArchitecture")] public string ProcessorArchitecture { get; set; }
[JsonProperty("gpuNames")] public List<string> GpuNames { get; set; } = new();
[JsonProperty("totalMemory")] public string TotalMemory { get; set; }
[JsonProperty("ipAddresses")] public List<NetworkInterfaceInfo> IpAddresses { get; set; } = new();
[JsonProperty("lastBootTime")] public string LastBootTime { get; set; }
[JsonProperty("collectedAt")] public string CollectedAt { get; set; }
[JsonProperty("osVersion")]
public string OSVersion { get; set; }
[JsonProperty("windowsVersion")]
public string WindowsVersion { get; set; }
[JsonProperty("windowsBuild")]
public string WindowsBuild { get; set; }
[JsonProperty("osArchitecture")]
public string OSArchitecture { get; set; }
[JsonProperty("processorName")]
public string ProcessorName { get; set; }
[JsonProperty("processorArchitecture")]
public string ProcessorArchitecture { get; set; }
[JsonProperty("hostname")]
public string Hostname { get; set; }
[JsonProperty("gpuNames")]
public List<string> GpuNames { get; set; }
[JsonProperty("totalMemory")]
public string TotalMemory { get; set; }
[JsonProperty("ipAddresses")]
public List<NetworkInterfaceInfo> IpAddresses { get; set; } = new();
[JsonProperty("lastBootTime")]
public string LastBootTime { get; set; }
[JsonProperty("collectedAt")]
public string CollectedAt { get; set; }
[JsonProperty("installedApplications")]
public List<InstalledApplication> InstalledApplications { get; set; }
[JsonProperty("userInstalledApplications")]
public List<InstalledApplication> UserInstalledApplications { get; set; } = new();
[JsonProperty("windowsUpdates")]
public List<WindowsUpdate> WindowsUpdates { get; set; } = new();
[JsonProperty("appXPackages")]
public List<AppXPackage> AppXPackages { get; set; } = new();
[JsonProperty("drives")]
public List<DriveInfoSummary> Drives { get; set; } = new();
private const string WindowsCurrentVersionKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
[JsonProperty("installedApplications")] public List<InstalledApplication> InstalledApplications { get; set; } = new();
[JsonProperty("userInstalledApplications")] public List<InstalledApplication> UserInstalledApplications { get; set; } = new();
[JsonProperty("windowsUpdates")] public List<WindowsUpdate> WindowsUpdates { get; set; } = new();
[JsonProperty("appXPackages")] public List<AppXPackage> AppXPackages { get; set; } = new();
[JsonProperty("drives")] public List<DriveInfoSummary> Drives { get; set; } = new();
public static SystemInfo GetSystemInfo()
{
var sysInfo = new SystemInfo();
var info = new SystemInfo { CollectedAt = DateTime.Now.ToString("MMM dd, yyyy h:mm tt") };
try
{
// Capture collection timestamp with timezone info
sysInfo.CollectedAt = DateTimeOffset.Now.ToString("o");
sysInfo.Hostname = Environment.MachineName;
sysInfo.OSName = GetOSFriendlyName();
sysInfo.OSVersion = Environment.OSVersion.Version.ToString();
sysInfo.WindowsVersion = GetRegistryValue(WindowsCurrentVersionKey, "DisplayVersion");
sysInfo.WindowsBuild = GetRegistryValue(WindowsCurrentVersionKey, "CurrentBuild");
sysInfo.OSArchitecture = Environment.Is64BitOperatingSystem ? "x64" : "x86";
sysInfo.ProcessorName = GetProcessorName();
sysInfo.ProcessorArchitecture = Environment.Is64BitProcess ? "x64" : "x86";
sysInfo.GpuNames = GetGpuNames();
sysInfo.TotalMemory = GetTotalMemory();
var allInterfaces = GetNetworkInterfaces();
sysInfo.IpAddresses = allInterfaces
.Where(i => !string.IsNullOrWhiteSpace(i.InterfaceName))
.ToList();
sysInfo.LastBootTime = GetLastBootTime();
PopulateDriveInfo(sysInfo);
// Populate new collections for comprehensive system information
sysInfo.UserInstalledApplications = GetUserInstalledApplications();
sysInfo.WindowsUpdates = GetInstalledPatches();
sysInfo.AppXPackages = GetAppXPackages();
}
catch (Exception ex)
{
string logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LD_SysInfo", "SysInfo_ErrorLog.txt");
Directory.CreateDirectory(Path.GetDirectoryName(logPath));
File.AppendAllText(logPath, $"[{DateTime.Now}] ERROR: {ex.Message}\n");
}
return sysInfo;
}
private static void PopulateDriveInfo(SystemInfo info)
{
foreach (var drive in DriveInfo.GetDrives())
{
if (!drive.IsReady) continue;
info.Drives.Add(new DriveInfoSummary
// ✅ Base system info - using correct osquery column names
var sys = OsqueryService.Query("SELECT * FROM system_info;").FirstOrDefault();
if (sys != null)
{
Name = drive.Name,
TotalSizeGB = Math.Round(drive.TotalSize / (1024.0 * 1024 * 1024), 2),
FreeSpaceGB = Math.Round(drive.AvailableFreeSpace / (1024.0 * 1024 * 1024), 2),
DriveType = drive.DriveType.ToString()
});
}
}
info.Hostname = sys.GetValueOrDefault("hostname");
// OSName removed - was redundant with hostname
private static string GetRegistryValue(string key, string valueName)
{
using RegistryKey rk = Registry.LocalMachine.OpenSubKey(key);
return rk?.GetValue(valueName)?.ToString();
}
private static string GetOSFriendlyName()
{
string productName = GetRegistryValue(WindowsCurrentVersionKey, "ProductName");
string build = GetRegistryValue(WindowsCurrentVersionKey, "CurrentBuild");
if (int.TryParse(build, out int buildNumber) && buildNumber >= 22000)
return "Windows 11 Pro";
return productName ?? "Unknown OS";
}
private static string GetProcessorName()
{
const string key = @"HARDWARE\DESCRIPTION\System\CentralProcessor\0";
return GetRegistryValue(key, "ProcessorNameString");
}
private static List<string> GetGpuNames()
{
var gpuNames = new List<string>();
try
{
using var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_VideoController");
foreach (var obj in searcher.Get())
{
string name = obj["Name"]?.ToString();
if (!string.IsNullOrEmpty(name))
// Windows version info from os_version table
var osVer = OsqueryService.Query("SELECT * FROM os_version;").FirstOrDefault();
if (osVer != null)
{
gpuNames.Add(name);
info.OSVersion = osVer.GetValueOrDefault("version");
info.WindowsVersion = osVer.GetValueOrDefault("name");
info.WindowsBuild = osVer.GetValueOrDefault("build");
// Get proper OS name from os_version
info.OSName = osVer.GetValueOrDefault("name"); // e.g., "Windows 10 Pro"
}
info.ProcessorName = sys.GetValueOrDefault("cpu_brand");
info.ProcessorArchitecture = sys.GetValueOrDefault("cpu_type");
// Fix OS Architecture - should be x64, x86, etc.
info.OSArchitecture = sys.GetValueOrDefault("cpu_subtype");
if (string.IsNullOrEmpty(info.OSArchitecture) || info.OSArchitecture == "-1")
{
// Fallback: determine from cpu_type or use platform info
var platform = sys.GetValueOrDefault("cpu_type");
info.OSArchitecture = platform.Contains("64") ? "x64" : "x86";
}
}
}
catch (Exception ex)
{
gpuNames.Add($"Error: {ex.Message}");
}
return gpuNames;
}
private static string GetTotalMemory()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem");
foreach (var obj in searcher.Get())
// ✅ Memory - query physical_memory table and sum up total
var memResults = OsqueryService.Query("SELECT SUM(size) as total_bytes FROM physical_memory;").FirstOrDefault();
if (memResults != null && memResults.ContainsKey("total_bytes"))
{
long bytes = Convert.ToInt64(obj["TotalPhysicalMemory"]);
return $"{bytes / (1024 * 1024)} MB";
}
}
catch { }
return null;
}
private static List<NetworkInterfaceInfo> GetNetworkInterfaces()
{
var interfaces = new List<NetworkInterfaceInfo>();
try
{
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
{
if (adapter.OperationalStatus != OperationalStatus.Up) continue;
var ip = adapter.GetIPProperties().UnicastAddresses
.FirstOrDefault(a => a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
var mac = adapter.GetPhysicalAddress();
var macAddress = mac?.GetAddressBytes().Length > 0
? string.Join(":", mac.GetAddressBytes().Select(b => b.ToString("X2")))
: null;
if (ip != null || macAddress != null)
if (long.TryParse(memResults["total_bytes"], out long memBytes))
{
interfaces.Add(new NetworkInterfaceInfo
double memGB = Math.Round(memBytes / (1024.0 * 1024.0 * 1024.0), 2);
info.TotalMemory = $"{memGB} GB";
}
}
// Fallback: try WMI-based system_info table
if (string.IsNullOrEmpty(info.TotalMemory))
{
var sysInfo = OsqueryService.Query("SELECT physical_memory FROM system_info;").FirstOrDefault();
if (sysInfo != null && sysInfo.ContainsKey("physical_memory"))
{
if (long.TryParse(sysInfo["physical_memory"], out long memBytes))
{
InterfaceName = adapter.Name,
IpAddress = ip?.Address.ToString(),
MacAddress = macAddress
});
double memGB = Math.Round(memBytes / (1024.0 * 1024.0 * 1024.0), 2);
info.TotalMemory = $"{memGB} GB";
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to gather network interfaces: {ex.Message}");
}
return interfaces;
}
private static string GetLastBootTime()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT LastBootUpTime FROM Win32_OperatingSystem WHERE Primary='true'");
foreach (var obj in searcher.Get())
{
string lastBoot = obj["LastBootUpTime"]?.ToString();
// Convert to local time and ensure timezone info is preserved
DateTime localBootTime = ManagementDateTimeConverter.ToDateTime(lastBoot);
// Use DateTimeOffset to ensure timezone information is included
DateTimeOffset bootTimeWithZone = new DateTimeOffset(localBootTime, TimeZoneInfo.Local.GetUtcOffset(localBootTime));
return bootTimeWithZone.ToString("o");
}
}
catch { }
return null;
}
public static List<InstalledApplication> GetInstalledApplicationsFromRegistry()
{
var applications = new List<InstalledApplication>();
string[] registryKeys = {
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};
foreach (string key in registryKeys)
{
using var regKey = Registry.LocalMachine.OpenSubKey(key);
if (regKey == null) continue;
foreach (string subKeyName in regKey.GetSubKeyNames())
{
using var subKey = regKey.OpenSubKey(subKeyName);
string name = subKey?.GetValue("DisplayName")?.ToString();
if (!string.IsNullOrWhiteSpace(name))
// ✅ Drives
info.Drives = OsqueryService.Query("SELECT device_id, size, free_space, type FROM logical_drives;")
.Select(d => new DriveInfoSummary
{
applications.Add(new InstalledApplication
{
Name = name,
Version = subKey?.GetValue("DisplayVersion")?.ToString(),
Publisher = subKey?.GetValue("Publisher")?.ToString()
});
}
}
}
Name = d.GetValueOrDefault("device_id"),
TotalSizeGB = d.ContainsKey("size") && double.TryParse(d["size"], out double size)
? Math.Round(size / (1024.0 * 1024.0 * 1024.0), 2) : 0,
FreeSpaceGB = d.ContainsKey("free_space") && double.TryParse(d["free_space"], out double free)
? Math.Round(free / (1024.0 * 1024.0 * 1024.0), 2) : 0,
DriveType = d.GetValueOrDefault("type")
}).ToList();
return applications;
}
// ✅ GPU info - query registry for all GPUs
var gpuQuery = OsqueryService.Query(@"
SELECT data
FROM registry
WHERE path LIKE 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\%\DriverDesc';");
/// <summary>
/// Gets user-context installed applications from HKEY_CURRENT_USER registry.
/// </summary>
public static List<InstalledApplication> GetUserInstalledApplications()
{
var applications = new List<InstalledApplication>();
string registryKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall";
info.GpuNames = gpuQuery
.Select(g => g.GetValueOrDefault("data"))
.Where(s => !string.IsNullOrEmpty(s))
.Distinct() // Remove duplicates
.ToList();
try
{
using var regKey = Registry.CurrentUser.OpenSubKey(registryKey);
if (regKey == null) return applications;
// ✅ Network interfaces - IPv4 only, with better formatting
var ipResults = OsqueryService.Query(
"SELECT interface, address FROM interface_addresses WHERE address NOT LIKE '127.%' AND address NOT LIKE '%:%';");
foreach (string subKeyName in regKey.GetSubKeyNames())
{
using var subKey = regKey.OpenSubKey(subKeyName);
string name = subKey?.GetValue("DisplayName")?.ToString();
if (!string.IsNullOrWhiteSpace(name))
info.IpAddresses = ipResults
.Select(i => new NetworkInterfaceInfo
{
applications.Add(new InstalledApplication
{
Name = name,
Version = subKey?.GetValue("DisplayVersion")?.ToString(),
Publisher = subKey?.GetValue("Publisher")?.ToString()
});
}
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to gather user-installed applications: {ex.Message}");
}
InterfaceName = i.GetValueOrDefault("interface"),
IpAddress = i.GetValueOrDefault("address"),
MacAddress = ""
})
.Where(ni => !string.IsNullOrEmpty(ni.IpAddress))
.ToList();
return applications;
}
// ✅ Last boot time - calculate from current time minus uptime
info.LastBootTime = OsqueryService.Query("SELECT datetime((SELECT CAST(unix_time AS INTEGER) FROM time) - total_seconds, 'unixepoch') as boot_time FROM uptime;")
.FirstOrDefault()?.GetValueOrDefault("boot_time");
/// <summary>
/// Gets Windows Updates and patches installed on the system using WMI.
/// </summary>
public static List<WindowsUpdate> GetInstalledPatches()
{
var patches = new List<WindowsUpdate>();
try
{
using var searcher = new ManagementObjectSearcher(
"SELECT HotFixID, Description, InstalledOn, InstalledBy FROM Win32_QuickFixEngineering");
foreach (var obj in searcher.Get())
{
patches.Add(new WindowsUpdate
// ✅ Installed apps (user + system)
info.InstalledApplications = OsqueryService.Query("SELECT name, version, publisher FROM programs;")
.Select(a => new InstalledApplication
{
HotFixID = obj["HotFixID"]?.ToString(),
Description = obj["Description"]?.ToString(),
InstalledOn = obj["InstalledOn"]?.ToString(),
InstalledBy = obj["InstalledBy"]?.ToString()
});
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to gather Windows Updates: {ex.Message}");
}
Name = a["name"],
Version = a["version"],
Publisher = a["publisher"]
}).ToList();
return patches;
}
info.UserInstalledApplications = info.InstalledApplications
.Where(a => !string.IsNullOrEmpty(a.Publisher))
.ToList();
/// <summary>
/// Gets Windows Store (AppX) packages installed on the system using WMI.
/// Note: This may require elevated permissions for complete results.
/// </summary>
public static List<AppXPackage> GetAppXPackages()
{
var packages = new List<AppXPackage>();
try
{
// Try using Win32_InstalledStoreProgram (Windows 10+)
using var searcher = new ManagementObjectSearcher(
"SELECT Name, Version, Publisher FROM Win32_InstalledStoreProgram");
foreach (var obj in searcher.Get())
{
var name = obj["Name"]?.ToString();
if (!string.IsNullOrWhiteSpace(name))
// ✅ AppX packages (Windows Store)
info.AppXPackages = OsqueryService.Query("SELECT name, version, publisher, package_id FROM appx_packages;")
.Select(p => new AppXPackage
{
packages.Add(new AppXPackage
{
Name = name,
Version = obj["Version"]?.ToString(),
Publisher = obj["Publisher"]?.ToString(),
PackageFullName = name // Win32_InstalledStoreProgram doesn't provide PackageFullName
});
}
Name = p["name"],
Version = p["version"],
Publisher = p["publisher"],
PackageFullName = p["package_id"]
}).ToList();
// ✅ Windows Updates (patches)
info.WindowsUpdates = OsqueryService.Query("SELECT hotfix_id, description, installed_on FROM patches;")
.Select(p => new WindowsUpdate
{
HotFixID = p["hotfix_id"],
Description = p["description"],
InstalledOn = p["installed_on"],
InstalledBy = Environment.UserName
}).ToList();
}
catch (Exception ex)
{
try
{
string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PSG-Oversight");
Directory.CreateDirectory(logDir);
string logPath = Path.Combine(logDir, "osquery_error.log");
File.AppendAllText(logPath, $"[{DateTime.Now}] {ex}\n");
}
catch
{
// Silently fail if we can't write logs
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to gather AppX packages: {ex.Message}");
}
return packages;
return info;
}
/// <summary>
/// Checks if a specific Windows Update patch is installed on the system.
/// </summary>
/// <param name="kbNumber">The KB number to check (e.g., "KB5034441")</param>
/// <returns>True if the patch is installed, false otherwise</returns>
public static bool IsPatchInstalled(string kbNumber)
{
if (string.IsNullOrWhiteSpace(kbNumber))
return false;
try
{
// Normalize KB number format
string normalizedKB = kbNumber.ToUpper().Trim();
if (!normalizedKB.StartsWith("KB"))
normalizedKB = "KB" + normalizedKB;
using var searcher = new ManagementObjectSearcher(
$"SELECT HotFixID FROM Win32_QuickFixEngineering WHERE HotFixID = '{normalizedKB}'");
return searcher.Get().Count > 0;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to check patch {kbNumber}: {ex.Message}");
return false;
}
}
}
}

1
deps/osquery vendored Submodule

Submodule deps/osquery added at e8f154ef31