2025-07-04 00:04:51 +03:00
|
|
|
|
/* This software is licensed by the MIT License, see LICENSE file */
|
|
|
|
|
/* Copyright © 2024-2025 Gregory Lirent */
|
2025-07-03 15:22:40 +03:00
|
|
|
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Management;
|
|
|
|
|
using WebmrAPI.Models;
|
|
|
|
|
using WebmrAPI.Utils;
|
|
|
|
|
|
|
|
|
|
namespace WebmrAPI.Services.Scanners
|
|
|
|
|
{
|
|
|
|
|
public class ProcessScanner : AbstractCpuScanner<ProcessInfo>
|
|
|
|
|
{
|
|
|
|
|
override public ScanTarget Target { get => ScanTarget.Processes; }
|
|
|
|
|
|
2025-07-03 20:01:19 +03:00
|
|
|
|
private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> _wmiTask;
|
2025-07-03 15:22:40 +03:00
|
|
|
|
|
|
|
|
|
async private Task<Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>> GetWmiDataAsync()
|
|
|
|
|
{
|
|
|
|
|
var wmi = new Dictionary<int, (string? Name, string? CommandLine, int ParentPID)>();
|
2025-07-03 20:01:19 +03:00
|
|
|
|
await Task.Run(() =>
|
2025-07-03 15:22:40 +03:00
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
try
|
2025-07-03 15:22:40 +03:00
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, Name, CommandLine, ParentProcessId FROM Win32_Process"))
|
|
|
|
|
using (var processes = searcher.Get())
|
2025-07-03 15:22:40 +03:00
|
|
|
|
foreach (var obj in processes)
|
|
|
|
|
{
|
|
|
|
|
int pid = Convert.ToInt32(obj["ProcessId"]);
|
|
|
|
|
int ppid = Convert.ToInt32(obj["ParentProcessId"]);
|
|
|
|
|
string? name = obj["Name"]?.ToString();
|
|
|
|
|
string? cmd = obj["CommandLine"]?.ToString();
|
|
|
|
|
|
|
|
|
|
wmi[pid] = (name, cmd, ppid);
|
|
|
|
|
}
|
2025-07-03 20:01:19 +03:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Provider.Logger.LogError(ex, "Failed to retrieve WMI process data for all processes.");
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-07-03 15:22:40 +03:00
|
|
|
|
|
|
|
|
|
return wmi;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 20:01:19 +03:00
|
|
|
|
private bool Populate(ProcessInfo info, Process process, Dictionary<int, (string? Name, string? CommandLine, int ParentPID)> wmiData)
|
2025-07-03 15:22:40 +03:00
|
|
|
|
{
|
|
|
|
|
info.TotalProcessorTime = process.TotalProcessorTime;
|
|
|
|
|
info.MemorySize = (ulong)process.WorkingSet64;
|
|
|
|
|
info.ThreadCount = process.Threads.Count;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (info.MemoryAddress == 0 && process.MainModule != null)
|
|
|
|
|
{
|
|
|
|
|
info.FileName = process.MainModule.FileName;
|
|
|
|
|
info.MemoryAddress = process.MainModule.BaseAddress.ToInt64();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Provider.Logger.LogWarning($"Process {process.ProcessName} (PID: {process.Id}), an error occurred while getting process address: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.MemoryAddress != 0)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
if (wmiData.TryGetValue(process.Id, out var entry))
|
2025-07-03 15:22:40 +03:00
|
|
|
|
{
|
|
|
|
|
info.Name = entry.Name;
|
|
|
|
|
info.CommandLine = entry.CommandLine;
|
|
|
|
|
info.ParentPID = entry.ParentPID;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
info.Name = process.ProcessName;
|
|
|
|
|
Provider.Logger.LogDebug($"WMI data not found for PID {process.Id}. Falling back to Process.ProcessName.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.StartTime == null)
|
|
|
|
|
{
|
|
|
|
|
try { info.StartTime = process.StartTime; }
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Provider.Logger.LogDebug($"Could not get StartTime for PID {process.Id}: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
process.Refresh();
|
|
|
|
|
info.Status = process.Responding ? ProcessInfo.ProcessStatus.Running : ProcessInfo.ProcessStatus.NotResponding;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
info.Status = ProcessInfo.ProcessStatus.Undefined;
|
|
|
|
|
Provider.Logger.LogDebug($"Could not get Status for PID {process.Id}: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 20:01:19 +03:00
|
|
|
|
override async internal Task<bool> ScanAsync(Dictionary<long, ProcessInfo> data)
|
2025-07-03 15:22:40 +03:00
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
var wmiData = await _wmiTask;
|
|
|
|
|
foreach (var process in await Task.Run(Process.GetProcesses))
|
2025-07-03 15:22:40 +03:00
|
|
|
|
using (process)
|
|
|
|
|
{
|
|
|
|
|
ProcessInfo? info;
|
|
|
|
|
|
|
|
|
|
if (process.Id == 0 || process.Id == 4)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (GetFromCacheOrNew(process.Id, out info))
|
|
|
|
|
{
|
|
|
|
|
info.CpuUsage = CalcCpuUsage(info.ProcessorTime, Container.Elapsed.TotalMilliseconds);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
info.PID = process.Id;
|
2025-07-03 20:01:19 +03:00
|
|
|
|
info.CpuUsage = 0;
|
2025-07-03 15:22:40 +03:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 20:01:19 +03:00
|
|
|
|
if (Populate(info, process, wmiData))
|
2025-07-03 15:22:40 +03:00
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
data.Add(info.PID, info);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Provider.Logger.LogWarning($"Skipping process {process.Id} due to incomplete information during population.");
|
|
|
|
|
}
|
2025-07-03 15:22:40 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProcessScanner(IScanProvider scanner, LazyConcurrentContainer<ProcessInfo> container)
|
|
|
|
|
: base(scanner, container)
|
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
_wmiTask = GetWmiDataAsync();
|
2025-07-03 15:22:40 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|