Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.20.40] - 2026-06-17

### Fixed
- **The Health Score no longer fails outright when a system query hits a transient WMI error.** A repository or RPC fault while reading system info, disk health, or battery could throw an error the score didn't handle, failing the whole calculation; each source now degrades gracefully and the score is still produced from the rest.
- **The memory-error check no longer counts a *passing* memory test as an error.** It counted every Windows Memory Diagnostic result, including the "no errors found" result (event 1101), as a problem; it now counts only the actual error result (event 1201).
- **Known-folder lookup (Downloads, Documents, etc.) now falls back correctly when the system call fails.** The call's failure code was being ignored, so a failed lookup could return an empty path instead of using the standard fallback location; the result is now checked and the fallback applies.

## [1.20.39] - 2026-06-17

### Fixed
Expand Down
46 changes: 26 additions & 20 deletions SysManager/SysManager/Helpers/KnownFolders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal static partial class KnownFolders
private static readonly Guid Videos = new("18989B1D-99B5-455B-841C-AB7C74E4DDFC");

[LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)]
private static partial void SHGetKnownFolderPath(
private static partial int SHGetKnownFolderPath(
in Guid rfid,
uint dwFlags,
nint hToken,
Expand All @@ -49,27 +49,33 @@ private static partial void SHGetKnownFolderPath(

private static string GetPath(Guid folderId)
{
// The import returns the HRESULT. Because this is a plain (non-COM-interface)
// LibraryImport, a failure does NOT throw — it returns a non-zero HRESULT and a
// null/empty path. Check the HRESULT (and guard the path) and fall back to the
// SpecialFolder equivalent on any failure.
try
{
SHGetKnownFolderPath(folderId, 0, nint.Zero, out var path);
return path;
}
catch (COMException)
{
// Fallback to Environment.SpecialFolder if P/Invoke fails
return folderId == Downloads
? Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads")
: folderId == Documents
? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
: folderId == Desktop
? Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
: folderId == Pictures
? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
: folderId == Music
? Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)
: folderId == Videos
? Environment.GetFolderPath(Environment.SpecialFolder.MyVideos)
: string.Empty;
int hr = SHGetKnownFolderPath(folderId, 0, nint.Zero, out var path);
if (hr >= 0 && !string.IsNullOrEmpty(path))
return path;
}
catch (COMException) { /* fall through to the SpecialFolder fallback below */ }

return Fallback(folderId);
}

private static string Fallback(Guid folderId) =>
folderId == Downloads
? Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads")
: folderId == Documents
? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
: folderId == Desktop
? Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
: folderId == Pictures
? Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
: folderId == Music
? Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)
: folderId == Videos
? Environment.GetFolderPath(Environment.SpecialFolder.MyVideos)
: string.Empty;
}
6 changes: 6 additions & 0 deletions SysManager/SysManager/Services/HealthScoreService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,22 @@ public async Task<HealthScoreResult> ComputeAsync(CancellationToken ct = default
IReadOnlyList<DiskHealthReport>? disks = null;
BatteryInfo? battery = null;

// WMI enumeration (Get()) can throw COMException on repository/RPC failures, not
// just ManagementException — without this arm a transient WMI fault crashes the
// whole health score instead of degrading to a partial result.
try { snapshot = await sysTask.ConfigureAwait(false); }
catch (System.Management.ManagementException ex) { Log.Warning("HealthScore: system info failed: {Error}", ex.Message); }
catch (System.Runtime.InteropServices.COMException ex) { Log.Warning("HealthScore: system info WMI COM error: 0x{HResult:X8}", ex.HResult); }
catch (InvalidOperationException ex) { Log.Warning("HealthScore: system info failed: {Error}", ex.Message); }

try { disks = await diskTask.ConfigureAwait(false); }
catch (System.Management.ManagementException ex) { Log.Warning("HealthScore: disk health failed: {Error}", ex.Message); }
catch (System.Runtime.InteropServices.COMException ex) { Log.Warning("HealthScore: disk health WMI COM error: 0x{HResult:X8}", ex.HResult); }
catch (InvalidOperationException ex) { Log.Warning("HealthScore: disk health failed: {Error}", ex.Message); }

try { battery = await batteryTask.ConfigureAwait(false); }
catch (System.Management.ManagementException ex) { Log.Warning("HealthScore: battery failed: {Error}", ex.Message); }
catch (System.Runtime.InteropServices.COMException ex) { Log.Warning("HealthScore: battery WMI COM error: 0x{HResult:X8}", ex.HResult); }
catch (InvalidOperationException ex) { Log.Warning("HealthScore: battery failed: {Error}", ex.Message); }

// Compute component scores
Expand Down
22 changes: 18 additions & 4 deletions SysManager/SysManager/Services/MemoryTestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,39 @@ public async Task<MemoryErrorSummary> CheckErrorLogsAsync(CancellationToken ct =
{ ReverseDirection = true });

var cutoff = DateTime.Now.AddDays(-30);
System.Diagnostics.Eventing.Reader.EventRecord? rec;
while ((rec = reader.ReadEvent()) != null && !ct.IsCancellationRequested)
// Check cancellation BEFORE reading so a record read at the moment of
// cancellation isn't left to the GC; the read result is always wrapped
// in using(rec) below.
while (!ct.IsCancellationRequested && reader.ReadEvent() is { } rec)
{
using (rec)
{
if (rec.TimeCreated.HasValue && rec.TimeCreated.Value < cutoff) break;

var provider = rec.ProviderName ?? "";
bool counted = false;
if (provider.Contains("WHEA"))
{
// Memory-related WHEA events are ID 17 / 18 / 19 / 20 typically
if (rec.Id == 17 || rec.Id == 18 || rec.Id == 19 || rec.Id == 20)
{
wheaCount++;
counted = true;
}
}
else if (provider.Contains("MemoryDiagnostics"))
{
diagCount++;
// 1201 = errors detected. 1101 = test passed (no errors), which
// must NOT count as a memory error (previously any ID counted,
// turning a clean test into a false warning).
if (rec.Id == 1201)
{
diagCount++;
counted = true;
}
}
if (rec.TimeCreated.HasValue && (lastError is null || rec.TimeCreated.Value > lastError))
// Only advance lastError for records that actually count as errors.
if (counted && rec.TimeCreated.HasValue && (lastError is null || rec.TimeCreated.Value > lastError))
lastError = rec.TimeCreated.Value;
}
}
Expand Down
6 changes: 3 additions & 3 deletions SysManager/SysManager/SysManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<RootNamespace>SysManager</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>NU1603;NU1701</NoWarn>
<Version>1.20.39</Version>
<FileVersion>1.20.39.0</FileVersion>
<AssemblyVersion>1.20.39.0</AssemblyVersion>
<Version>1.20.40</Version>
<FileVersion>1.20.40.0</FileVersion>
<AssemblyVersion>1.20.40.0</AssemblyVersion>
<Product>SysManager</Product>
<Description>SysManager — Windows system monitoring toolkit by laurentiu021. Network, updates, health, logs, safe deep cleanup.</Description>
<PackageProjectUrl>https://github.com/laurentiu021/SystemManager</PackageProjectUrl>
Expand Down
Loading