diff --git a/CHANGELOG.md b/CHANGELOG.md
index ddfdfa2..67cf250 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+## [1.17.2] - 2026-05-29
+
+### Fixed
+- **Memory leaks** — AboutViewModel now properly disposes ManagementObject instances in all 5 WMI foreach loops (CPU, RAM, GPU, Display, OS detection).
+- **Silent failures** — ThemeService Save/Load empty catch blocks now log errors via Serilog instead of swallowing silently.
+- **Dashboard error handling** — replaced 4 bare `catch (Exception)` in alert scanners with logged exceptions for diagnostics.
+- **UI flicker** — BulkInstallerViewModel.FilteredApps converted from ObservableCollection to BulkObservableCollection with ReplaceWith().
+- **Visual consistency** — ShortcutCleanerView DataGrid now has `Background="Transparent"` and `BorderThickness="0"` matching all other views.
+
## [1.17.1] - 2026-05-29
### Fixed
diff --git a/SysManager/SysManager/Services/ThemeService.cs b/SysManager/SysManager/Services/ThemeService.cs
index 7f5104a..6326367 100644
--- a/SysManager/SysManager/Services/ThemeService.cs
+++ b/SysManager/SysManager/Services/ThemeService.cs
@@ -6,6 +6,7 @@
using System.Text.Json;
using System.Windows;
using System.Windows.Media;
+using Serilog;
namespace SysManager.Services;
@@ -216,7 +217,7 @@ private void Save()
var json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettingsPath, json);
}
- catch { }
+ catch (Exception ex) { Log.Debug("Theme save failed: {Error}", ex.Message); }
}
private void Load()
@@ -247,7 +248,7 @@ private void Load()
ApplyShade();
}
}
- catch { }
+ catch (Exception ex) { Log.Debug("Theme load failed: {Error}", ex.Message); }
}
private sealed record ThemeSettings(
diff --git a/SysManager/SysManager/SysManager.csproj b/SysManager/SysManager/SysManager.csproj
index 9ec8279..350dc2e 100644
--- a/SysManager/SysManager/SysManager.csproj
+++ b/SysManager/SysManager/SysManager.csproj
@@ -10,9 +10,9 @@
SysManager
true
NU1603;NU1701
- 1.17.1
- 1.17.1.0
- 1.17.1.0
+ 1.17.2
+ 1.17.2.0
+ 1.17.2.0
SysManager
SysManager — Windows system monitoring toolkit by laurentiu021. Network, updates, health, logs, safe deep cleanup.
https://github.com/laurentiu021/SystemManager
diff --git a/SysManager/SysManager/ViewModels/AboutViewModel.cs b/SysManager/SysManager/ViewModels/AboutViewModel.cs
index 65a3f8f..5f2c7e3 100644
--- a/SysManager/SysManager/ViewModels/AboutViewModel.cs
+++ b/SysManager/SysManager/ViewModels/AboutViewModel.cs
@@ -278,17 +278,18 @@ private string CollectEnvironmentInfo()
using var cpuSearch = new System.Management.ManagementObjectSearcher(
"SELECT Name,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed FROM Win32_Processor");
foreach (System.Management.ManagementObject mo in cpuSearch.Get())
- {
- var name = mo["Name"]?.ToString()?.Trim() ?? "unknown";
- var cores = mo["NumberOfCores"];
- var threads = mo["NumberOfLogicalProcessors"];
- var mhz = mo["MaxClockSpeed"];
- sb.Append("CPU: ").Append(name);
- if (cores is not null) sb.Append($" ({cores}c/{threads}t)");
- if (mhz is uint speed) sb.Append($" @ {speed / 1000.0:F1} GHz");
- sb.AppendLine();
- break;
- }
+ using (mo)
+ {
+ var name = mo["Name"]?.ToString()?.Trim() ?? "unknown";
+ var cores = mo["NumberOfCores"];
+ var threads = mo["NumberOfLogicalProcessors"];
+ var mhz = mo["MaxClockSpeed"];
+ sb.Append("CPU: ").Append(name);
+ if (cores is not null) sb.Append($" ({cores}c/{threads}t)");
+ if (mhz is uint speed) sb.Append($" @ {speed / 1000.0:F1} GHz");
+ sb.AppendLine();
+ break;
+ }
}
catch (System.Management.ManagementException ex) { Log.Debug("CPU info unavailable: {Error}", ex.Message); }
@@ -298,13 +299,14 @@ private string CollectEnvironmentInfo()
using var memSearch = new System.Management.ManagementObjectSearcher(
"SELECT TotalVisibleMemorySize,FreePhysicalMemory FROM Win32_OperatingSystem");
foreach (System.Management.ManagementObject mo in memSearch.Get())
- {
- var totalKb = mo["TotalVisibleMemorySize"] as ulong? ?? 0;
- var freeKb = mo["FreePhysicalMemory"] as ulong? ?? 0;
- if (totalKb > 0)
- sb.AppendLine($"RAM: {totalKb / 1024.0 / 1024.0:F1} GB total, {freeKb / 1024.0 / 1024.0:F1} GB free");
- break;
- }
+ using (mo)
+ {
+ var totalKb = mo["TotalVisibleMemorySize"] as ulong? ?? 0;
+ var freeKb = mo["FreePhysicalMemory"] as ulong? ?? 0;
+ if (totalKb > 0)
+ sb.AppendLine($"RAM: {totalKb / 1024.0 / 1024.0:F1} GB total, {freeKb / 1024.0 / 1024.0:F1} GB free");
+ break;
+ }
}
catch (System.Management.ManagementException ex) { Log.Debug("RAM info unavailable: {Error}", ex.Message); }
@@ -314,15 +316,16 @@ private string CollectEnvironmentInfo()
using var gpuSearch = new System.Management.ManagementObjectSearcher(
"SELECT Name,DriverVersion,AdapterRAM FROM Win32_VideoController");
foreach (System.Management.ManagementObject mo in gpuSearch.Get())
- {
- var name = mo["Name"]?.ToString()?.Trim() ?? "unknown";
- var driver = mo["DriverVersion"]?.ToString() ?? "";
- var vram = mo["AdapterRAM"] as uint? ?? 0;
- sb.Append("GPU: ").Append(name);
- if (vram > 0) sb.Append($" ({vram / 1024.0 / 1024.0 / 1024.0:F1} GB VRAM)");
- if (!string.IsNullOrEmpty(driver)) sb.Append($" driver {driver}");
- sb.AppendLine();
- }
+ using (mo)
+ {
+ var name = mo["Name"]?.ToString()?.Trim() ?? "unknown";
+ var driver = mo["DriverVersion"]?.ToString() ?? "";
+ var vram = mo["AdapterRAM"] as uint? ?? 0;
+ sb.Append("GPU: ").Append(name);
+ if (vram > 0) sb.Append($" ({vram / 1024.0 / 1024.0 / 1024.0:F1} GB VRAM)");
+ if (!string.IsNullOrEmpty(driver)) sb.Append($" driver {driver}");
+ sb.AppendLine();
+ }
}
catch (System.Management.ManagementException ex) { Log.Debug("GPU info unavailable: {Error}", ex.Message); }
@@ -341,18 +344,19 @@ private string CollectEnvironmentInfo()
using var dispSearch = new System.Management.ManagementObjectSearcher(
"SELECT CurrentHorizontalResolution,CurrentVerticalResolution,CurrentRefreshRate FROM Win32_VideoController");
foreach (System.Management.ManagementObject mo in dispSearch.Get())
- {
- var w = mo["CurrentHorizontalResolution"];
- var h = mo["CurrentVerticalResolution"];
- var hz = mo["CurrentRefreshRate"];
- if (w is not null && h is not null)
+ using (mo)
{
- sb.Append($"Display: {w}×{h}");
- if (hz is not null) sb.Append($" @ {hz} Hz");
- sb.AppendLine();
- break;
+ var w = mo["CurrentHorizontalResolution"];
+ var h = mo["CurrentVerticalResolution"];
+ var hz = mo["CurrentRefreshRate"];
+ if (w is not null && h is not null)
+ {
+ sb.Append($"Display: {w}×{h}");
+ if (hz is not null) sb.Append($" @ {hz} Hz");
+ sb.AppendLine();
+ break;
+ }
}
- }
}
catch (System.Management.ManagementException ex) { Log.Debug("Display info unavailable: {Error}", ex.Message); }
@@ -377,12 +381,13 @@ private static string DescribeWindows()
using var searcher = new System.Management.ManagementObjectSearcher(
"SELECT Caption,BuildNumber FROM Win32_OperatingSystem");
foreach (System.Management.ManagementObject mo in searcher.Get())
- {
- var caption = mo["Caption"]?.ToString()?.Trim() ?? "";
- var build = mo["BuildNumber"]?.ToString() ?? "";
- if (!string.IsNullOrEmpty(caption))
- return $"{caption} (build {build})";
- }
+ using (mo)
+ {
+ var caption = mo["Caption"]?.ToString()?.Trim() ?? "";
+ var build = mo["BuildNumber"]?.ToString() ?? "";
+ if (!string.IsNullOrEmpty(caption))
+ return $"{caption} (build {build})";
+ }
}
catch (System.Management.ManagementException ex) { Log.Debug("WMI OS info unavailable: {Error}", ex.Message); }
diff --git a/SysManager/SysManager/ViewModels/BulkInstallerViewModel.cs b/SysManager/SysManager/ViewModels/BulkInstallerViewModel.cs
index e7b1d14..617a894 100644
--- a/SysManager/SysManager/ViewModels/BulkInstallerViewModel.cs
+++ b/SysManager/SysManager/ViewModels/BulkInstallerViewModel.cs
@@ -25,7 +25,7 @@ public sealed partial class BulkInstallerViewModel : ViewModelBase
private CancellationTokenSource? _cts;
public BulkObservableCollection Apps { get; } = new();
- public ObservableCollection FilteredApps { get; } = new();
+ public BulkObservableCollection FilteredApps { get; } = new();
public ICollectionView GroupedView { get; }
[ObservableProperty] private string _filterText = "";
@@ -344,9 +344,7 @@ private void ApplyFilter()
filtered = filtered.Where(a =>
a.Name.Contains(FilterText, StringComparison.OrdinalIgnoreCase));
- FilteredApps.Clear();
- foreach (var app in filtered)
- FilteredApps.Add(app);
+ FilteredApps.ReplaceWith(filtered);
}
private static string GlyphForCategory(string category) => category switch
diff --git a/SysManager/SysManager/ViewModels/DashboardViewModel.cs b/SysManager/SysManager/ViewModels/DashboardViewModel.cs
index 3c115ee..3c2687c 100644
--- a/SysManager/SysManager/ViewModels/DashboardViewModel.cs
+++ b/SysManager/SysManager/ViewModels/DashboardViewModel.cs
@@ -173,9 +173,9 @@ private void UpdateGpuUsage()
});
}
}
- catch (Exception)
+ catch (Exception ex)
{
- // No NVIDIA or driver issue — GPU stays at default values
+ Log.Debug("GPU polling unavailable: {Error}", ex.Message);
}
}
@@ -370,7 +370,7 @@ await runner.RunScriptViaPwshAsync(
}
});
}
- catch (Exception)
+ catch (Exception ex) { Log.Debug("Alert scan failed: {Error}", ex.Message); }//
{
System.Windows.Application.Current?.Dispatcher.BeginInvoke(() =>
{
@@ -425,7 +425,7 @@ private Task ScanEventLogAsync(DashboardAlert alert)
}
});
}
- catch (Exception)
+ catch (Exception ex) { Log.Debug("Alert scan failed: {Error}", ex.Message); }//
{
System.Windows.Application.Current?.Dispatcher.BeginInvoke(() =>
{
@@ -458,7 +458,7 @@ private Task ScanWindowsFeaturesAsync(DashboardAlert alert)
}
});
}
- catch (Exception)
+ catch (Exception ex) { Log.Debug("Alert scan failed: {Error}", ex.Message); }//
{
System.Windows.Application.Current?.Dispatcher.BeginInvoke(() =>
{
diff --git a/SysManager/SysManager/Views/ShortcutCleanerView.xaml b/SysManager/SysManager/Views/ShortcutCleanerView.xaml
index 3a40a22..9ba9ff1 100644
--- a/SysManager/SysManager/Views/ShortcutCleanerView.xaml
+++ b/SysManager/SysManager/Views/ShortcutCleanerView.xaml
@@ -56,9 +56,9 @@