6 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
62cf2425b1 Updated to include new dir, inclusion of tags. Version appears in Control Panel now. Also appears in TItlebar of AppWindow. 2025-10-10 14:32:49 +08:00
15 changed files with 840 additions and 633 deletions

View File

@@ -1,7 +1,7 @@
{ {
"permissions": { "permissions": {
"allow": [ "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": [], "deny": [],
"ask": [] "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

View File

@@ -1,32 +1,46 @@
<!-- <!--
This file allow for customizing your build process. This file allows for customizing your build process.
See: https://learn.microsoft.com/visualstudio/msbuild/customize-your-build See: https://learn.microsoft.com/visualstudio/msbuild/customize-your-build
--> -->
<Project> <Project>
<!--
Uncomment if you need to enable inclusion of another Directory.Build.props file from a parent directory
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
-->
<PropertyGroup> <PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>12</LangVersion> <LangVersion>12</LangVersion>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio> <AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
<!--
If you you like to see source generated files saved to disk you can enable the following:
https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview?WT.mc_id=DT-MVP-5003472
-->
<!--<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>--> <!--<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>-->
</PropertyGroup> </PropertyGroup>
<!--
This allows all projects to share the same user secrets file.
If you want project to have their own, set it to a different GUID on each project.
See: https://learn.microsoft.com/dotnet/architecture/microservices/secure-net-microservices-web-applications/developer-app-secrets-storage
-->
<PropertyGroup Label="User Secrets"> <PropertyGroup Label="User Secrets">
<UserSecretsId>1ef91ee6-7b55-474e-ab2b-2d164b723a56</UserSecretsId> <UserSecretsId>1ef91ee6-7b55-474e-ab2b-2d164b723a56</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<!-- 🔹 Automatically derive version from latest Git tag -->
<Target Name="GetVersionFromGit" BeforeTargets="GetAssemblyVersion">
<!-- Get the most recent tag (like v1.1.1) -->
<Exec Command="git describe --tags --abbrev=0" ConsoleToMSBuild="true" IgnoreExitCode="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GitTag" />
</Exec>
<!-- Strip leading 'v' if present -->
<PropertyGroup>
<GitVersion>$([System.Text.RegularExpressions.Regex]::Replace('$(GitTag)', '^v', ''))</GitVersion>
</PropertyGroup>
<!-- Fallback if no tags exist yet -->
<PropertyGroup Condition="'$(GitVersion)' == ''">
<GitVersion>0.0.0</GitVersion>
</PropertyGroup>
<!-- Apply to assembly and file versions -->
<Message Text="🔖 Using version: $(GitVersion)" Importance="High" />
<PropertyGroup>
<Version>$(GitVersion)</Version>
<FileVersion>$(GitVersion)</FileVersion>
<AssemblyVersion>$(GitVersion)</AssemblyVersion>
</PropertyGroup>
</Target>
</Project> </Project>

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> <UseWPF>true</UseWPF>
<StartupObject>LD_SysInfo.App</StartupObject> <StartupObject>LD_SysInfo.App</StartupObject>
<ApplicationIcon>Assets\LDShortcut.ico</ApplicationIcon> <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> <IncludeSatelliteAssembliesForPublish>false</IncludeSatelliteAssembliesForPublish>
<!-- 🔢 Version Info --> <!-- 🔢 Version Info -->
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>1.2.0.0</AssemblyVersion>
<FileVersion>1.0.3.0</FileVersion> <FileVersion>1.2.0.0</FileVersion>
<Version>1.0.3</Version> <Version>1.2.0</Version>
<!-- ✅ Enables source metadata and Git info --> <!-- ✅ Enables source metadata and Git info -->
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
@@ -48,9 +49,13 @@
<ItemGroup> <ItemGroup>
<ApplicationDefinition Remove="App.xaml" /> <ApplicationDefinition Remove="App.xaml" />
<None Remove="Assets\osqueryi.exe" />
<None Remove="Assets\trayicon.ico" /> <None Remove="Assets\trayicon.ico" />
<None Remove="config.json" /> <None Remove="config.json" />
<Content Include="Assets\LDShortcut.ico" /> <Content Include="Assets\LDShortcut.ico" />
<Content Include="Assets\osqueryi.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Page Include="App.xaml" /> <Page Include="App.xaml" />
</ItemGroup> </ItemGroup>

View File

@@ -11,11 +11,15 @@
mc:Ignorable="d" mc:Ignorable="d"
Title="PSG - Oversight" Title="PSG - Oversight"
Height="500" SizeToContent="Height"
Width="400" Width="420"
MinHeight="400"
MinWidth="350"
MaxHeight="800"
Icon="Assets/trayicon.ico" Icon="Assets/trayicon.ico"
WindowStyle="None" WindowStyle="None"
AllowsTransparency="True" AllowsTransparency="True"
ResizeMode="CanResizeWithGrip"
Background="Transparent" Background="Transparent"
Foreground="{DynamicResource TextDarkBrush}"> Foreground="{DynamicResource TextDarkBrush}">
@@ -225,6 +229,7 @@
<TabItem Header="SysInfo" FontSize="16"/> <TabItem Header="SysInfo" FontSize="16"/>
<TabItem Header="InstalledApps" FontSize="16"/> <TabItem Header="InstalledApps" FontSize="16"/>
<TabItem Header="Status" FontSize="16"/> <TabItem Header="Status" FontSize="16"/>
<TabItem Header="Osquery" FontSize="16"/>
</TabControl> </TabControl>
<!-- <!--
@@ -264,6 +269,7 @@
<!-- 🔹 SysInfo Tab --> <!-- 🔹 SysInfo Tab -->
<TabItem Header="SysInfo"> <TabItem Header="SysInfo">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="5,0,5,0" VerticalAlignment="Top"> <StackPanel Margin="5,0,5,0" VerticalAlignment="Top">
<TextBlock x:Name="HostnameTextBlock" Text="Hostname" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" /> <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="OSNameTextBlock" Text="OS Name" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
@@ -275,46 +281,49 @@
<TextBlock x:Name="ProcessorArchitectureTextBlock" Text="Processor Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" /> <TextBlock x:Name="ProcessorArchitectureTextBlock" Text="Processor Architecture" FontSize="12" Margin="5" Foreground="{DynamicResource TextBrush}" />
<!-- 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" />
<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" <Button Content="Export to CSV"
Style="{StaticResource CustomRaisedButton}" Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}" Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}" Foreground="{DynamicResource SelectedLightBrush}"
Click="ExportToCsvButton_Click" Click="ExportToCsvButton_Click"
Width="200" Width="120"
Margin="5" Margin="0,0,8,0" />
HorizontalAlignment="Left" />
<Button Content="Save System Info" <Button Content="Save System Info"
Style="{StaticResource CustomRaisedButton}" Style="{StaticResource CustomRaisedButton}"
Background="{DynamicResource PrimaryBrush}" Background="{DynamicResource PrimaryBrush}"
Foreground="{DynamicResource SelectedLightBrush}" Foreground="{DynamicResource SelectedLightBrush}"
Click="StoreSystemInfoButton_Click" Click="StoreSystemInfoButton_Click"
Width="200" Width="120" />
Margin="5" </StackPanel>
HorizontalAlignment="Left" />
<CheckBox <CheckBox x:Name="IncludeInstalledAppsCheckBox"
x:Name="IncludeInstalledAppsCheckBox"
Content="Include installed Apps?" Content="Include installed Apps?"
FontSize="14" FontSize="14"
Margin="5" Margin="5"
Foreground="{DynamicResource TextDarkBrush}" Foreground="{DynamicResource TextDarkBrush}"
Style="{StaticResource MaterialDesignCheckBox}" /> Style="{StaticResource MaterialDesignCheckBox}" />
<TextBlock x:Name="StatusTextBlock" <TextBlock x:Name="StatusTextBlock"
FontSize="14" FontSize="14"
FontWeight="SemiBold" FontWeight="SemiBold"
Margin="10,5" Margin="10,5"
Foreground="{DynamicResource ErrorBrush}" /> Foreground="{DynamicResource ErrorBrush}" />
</StackPanel> </StackPanel>
</ScrollViewer>
</TabItem> </TabItem>
<!-- 🔹 InstalledApps Tab --> <!-- 🔹 InstalledApps Tab -->
@@ -401,6 +410,52 @@
</StackPanel> </StackPanel>
</TabItem> </TabItem>
<!-- 🔹 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> </TabControl>
</Border> </Border>

View File

@@ -38,27 +38,45 @@ namespace LD_SysInfo
{ {
private static readonly HttpClient httpClient = new HttpClient(); private static readonly HttpClient httpClient = new HttpClient();
private static AppConfig _config; private static AppConfig _config = null!; // Initialized in LoadConfig
private static string jwtToken = null; // 🔹 Store the JWT token globally in the app private static string? jwtToken = null; // 🔹 Store the JWT token globally in the app
private readonly DispatcherTimer messageClearTimer; private readonly DispatcherTimer messageClearTimer = null!; // Initialized in constructor
private readonly DispatcherTimer postTimer; private readonly DispatcherTimer postTimer = null!; // Initialized in constructor
private readonly DispatcherTimer keepAliveTimer; private readonly DispatcherTimer keepAliveTimer = null!; // Initialized in constructor
private readonly DispatcherTimer tokenRefreshTimer; private readonly DispatcherTimer tokenRefreshTimer = null!; // Initialized in constructor
private readonly Uri LightThemeUri = new Uri("pack://application:,,,/Themes/LightTheme.xaml", UriKind.Absolute); 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 readonly Uri DarkThemeUri = new Uri("pack://application:,,,/Themes/DarkTheme.xaml", UriKind.Absolute);
private bool isDarkTheme = false; private bool isDarkTheme = false;
private SystemInfo _cachedSystemInfo = null!; // Initialized in DisplaySystemInfo
private static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.json"); private static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.json");
public MainWindow() public MainWindow()
{ {
// ⚠️ Temporary SSL Certificate Bypass - For Testing Only // ⚠️ Temporary SSL Certificate Bypass - For Testing Only
System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; System.Net.ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
InitializeComponent(); InitializeComponent();
var fvi = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); var exePath = Assembly.GetEntryAssembly()?.Location;
VersionTextBlock.Text = $"v{fvi.ProductVersion}";
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(); LoadConfig();
DisplaySystemInfo(); DisplaySystemInfo();
AutoLogin(); AutoLogin();
@@ -93,9 +111,35 @@ namespace LD_SysInfo
tokenRefreshTimer.Tick += TokenRefreshTimer_Tick; tokenRefreshTimer.Tick += TokenRefreshTimer_Tick;
tokenRefreshTimer.Start(); 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() private void LoadConfig()
{ {
@@ -104,7 +148,7 @@ namespace LD_SysInfo
//System.Windows.MessageBox.Show($"DEBUG: AppContext.BaseDirectory = {AppContext.BaseDirectory}"); //System.Windows.MessageBox.Show($"DEBUG: AppContext.BaseDirectory = {AppContext.BaseDirectory}");
//System.Windows.MessageBox.Show($"DEBUG: ConfigPath = {ConfigPath}"); //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!"); System.Windows.MessageBox.Show("❌ config.json not found at resolved path!");
} }
@@ -210,7 +254,7 @@ namespace LD_SysInfo
string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LD_SysInfo", "Logs"); string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LD_SysInfo", "Logs");
Directory.CreateDirectory(logDir); // Ensure directory exists 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.WriteAllText(logFile, "=== LD SysInfo Diagnostic Log ===\n");
File.AppendAllText(logFile, $"Timestamp: {DateTime.Now}\n"); File.AppendAllText(logFile, $"Timestamp: {DateTime.Now}\n");
@@ -228,21 +272,15 @@ namespace LD_SysInfo
private void DisplaySystemInfo() private void DisplaySystemInfo()
{ {
var sysInfo = SystemInfo.GetSystemInfo(); _cachedSystemInfo = SystemInfo.GetSystemInfo();
OSNameTextBlock.Text = $"OS Name: {sysInfo.OSName}"; UpdateSysInfoUI(_cachedSystemInfo);
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}";
} }
private void LoadInstalledAppsButton_Click(object sender, RoutedEventArgs e) private void LoadInstalledAppsButton_Click(object sender, RoutedEventArgs e)
{ {
var applications = SystemInfo.GetInstalledApplicationsFromRegistry(); var sysInfo = SystemInfo.GetSystemInfo();
InstalledAppsListBox.ItemsSource = applications; InstalledAppsListBox.ItemsSource = sysInfo.InstalledApplications;
} }
private async void StoreSystemInfoButton_Click(object sender, RoutedEventArgs e) private async void StoreSystemInfoButton_Click(object sender, RoutedEventArgs e)
@@ -256,7 +294,7 @@ namespace LD_SysInfo
try try
{ {
var sysInfo = SystemInfo.GetSystemInfo(); var sysInfo = SystemInfo.GetSystemInfo();
var applications = SystemInfo.GetInstalledApplicationsFromRegistry(); var applications = sysInfo.InstalledApplications;
var formattedApplications = new List<object>(); var formattedApplications = new List<object>();
foreach (var app in applications) foreach (var app in applications)
@@ -321,7 +359,6 @@ namespace LD_SysInfo
lastBootTime = sysInfo.LastBootTime, lastBootTime = sysInfo.LastBootTime,
drives = sysInfo.Drives, drives = sysInfo.Drives,
installedApplications = formattedApplications, installedApplications = formattedApplications,
userInstalledApplications = formattedUserApplications,
windowsUpdates = formattedWindowsUpdates, windowsUpdates = formattedWindowsUpdates,
appXPackages = formattedAppXPackages appXPackages = formattedAppXPackages
}; };
@@ -454,7 +491,7 @@ namespace LD_SysInfo
writer.WriteLine("Installed Applications:"); writer.WriteLine("Installed Applications:");
writer.WriteLine("Name,Version,Publisher"); writer.WriteLine("Name,Version,Publisher");
var applications = SystemInfo.GetInstalledApplicationsFromRegistry(); var applications = sysInfo.InstalledApplications;
foreach (var app in applications) foreach (var app in applications)
{ {
writer.WriteLine($"{app.Name},{app.Version},{app.Publisher}"); writer.WriteLine($"{app.Name},{app.Version},{app.Publisher}");
@@ -550,7 +587,16 @@ namespace LD_SysInfo
messageClearTimer.Stop(); 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 if (string.IsNullOrEmpty(ApiClient.GetJwtToken())) // Ensure authentication is active
{ {
@@ -560,8 +606,9 @@ namespace LD_SysInfo
var apiClient = new ApiClient(_config); // Create an instance of ApiClient var apiClient = new ApiClient(_config); // Create an instance of ApiClient
var sysInfo = SystemInfo.GetSystemInfo(); _cachedSystemInfo = SystemInfo.GetSystemInfo();
var applications = SystemInfo.GetInstalledApplicationsFromRegistry(); var sysInfo = _cachedSystemInfo;
var applications = sysInfo.InstalledApplications;
var formattedApplications = new List<object>(); var formattedApplications = new List<object>();
foreach (var app in applications) foreach (var app in applications)
@@ -655,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); var apiClient = new ApiClient(_config);
bool isConnected = await apiClient.CheckConnectivity(); bool isConnected = await apiClient.CheckConnectivity();
@@ -678,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 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())) if (string.IsNullOrEmpty(ApiClient.GetJwtToken()))
{ {
@@ -736,5 +783,101 @@ namespace LD_SysInfo
base.OnStateChanged(e); 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; #nullable disable
using System.IO;
using System.Management;
using System.Diagnostics;
using System.Net.NetworkInformation;
using Newtonsoft.Json; 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 namespace LD_SysInfo
{ {
public class AppConfig // Extension method for safe dictionary access
public static class DictionaryExtensions
{ {
[JsonProperty("ServerUrl")] public static string GetValueOrDefault(this Dictionary<string, string> dict, string key)
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")] return dict.ContainsKey(key) ? dict[key] : "";
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)
{
//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);
} }
} }
public class InstalledApplication public class InstalledApplication
{ {
public string Name { get; set; } [JsonProperty("name")] public string Name { get; set; }
public string Version { get; set; } [JsonProperty("version")] public string Version { get; set; }
public string Publisher { get; set; } [JsonProperty("publisher")] public string Publisher { get; set; }
} }
public class WindowsUpdate public class WindowsUpdate
{ {
[JsonProperty("hotFixID")] [JsonProperty("hotFixID")] public string HotFixID { get; set; }
public string HotFixID { get; set; } [JsonProperty("description")] public string Description { get; set; }
[JsonProperty("installedOn")] public string InstalledOn { get; set; }
[JsonProperty("description")] [JsonProperty("installedBy")] public string InstalledBy { get; set; } // 🔹 added back for compatibility
public string Description { get; set; }
[JsonProperty("installedOn")]
public string InstalledOn { get; set; }
[JsonProperty("installedBy")]
public string InstalledBy { get; set; }
} }
public class AppXPackage public class AppXPackage
{ {
[JsonProperty("name")] [JsonProperty("name")] public string Name { get; set; }
public string Name { get; set; } [JsonProperty("version")] public string Version { get; set; }
[JsonProperty("publisher")] public string Publisher { get; set; }
[JsonProperty("version")] [JsonProperty("packageFullName")] public string PackageFullName { get; set; }
public string Version { get; set; }
[JsonProperty("publisher")]
public string Publisher { get; set; }
[JsonProperty("packageFullName")]
public string PackageFullName { get; set; }
} }
public class DriveInfoSummary public class DriveInfoSummary
{ {
[JsonProperty("name")] [JsonProperty("name")] public string Name { get; set; }
public string Name { get; set; } [JsonProperty("totalSizeGB")] public double TotalSizeGB { get; set; }
[JsonProperty("freeSpaceGB")] public double FreeSpaceGB { get; set; }
[JsonProperty("totalSizeGB")] [JsonProperty("driveType")] public string DriveType { get; set; }
public double TotalSizeGB { get; set; }
[JsonProperty("freeSpaceGB")]
public double FreeSpaceGB { get; set; }
[JsonProperty("driveType")]
public string DriveType { get; set; }
} }
public class NetworkInterfaceInfo public class NetworkInterfaceInfo
{ {
[JsonProperty("interfaceName")] [JsonProperty("interfaceName")] public string InterfaceName { get; set; }
public string InterfaceName { get; set; } [JsonProperty("ipAddress")] public string IpAddress { get; set; }
[JsonProperty("macAddress")] public string MacAddress { get; set; }
[JsonProperty("ipAddress")]
public string IpAddress { get; set; }
[JsonProperty("macAddress")]
public string MacAddress { get; set; }
} }
public class SystemInfo public class SystemInfo
{ {
[JsonProperty("osName")] [JsonProperty("hostname")] public string Hostname { get; set; }
public string OSName { 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")] [JsonProperty("installedApplications")] public List<InstalledApplication> InstalledApplications { get; set; } = new();
public string OSVersion { get; set; } [JsonProperty("userInstalledApplications")] public List<InstalledApplication> UserInstalledApplications { get; set; } = new();
[JsonProperty("windowsUpdates")] public List<WindowsUpdate> WindowsUpdates { get; set; } = new();
[JsonProperty("windowsVersion")] [JsonProperty("appXPackages")] public List<AppXPackage> AppXPackages { get; set; } = new();
public string WindowsVersion { get; set; } [JsonProperty("drives")] public List<DriveInfoSummary> Drives { get; set; } = new();
[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";
public static SystemInfo GetSystemInfo() public static SystemInfo GetSystemInfo()
{ {
var sysInfo = new SystemInfo(); var info = new SystemInfo { CollectedAt = DateTime.Now.ToString("MMM dd, yyyy h:mm tt") };
try try
{ {
// Capture collection timestamp with timezone info // ✅ Base system info - using correct osquery column names
sysInfo.CollectedAt = DateTimeOffset.Now.ToString("o"); var sys = OsqueryService.Query("SELECT * FROM system_info;").FirstOrDefault();
if (sys != null)
{
info.Hostname = sys.GetValueOrDefault("hostname");
// OSName removed - was redundant with hostname
sysInfo.Hostname = Environment.MachineName; // Windows version info from os_version table
sysInfo.OSName = GetOSFriendlyName(); var osVer = OsqueryService.Query("SELECT * FROM os_version;").FirstOrDefault();
sysInfo.OSVersion = Environment.OSVersion.Version.ToString(); if (osVer != null)
sysInfo.WindowsVersion = GetRegistryValue(WindowsCurrentVersionKey, "DisplayVersion"); {
sysInfo.WindowsBuild = GetRegistryValue(WindowsCurrentVersionKey, "CurrentBuild"); info.OSVersion = osVer.GetValueOrDefault("version");
sysInfo.OSArchitecture = Environment.Is64BitOperatingSystem ? "x64" : "x86"; info.WindowsVersion = osVer.GetValueOrDefault("name");
sysInfo.ProcessorName = GetProcessorName(); info.WindowsBuild = osVer.GetValueOrDefault("build");
sysInfo.ProcessorArchitecture = Environment.Is64BitProcess ? "x64" : "x86";
sysInfo.GpuNames = GetGpuNames(); // Get proper OS name from os_version
sysInfo.TotalMemory = GetTotalMemory(); info.OSName = osVer.GetValueOrDefault("name"); // e.g., "Windows 10 Pro"
var allInterfaces = GetNetworkInterfaces(); }
sysInfo.IpAddresses = allInterfaces
.Where(i => !string.IsNullOrWhiteSpace(i.InterfaceName)) 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";
}
}
// ✅ 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"))
{
if (long.TryParse(memResults["total_bytes"], out long memBytes))
{
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))
{
double memGB = Math.Round(memBytes / (1024.0 * 1024.0 * 1024.0), 2);
info.TotalMemory = $"{memGB} GB";
}
}
}
// ✅ Drives
info.Drives = OsqueryService.Query("SELECT device_id, size, free_space, type FROM logical_drives;")
.Select(d => new DriveInfoSummary
{
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();
// ✅ 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';");
info.GpuNames = gpuQuery
.Select(g => g.GetValueOrDefault("data"))
.Where(s => !string.IsNullOrEmpty(s))
.Distinct() // Remove duplicates
.ToList(); .ToList();
sysInfo.LastBootTime = GetLastBootTime();
PopulateDriveInfo(sysInfo); // ✅ 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 '%:%';");
// Populate new collections for comprehensive system information info.IpAddresses = ipResults
sysInfo.UserInstalledApplications = GetUserInstalledApplications(); .Select(i => new NetworkInterfaceInfo
sysInfo.WindowsUpdates = GetInstalledPatches(); {
sysInfo.AppXPackages = GetAppXPackages(); InterfaceName = i.GetValueOrDefault("interface"),
IpAddress = i.GetValueOrDefault("address"),
MacAddress = ""
})
.Where(ni => !string.IsNullOrEmpty(ni.IpAddress))
.ToList();
// ✅ 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");
// ✅ Installed apps (user + system)
info.InstalledApplications = OsqueryService.Query("SELECT name, version, publisher FROM programs;")
.Select(a => new InstalledApplication
{
Name = a["name"],
Version = a["version"],
Publisher = a["publisher"]
}).ToList();
info.UserInstalledApplications = info.InstalledApplications
.Where(a => !string.IsNullOrEmpty(a.Publisher))
.ToList();
// ✅ AppX packages (Windows Store)
info.AppXPackages = OsqueryService.Query("SELECT name, version, publisher, package_id FROM appx_packages;")
.Select(p => new AppXPackage
{
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) 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
{
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()
});
}
}
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 try
{ {
using var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_VideoController"); string logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PSG-Oversight");
Directory.CreateDirectory(logDir);
foreach (var obj in searcher.Get()) string logPath = Path.Combine(logDir, "osquery_error.log");
{ File.AppendAllText(logPath, $"[{DateTime.Now}] {ex}\n");
string name = obj["Name"]?.ToString();
if (!string.IsNullOrEmpty(name))
{
gpuNames.Add(name);
} }
} catch
}
catch (Exception ex)
{ {
gpuNames.Add($"Error: {ex.Message}"); // Silently fail if we can't write logs
}
return gpuNames;
}
private static string GetTotalMemory()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem");
foreach (var obj in searcher.Get())
{
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)
{
interfaces.Add(new NetworkInterfaceInfo
{
InterfaceName = adapter.Name,
IpAddress = ip?.Address.ToString(),
MacAddress = macAddress
});
}
}
}
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))
{
applications.Add(new InstalledApplication
{
Name = name,
Version = subKey?.GetValue("DisplayVersion")?.ToString(),
Publisher = subKey?.GetValue("Publisher")?.ToString()
});
}
} }
} }
return applications; return info;
} }
/// <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";
try
{
using var regKey = Registry.CurrentUser.OpenSubKey(registryKey);
if (regKey == null) return applications;
foreach (string subKeyName in regKey.GetSubKeyNames())
{
using var subKey = regKey.OpenSubKey(subKeyName);
string name = subKey?.GetValue("DisplayName")?.ToString();
if (!string.IsNullOrWhiteSpace(name))
{
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}");
}
return applications;
}
/// <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
{
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}");
}
return patches;
}
/// <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))
{
packages.Add(new AppXPackage
{
Name = name,
Version = obj["Version"]?.ToString(),
Publisher = obj["Publisher"]?.ToString(),
PackageFullName = name // Win32_InstalledStoreProgram doesn't provide PackageFullName
});
}
}
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to gather AppX packages: {ex.Message}");
}
return packages;
}
/// <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;
}
}
} }
} }

View File

@@ -1,34 +1,56 @@
<Project Sdk="WixToolset.Sdk/6.0.0"> <Project Sdk="WixToolset.Sdk/6.0.0">
<ItemGroup>
<PackageReference Include="WixToolset.UI.wixext" /> <!-- ✅ Defaults -->
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<!-- Force embedding files into the MSI (no external .cab file) --> <Version>0.0.0</Version>
<Platform>x64</Platform>
<InstallerPlatform>x64</InstallerPlatform>
<EmbedCab>true</EmbedCab> <EmbedCab>true</EmbedCab>
</PropertyGroup>
<PropertyGroup>
<WixExtensionInclude>WixToolset.UI.wixext</WixExtensionInclude> <WixExtensionInclude>WixToolset.UI.wixext</WixExtensionInclude>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>PublishDir=C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir</DefineConstants> <!-- ✅ STEP 1: Run Git BEFORE WiX compilation -->
<Target Name="SetVersionFromGit" BeforeTargets="BeforeBuild">
<Exec Command="git describe --tags --abbrev=0" ConsoleToMSBuild="true" IgnoreExitCode="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GitTag" />
</Exec>
<PropertyGroup Condition="'$(GitTag)' != ''">
<GitTagTrimmed>$([System.Text.RegularExpressions.Regex]::Replace('$(GitTag)', '\s', ''))</GitTagTrimmed>
<GitVersion>$([System.Text.RegularExpressions.Regex]::Replace('$(GitTagTrimmed)', '^v', ''))</GitVersion>
<Version>$(GitVersion)</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<DefineConstants>
Version=$(Version);
PublishDir=C:\Users\Sonder\source\repos\psg-oversight-app\PublishDir
</DefineConstants>
<OutputName>OversightInstaller_$(Version)_$(Platform)</OutputName>
</PropertyGroup>
<Message Text="📦 Building OversightInstaller version $(Version)" Importance="high" />
<Message Text="📦 WiX DefineConstants=$(DefineConstants)" Importance="high" />
</Target>
<!-- ✅ STEP 2: Rename the MSI after build -->
<Target Name="RenameMsi" AfterTargets="Build">
<PropertyGroup>
<BuiltMsi>$(OutputPath)OversightInstaller_$(Version)_$(Platform).msi</BuiltMsi>
<ActualMsi>$(OutputPath)OversightInstaller.msi</ActualMsi>
</PropertyGroup>
<Message Text="📦 Renaming $(ActualMsi) → $(BuiltMsi)" Importance="high" />
<Exec Command="move /Y &quot;$(ActualMsi)&quot; &quot;$(BuiltMsi)&quot;" />
</Target>
<ItemGroup> <ItemGroup>
<Content Include="Assets\DLShortcut.ico"> <PackageReference Include="WixToolset.UI.wixext" />
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assets\trayicon.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<WixSource Include="HarvestedFiles.wxs" /> <WixSource Include="HarvestedFiles.wxs" />
</ItemGroup> <Content Include="Assets\DLShortcut.ico" CopyToOutputDirectory="Always" />
<ItemGroup> <Content Include="Assets\trayicon.ico" CopyToOutputDirectory="Always" />
<Content Include="bpl.rtf"> <Content Include="bpl.rtf" CopyToOutputDirectory="Always" />
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="Assets\windowsdesktop-runtime-8.0.13-win-x64.exe" /> <None Include="Assets\windowsdesktop-runtime-8.0.13-win-x64.exe" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,18 +1,34 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"> xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
<Package Name="Oversight" Manufacturer="Paragon Systems Group" Version="1.0.0.0" UpgradeCode="e0e26d46-ed26-425e-a1bb-9bc76523ae6b"> <!-- ✅ Safe default in case $(var.Version) is undefined -->
<?ifndef Version?>
<?define Version="0.0.0"?>
<?endif?>
<Package
Name="Oversight"
Manufacturer="Paragon Systems Group"
Version="$(var.Version)"
UpgradeCode="e0e26d46-ed26-425e-a1bb-9bc76523ae6b">
<Property Id="DisplayVersion" Value="$(var.Version)" />
<!-- 🔁 Automatic major-upgrade behavior -->
<MajorUpgrade DowngradeErrorMessage="A newer version of this application is already installed." /> <MajorUpgrade DowngradeErrorMessage="A newer version of this application is already installed." />
<WixVariable <!-- ✅ Only define custom ARP metadata that isnt built-in -->
Id="WixUILicenseRtf"
Value="bpl.rtf"
/>
<!-- 🔍 Properties -->
<Property Id="ARPPRODUCTICON" Value="DLShortcutIcon" /> <Property Id="ARPPRODUCTICON" Value="DLShortcutIcon" />
<Property Id="ARPHELPLINK" Value="https://psg.net.au/" />
<Property Id="ARPCONTACT" Value="support@psg.net.au" />
<Property Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" />
<!-- Optional custom properties -->
<Property Id="SERVER_URL" Value="http://your-default-server/api/register" /> <Property Id="SERVER_URL" Value="http://your-default-server/api/register" />
<Property Id="APP_EXECUTABLE" Value="PSG-Oversight.exe" /> <Property Id="APP_EXECUTABLE" Value="PSG-Oversight.exe" />
<!-- 💬 EULA -->
<WixVariable Id="WixUILicenseRtf" Value="bpl.rtf" />
<!-- 🔧 UI --> <!-- 🔧 UI -->
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" /> <ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
@@ -22,15 +38,14 @@
<!-- 📂 Installation Directory --> <!-- 📂 Installation Directory -->
<StandardDirectory Id="ProgramFiles64Folder"> <StandardDirectory Id="ProgramFiles64Folder">
<Directory Id="PSGROOT" Name="PSG">
<Directory Id="INSTALLFOLDER" Name="Oversight"> <Directory Id="INSTALLFOLDER" Name="Oversight">
<!-- Your files and harvested components go here -->
</Directory>
<!-- 📁 Assets Folder -->
</Directory> </Directory>
</StandardDirectory> </StandardDirectory>
<!-- 📦 Application Files -->
<ComponentGroupRef Id="AppFiles" /> <ComponentGroupRef Id="AppFiles" />
<!-- 📁 Start Menu Shortcut --> <!-- 📁 Start Menu Shortcut -->
@@ -46,17 +61,37 @@
IconIndex="0" /> IconIndex="0" />
<RemoveFile Id="RemoveStartMenuShortcut" Name="Oversight.lnk" On="uninstall" /> <RemoveFile Id="RemoveStartMenuShortcut" Name="Oversight.lnk" On="uninstall" />
<RemoveFolder Id="RemoveStartMenuFolder" On="uninstall" /> <RemoveFolder Id="RemoveStartMenuFolder" On="uninstall" />
<RegistryValue Root="HKCU" Key="Software\Oversight" Name="StartMenuShortcut" Type="integer" Value="1" KeyPath="yes"/> <RegistryValue Root="HKCU" Key="Software\Oversight"
Name="StartMenuShortcut" Type="integer" Value="1" KeyPath="yes"/>
</Component> </Component>
</Directory> </Directory>
</StandardDirectory> </StandardDirectory>
<!-- 📦 Feature References --> <!-- 64-bit component so ARP keys go to HKLM\Software (not Wow6432Node) -->
<Component Id="AppMetadata" Guid="E62354E8-3C7B-4B13-AFE2-A1E65A7F2E77" Bitness="always64">
<RegistryKey
Root="HKLM"
Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductCode]">
<!-- Nice, branded ARP entry -->
<RegistryValue Name="DisplayName" Value="Oversight" Type="string" />
<RegistryValue Name="Publisher" Value="Paragon Systems Group" Type="string" />
<!-- Make version show in Control Panel; compile-time value from your wixproj -->
<RegistryValue Name="DisplayVersion" Value="$(var.Version)" Type="string" KeyPath="yes" />
</RegistryKey>
</Component>
<!-- 🧱 Primary Feature -->
<Feature Id="Main" Title="DL SysInfo" Level="1"> <Feature Id="Main" Title="DL SysInfo" Level="1">
<ComponentGroupRef Id="AppFiles" /> <ComponentGroupRef Id="AppFiles" />
<ComponentRef Id="StartMenuShortcutComponent" /> <ComponentRef Id="StartMenuShortcutComponent" />
<ComponentRef Id="AppMetadata" />
</Feature> </Feature>
<!-- 💿 Media --> <!-- 💿 Media -->
<Media Id="1" Cabinet="embedded.cab" EmbedCab="yes" /> <Media Id="1" Cabinet="embedded.cab" EmbedCab="yes" />

1
deps/osquery vendored Submodule

Submodule deps/osquery added at e8f154ef31