Compare commits

...

5 Commits

Author SHA1 Message Date
37d4f290e3 v0.1.6: Added windows 2025-07-13 14:24:51 +03:00
c0ccca98cf Update README.md 2025-07-04 00:05:55 +03:00
757ece5967 Add LICENSE 2025-07-04 00:04:51 +03:00
1ffccd6e77 Add README.md 2025-07-04 00:04:27 +03:00
3d7ff9aac0 v0.1.5 Fixes 2025-07-03 21:56:20 +03:00
37 changed files with 680 additions and 36 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
/.vs/ /.*/
/bin/ /bin/
/obj/ /obj/
/Properties/ /Properties/

View File

@ -1,4 +1,5 @@
// File: Configuration/AppSettings.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Configuration namespace WebmrAPI.Configuration
{ {

View File

@ -1,4 +1,5 @@
// File: Controllers/ProcessController.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Text.Json; using System.Text.Json;
@ -22,7 +23,7 @@ namespace WebmrAPI.Controllers
_monitor = monitor; _monitor = monitor;
_logger = logger; _logger = logger;
} }
private string GetFormattedJson<T>(T data, bool pretty) internal static string GetFormattedJson<T>(T data, bool pretty)
{ {
var options = new JsonSerializerOptions var options = new JsonSerializerOptions
{ {
@ -268,11 +269,53 @@ namespace WebmrAPI.Controllers
} }
} }
private static IEnumerable<T1> Sort<T1, T2>(IEnumerable<T1> data, Func<T1, T2> selector, bool desc) [HttpGet("{pid}/windows")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<BaseWindowInfo>))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
async public Task<IActionResult> GetProcessWindowsById(int pid,
[FromQuery] bool pretty = false,
[FromQuery] string sortBy = "",
[FromQuery] bool desc = false,
[FromQuery] int limit = 0,
[FromQuery] int offset = 0
)
{
try
{
var data = await _monitor.GetProcessWindows(pid);
if (data == null)
{
return NotFound($"The process with the PID {pid} was not found or its windows could not be obtained.");
}
if (!String.IsNullOrEmpty(sortBy))
{
sortBy = sortBy.ToLowerInvariant();
switch (sortBy)
{
case "title": data = ProcessController.Sort(data, p => p.Title, desc); break;
case "id": data = ProcessController.Sort(data, p => p.Id, desc); break;
default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}.");
}
}
return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json");
}
catch (Exception ex)
{
_logger.LogError(ex, $"An error occurred while receiving threads for PID {pid}.");
return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving threads for PID {pid}.");
}
}
internal static IEnumerable<T1> Sort<T1, T2>(IEnumerable<T1> data, Func<T1, T2> selector, bool desc)
{ {
return desc ? data.OrderByDescending(selector) : data.OrderBy(selector); return desc ? data.OrderByDescending(selector) : data.OrderBy(selector);
} }
private IEnumerable<T> Paginate<T>(IEnumerable<T> data, int limit, int offset) internal static IEnumerable<T> Paginate<T>(IEnumerable<T> data, int limit, int offset)
{ {
if (offset > 0) data = data.Skip(offset); if (offset > 0) data = data.Skip(offset);
if (limit > 0) data = data.Take(limit); if (limit > 0) data = data.Take(limit);

View File

@ -0,0 +1,68 @@
/* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using Microsoft.AspNetCore.Mvc;
using WebmrAPI.Models;
using WebmrAPI.Services;
namespace WebmrAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public class WindowsController : ControllerBase
{
private readonly ProcessMonitor _monitor;
private readonly ILogger<ProcessController> _logger;
public WindowsController(ProcessMonitor monitor, ILogger<ProcessController> logger)
{
_monitor = monitor;
_logger = logger;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<WindowInfo>))]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetWindows(
[FromQuery] bool pretty = false,
[FromQuery] string sortBy = "",
[FromQuery] bool desc = false,
[FromQuery] int limit = 0,
[FromQuery] int offset = 0,
[FromQuery] string search = ""
)
{
try
{
var data = _monitor.GetBufferedWindows();
if (data != null && !String.IsNullOrEmpty(search))
{
data = data.Where(p => p.Title != null && p.Title.ToLowerInvariant().Contains(search.ToLowerInvariant()));
}
if (data != null && !String.IsNullOrEmpty(sortBy))
{
sortBy = sortBy.ToLowerInvariant();
switch (sortBy)
{
case "pid": data = ProcessController.Sort(data, p => p.PID, desc); break;
case "title": data = ProcessController.Sort(data, p => p.Title, desc); break;
case "threadid": data = ProcessController.Sort(data, p => p.ThreadId, desc); break;
case "id": data = ProcessController.Sort(data, p => p.Id, desc); break;
default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}.");
}
}
return Content(ProcessController.GetFormattedJson(ProcessController.Paginate(data, limit, offset), pretty), "application/json");
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred when getting the list of windows");
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred when receiving windows.");
}
}
}
}

View File

@ -1,4 +1,5 @@
// File: Exceptions/GettingModuleInfoException.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Exceptions namespace WebmrAPI.Exceptions
{ {

View File

@ -1,4 +1,5 @@
// File: Exceptions/MemoryRegionException.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Exceptions namespace WebmrAPI.Exceptions
{ {

View File

@ -1,4 +1,5 @@
// File: Exceptions/ProcessAccessDeniedException.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Exceptions namespace WebmrAPI.Exceptions
{ {

View File

@ -1,4 +1,5 @@
// File: Exceptions/ProcessMonitorException.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Exceptions namespace WebmrAPI.Exceptions
{ {

8
LICENSE Normal file
View File

@ -0,0 +1,8 @@
MIT License
Copyright (c) <2024-2025> <Gregory Lirent>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,4 +1,5 @@
// File: Models/MemoryRegion.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using WebmrAPI.Utils; using WebmrAPI.Utils;

View File

@ -1,4 +1,5 @@
// File: Models/MemoryRegionInfo.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using WebmrAPI.Utils; using WebmrAPI.Utils;

View File

@ -1,4 +1,5 @@
// File: Models/ProcessBaseInfo.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Text.Json.Serialization; using System.Text.Json.Serialization;

View File

@ -1,4 +1,5 @@
// File: Models/ProcessInfo.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -9,6 +10,8 @@ namespace WebmrAPI.Models
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public class ProcessInfo : ProcessBaseInfo public class ProcessInfo : ProcessBaseInfo
{ {
private IEnumerable<BaseWindowInfo>? _windows;
[JsonIgnore] [JsonIgnore]
public LazyConcurrentContainer<MemoryRegionInfo> MemoryRegionsContainer { get; set; } = new(); public LazyConcurrentContainer<MemoryRegionInfo> MemoryRegionsContainer { get; set; } = new();
[JsonIgnore] [JsonIgnore]
@ -33,5 +36,12 @@ namespace WebmrAPI.Models
{ {
get => ThreadsContainer.Values; get => ThreadsContainer.Values;
} }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IEnumerable<BaseWindowInfo>? Windows
{
get => LockedGet(ref _windows);
internal set => LockedSet(ref _windows, value);
}
} }
} }

View File

@ -1,4 +1,5 @@
// File: Models/ProcessModuleInfo.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Text.Json.Serialization; using System.Text.Json.Serialization;

View File

@ -1,4 +1,5 @@
// File: Models/ProcessThreadInfo.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using WebmrAPI.Utils; using WebmrAPI.Utils;
@ -9,6 +10,7 @@ namespace WebmrAPI.Models
{ {
private TimeSpan _lastPTime = TimeSpan.Zero; private TimeSpan _lastPTime = TimeSpan.Zero;
private TimeSpan _curPTime = TimeSpan.Zero; private TimeSpan _curPTime = TimeSpan.Zero;
private IEnumerable<BaseWindowInfo>? _windows;
private int _id = 0; private int _id = 0;
private int _currentPriority = 0; private int _currentPriority = 0;
@ -48,5 +50,12 @@ namespace WebmrAPI.Models
get => LockedGet(ref _cpuUsage); get => LockedGet(ref _cpuUsage);
set => LockedSet(ref _cpuUsage, value); set => LockedSet(ref _cpuUsage, value);
} }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IEnumerable<BaseWindowInfo>? Windows
{
get => LockedGet(ref _windows);
internal set => LockedSet(ref _windows, value);
}
} }
} }

122
Models/WindowInfo.cs Normal file
View File

@ -0,0 +1,122 @@
using System.Drawing;
using System.Text.Json.Serialization;
using WebmrAPI.Utils;
namespace WebmrAPI.Models
{
public class GeometryPoint
{
internal Point _point;
public static GeometryPoint Empty { get => new GeometryPoint(Point.Empty); }
public int X { get => _point.X; }
public int Y { get => _point.Y; }
public GeometryPoint(int x, int y)
{
_point = new Point(x, y);
}
public GeometryPoint(Point point)
{
_point = point;
}
}
public class BaseGeometry
{
protected Rectangle _rectangle;
public int Width { get => _rectangle.Width; }
public int Height { get => _rectangle.Height; }
public static BaseGeometry Empty { get => new BaseGeometry { _rectangle = Rectangle.Empty }; }
public static Geometry FromLTRB(int left, int top, int right, int bottom)
{
return new Geometry
{
_rectangle = Rectangle.FromLTRB(left, top, right, bottom)
};
}
public bool Contains(GeometryPoint pt)
{
return _rectangle.Contains(pt._point);
}
}
public class Geometry : BaseGeometry
{
public int X { get => _rectangle.X; }
public int Y { get => _rectangle.Y; }
public new static Geometry Empty { get => new Geometry { _rectangle = Rectangle.Empty }; }
}
public class BaseWindowInfo : ConcurrentObject
{
private IntPtr _hwnd;
private string _title = String.Empty;
private GeometryPoint _cursor = GeometryPoint.Empty;
private Geometry _gWindow = Geometry.Empty;
private BaseGeometry _gContent = BaseGeometry.Empty;
private bool _isActive;
public string Id
{
get => LockedGet(ref _hwnd).ToString();
set
{
if (!IntPtr.TryParse(value, out IntPtr hwnd))
throw new ArgumentException("Invalid window Id format. Must be a valid hwnd (IntPtr) string representation");
LockedSet(ref _hwnd, hwnd);
}
}
public string Title
{
get => LockedGet(ref _title);
set => LockedSet(ref _title, value);
}
public GeometryPoint CursorPosition
{
get => LockedGet(ref _cursor);
set => LockedSet(ref _cursor, value);
}
public Geometry WindowGeometry
{
get => LockedGet(ref _gWindow);
set => LockedSet(ref _gWindow, value);
}
public BaseGeometry ContentGeometry
{
get => LockedGet(ref _gContent);
set => LockedSet(ref _gContent, value);
}
public bool IsActive
{
get => LockedGet(ref _isActive);
set => LockedSet(ref _isActive, value);
}
[JsonIgnore]
public IntPtr Hwnd
{
get => LockedGet(ref _hwnd);
set => LockedSet(ref _hwnd, value);
}
public bool HasCursor { get => IsActive && ContentGeometry.Contains(CursorPosition); }
}
public class WindowInfo : BaseWindowInfo
{
private int _pid;
private int _threadId;
public int PID
{
get => LockedGet(ref _pid);
set => LockedSet(ref _pid, value);
}
public int ThreadId
{
get => LockedGet(ref _threadId);
set => LockedSet(ref _threadId, value);
}
}
}

View File

@ -1,4 +1,5 @@
// File: Program.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Runtime.Versioning; using System.Runtime.Versioning;
@ -9,6 +10,7 @@ using WebmrAPI.Services;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddWindowsService();
builder.Services.Configure<AppSettings>(builder.Configuration); builder.Services.Configure<AppSettings>(builder.Configuration);
builder.Services.AddLogging(config => builder.Services.AddLogging(config =>
{ {

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# Process Monitoring Service (WebmrAPI)
This project implements a Windows service designed for comprehensive monitoring of running processes, their memory regions, loaded modules, and threads. It exposes this detailed system information through a RESTful API, making it accessible for remote querying, integration with other systems, or advanced analysis.
## Features
* **RESTful API:** Provides endpoints to query various aspects of process information.
* **Comprehensive Process Details:** Gathers in-depth data including:
* Basic process information (PID, Name, File Path, Command Line).
* Detailed **Memory Regions** (virtual addresses, sizes, protection flags, state, type).
* Loaded **Modules (DLLs)** with their base addresses and sizes.
* Process **Threads** including their IDs, priorities, and CPU usage.
* **Background Scanning & Caching:** Periodically scans the system for processes and caches their basic information to reduce overhead on subsequent requests. Detailed information (memory regions, modules, threads) is loaded on demand and also cached with configurable timeouts.
* **CPU Usage Calculation:** Dynamically calculates CPU utilization for processes and threads.
* **Pagination & Sorting:** API endpoints support pagination (`limit`, `offset`) and sorting by various process attributes (`sortBy`, `desc`).
* **Process Name Search:** Allows filtering the list of processes by name (partial, case-insensitive match).
* **Windows Service Deployment:** Designed to run as a robust, automatically starting Windows Service.
* **Self-Contained Deployment:** Published as a self-contained application, meaning it includes all necessary .NET runtime components and does not require .NET to be pre-installed on the target machine.
* **Swagger/OpenAPI:** Provides an interactive API documentation interface for easy exploration and testing of endpoints.
## How It Works
The `Process Monitoring Service` is an ASP.NET Core application configured to run as an `IHostedService` within a Windows Service context.
1. **Data Collection:** It leverages `System.Diagnostics.Process` and low-level Windows API calls (via P/Invoke) to gather detailed information about processes, memory regions, modules, and threads.
2. **Background Monitoring:** The `ProcessMonitor` component periodically scans for running processes and updates a buffered cache.
3. **API Exposure:** An ASP.NET Core Web API layer (`ProcessController`) provides HTTP endpoints to access the cached and on-demand collected data.
4. **Serialization:** All data is serialized to JSON with snake_case naming and pretty-printing for readability.
5. **Deployment:** The application is published as a self-contained executable for `win-x64`, allowing it to be deployed without requiring a pre-installed .NET Runtime on the target Windows machine. Installation as a Windows Service is managed via a simple batch script (`install.bat`) included in the deployment package.
## Usage (API Endpoints Examples)
* `GET /api/v1/process`: Get a paginated, sorted, and searchable list of all processes.
* Example: `/api/v1/process?pretty=true&sortBy=cpuusage&desc=true&limit=10&offset=0&search=chrome`
* `GET /api/v1/process/{pid}`: Get full details for a specific process.
* `GET /api/v1/process/{pid}/base_info`: Get basic info for a specific process.
* `GET /api/v1/process/{pid}/memory_regions`: Get memory regions for a process (with pagination/sorting).
* `GET /api/v1/process/{pid}/modules`: Get loaded modules for a process (with pagination/sorting).
* `GET /api/v1/process/{pid}/threads`: Get threads for a process (with pagination/sorting).
## Deployment
Latest releases can be found [here](https://dev.lirent.ru/lirent/winmr-api/releases).

View File

@ -1,6 +1,8 @@
// File: Services/ProcessMonitor.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using WebmrAPI.Configuration; using WebmrAPI.Configuration;
using WebmrAPI.Exceptions; using WebmrAPI.Exceptions;
@ -16,16 +18,22 @@ namespace WebmrAPI.Services
private readonly ILogger<ProcessMonitor> _logger; private readonly ILogger<ProcessMonitor> _logger;
private readonly MonitoringSettings _config; private readonly MonitoringSettings _config;
private LazyConcurrentContainer<ProcessInfo> _processes = new(); private LazyConcurrentContainer<ProcessInfo> _processes = new();
private LazyConcurrentContainer<WindowInfo> _windows = new();
private ScanProvider _provider; private ScanProvider _provider;
public ILogger<ProcessMonitor> Logger { get => _logger; } public ILogger<ProcessMonitor> Logger { get => _logger; }
public MonitoringSettings Config { get => _config; } public MonitoringSettings Config { get => _config; }
public LazyConcurrentContainer<ProcessInfo> Processes { get => _processes; } public LazyConcurrentContainer<ProcessInfo> Processes { get => _processes; }
public LazyConcurrentContainer<WindowInfo> Windows { get => _windows; }
public IEnumerable<ProcessBaseInfo>? GetBufferedProcesses() public IEnumerable<ProcessBaseInfo>? GetBufferedProcesses()
{ {
return _processes.Values; return _processes.Values;
} }
public IEnumerable<WindowInfo>? GetBufferedWindows()
{
return _windows.Values;
}
async public Task<ProcessInfo?> GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails) async public Task<ProcessInfo?> GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails)
{ {
@ -49,6 +57,18 @@ namespace WebmrAPI.Services
return null; return null;
} }
public Task<IEnumerable<BaseWindowInfo>?> GetProcessWindows(int pid)
{
return Task.Run(() =>
{
if (_processes.Container != null && _processes.Container.TryGetValue(pid, out var info))
{
return info.Windows;
}
return null;
});
}
public ProcessMonitor(ILogger<ProcessMonitor> logger, IOptions<AppSettings> settings) public ProcessMonitor(ILogger<ProcessMonitor> logger, IOptions<AppSettings> settings)
{ {
@ -87,6 +107,46 @@ namespace WebmrAPI.Services
try try
{ {
await _provider.CreateScanTask().ScanAsync(); await _provider.CreateScanTask().ScanAsync();
await new WindowScanner(_provider, _windows).ScanAsync();
Dictionary<int, List<BaseWindowInfo>> pWin = new();
Dictionary<int, List<BaseWindowInfo>> tWin = new();
if (_windows.Values != null)
foreach (var window in _windows.Values)
{
List<BaseWindowInfo> p;
List<BaseWindowInfo> t;
if (!pWin.TryGetValue(window.PID, out p))
{
pWin.Add(window.PID, p = new());
}
if (!tWin.TryGetValue(window.ThreadId, out t))
{
tWin.Add(window.ThreadId, t = new());
}
p.Add(window);
t.Add(window);
}
if (_processes.Values != null)
foreach (var proc in _processes.Values)
{
if (pWin.TryGetValue(proc.PID, out var pws))
{
proc.Windows = pws;
if (proc.ThreadsContainer.Values != null)
foreach (var thread in proc.ThreadsContainer.Values)
{
if (tWin.TryGetValue(thread.ID, out var tws))
{
thread.Windows = tws;
}
}
}
}
_logger.LogInformation($"Process buffer updated, contains {Processes.Container?.Count} processes."); _logger.LogInformation($"Process buffer updated, contains {Processes.Container?.Count} processes.");
} }
catch (ProcessMonitorException ex) catch (ProcessMonitorException ex)

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/AbstractCpuScanner.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using WebmrAPI.Utils; using WebmrAPI.Utils;

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/AbstractScanner.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using WebmrAPI.Utils; using WebmrAPI.Utils;

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/IScanProvider.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Services.Scanners namespace WebmrAPI.Services.Scanners
{ {

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/IScannable.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Services.Scanners namespace WebmrAPI.Services.Scanners
{ {

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/MemoryRegionScanner.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using WebmrAPI.Exceptions; using WebmrAPI.Exceptions;
using WebmrAPI.Models; using WebmrAPI.Models;

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/ModuleScanner.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Diagnostics; using System.Diagnostics;
using WebmrAPI.Exceptions; using WebmrAPI.Exceptions;

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/ProcessScanner.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Diagnostics; using System.Diagnostics;
using System.Management; using System.Management;

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/ScanProvider.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using WebmrAPI.Models; using WebmrAPI.Models;

View File

@ -1,6 +1,5 @@
// File: Services/Scanners/ScanQueue.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Diagnostics;
namespace WebmrAPI.Services.Scanners namespace WebmrAPI.Services.Scanners
{ {

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/ScanTarget.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Services.Scanners namespace WebmrAPI.Services.Scanners
{ {
@ -9,6 +10,7 @@ namespace WebmrAPI.Services.Scanners
MemoryRegions = 0x02, MemoryRegions = 0x02,
Modules = 0x04, Modules = 0x04,
Threads = 0x08, Threads = 0x08,
Windows = 0x10,
ProcessDetails = MemoryRegions | Modules | Threads, ProcessDetails = MemoryRegions | Modules | Threads,
All = Processes | ProcessDetails All = Processes | ProcessDetails

View File

@ -1,4 +1,5 @@
// File: Services/Scanners/ThreadScanner.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Diagnostics; using System.Diagnostics;
using WebmrAPI.Models; using WebmrAPI.Models;

View File

@ -0,0 +1,130 @@
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using WebmrAPI.Models;
using WebmrAPI.Utils;
namespace WebmrAPI.Services.Scanners
{
public class WindowScanner : AbstractScanner<WindowInfo>
{
[StructLayout(LayoutKind.Explicit)]
private struct RECT
{
[FieldOffset(0)]
public int Left;
[FieldOffset(4)]
public int Top;
[FieldOffset(8)]
public int Right;
[FieldOffset(12)]
public int Bottom;
[FieldOffset(0)]
public int X;
[FieldOffset(4)]
public int Y;
}
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(out RECT lpPoint);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ScreenToClient(IntPtr hWnd, ref RECT lpPoint);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
override public ScanTarget Target { get => ScanTarget.Windows; }
private bool GetWindowInfo(WindowInfo info, IntPtr hwnd)
{
int length = GetWindowTextLength(hwnd);
if (length > 0)
{
var sb = new StringBuilder(length + 1);
GetWindowText(hwnd, sb, sb.Capacity);
info.Title = sb.ToString();
if (GetCursorPos(out RECT pt) && ScreenToClient(hwnd, ref pt))
{
info.CursorPosition = new GeometryPoint(pt.X, pt.Y);
}
if (GetWindowRect(hwnd, out RECT wRect))
{
info.WindowGeometry = Geometry.FromLTRB(wRect.Left, wRect.Top, wRect.Right, wRect.Bottom);
}
if (GetClientRect(hwnd, out RECT cRect))
{
info.ContentGeometry = BaseGeometry.FromLTRB(cRect.Left, cRect.Top, cRect.Right, cRect.Bottom);
}
return true;
}
return false;
}
override internal Task<bool> ScanAsync(Dictionary<long, WindowInfo> data)
{
return Task.Run(() =>
{
var foreground = GetForegroundWindow();
EnumWindows(delegate (IntPtr hwnd, IntPtr lParam)
{
if (IsWindowVisible(hwnd))
{
WindowInfo? info;
if (!GetFromCacheOrNew(hwnd.ToInt64(), out info))
{
info.Hwnd = hwnd;
int threadId;
if ((threadId = GetWindowThreadProcessId(hwnd, out int pid)) != 0)
{
info.PID = pid;
info.ThreadId = threadId;
}
else
{
info = null;
}
}
if (info != null && GetWindowInfo(info, hwnd))
{
info.IsActive = foreground == info.Hwnd;
data.Add(hwnd.ToInt64(), info);
}
}
return true;
}, IntPtr.Zero);
return true;
});
}
public WindowScanner(IScanProvider scanner, LazyConcurrentContainer<WindowInfo> container)
: base(scanner, container) { }
}
}

View File

@ -1,4 +1,5 @@
// File: Utils/ConcurrentObject.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
namespace WebmrAPI.Utils namespace WebmrAPI.Utils
{ {

View File

@ -1,4 +1,5 @@
// File: Utils/JsonEnumConverter.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;

View File

@ -1,4 +1,5 @@
// File: Utils/LazyConcurrentContainer.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Collections.Concurrent; using System.Collections.Concurrent;

View File

@ -1,4 +1,5 @@
// File: Utils/WindowsProcess.cs /* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;

103
install.bat Normal file
View File

@ -0,0 +1,103 @@
@echo off
REM install.bat - Script for installing/uninstalling ProcessMonitoringService on the target machine.
REM
REM --- Run as Administrator ---
REM This script must be run with administrator privileges to create and manage Windows services.
REM If the script is not run as administrator, it will attempt to restart itself with elevated privileges.
setlocal
REM Check if the script is running with administrator privileges
NET SESSION >NUL 2>&1
IF %ERRORLEVEL% NEQ 0 (
echo.
echo This script must be run with administrator privileges.
echo Attempting to restart with elevated privileges...
echo.
powershell -Command "Start-Process -FilePath '%~dpnx0' -Verb RunAs"
exit /b
)
echo Script is running with administrator privileges.
REM --- Service Settings ---
SET "SERVICE_NAME=ProcessMonitoringService"
SET "SERVICE_DISPLAY_NAME=Process Monitoring Service"
SET "SERVICE_DESCRIPTION=A service for detailed monitoring processes and memory regions."
REM The service executable is in the same directory as install.bat
SET "SERVICE_EXE_PATH=%~dp0%SERVICE_NAME%.exe"
REM --- Options ---
SET "ACTION=%1"
IF "%ACTION%"=="" SET "ACTION=install"
echo.
echo --- Executing action: %ACTION% ---
echo.
IF /I "%ACTION%"=="install" GOTO :INSTALL
IF /I "%ACTION%"=="uninstall" GOTO :UNINSTALL
echo Unknown action: "%ACTION%".
echo Usage:
echo %~nx0 install - Creates and starts the service.
echo %~nx0 uninstall - Stops, deletes the service. After this, manually delete the folder.
GOTO :END
:INSTALL
echo --- Step 1: Create Windows Service ---
echo Creating service "%SERVICE_DISPLAY_NAME%" (%SERVICE_NAME%)...
IF NOT EXIST "%SERVICE_EXE_PATH%" (
echo ERROR: Service executable not found: "%SERVICE_EXE_PATH%".
GOTO :END
)
sc create "%SERVICE_NAME%" binPath="\"%SERVICE_EXE_PATH%\"" DisplayName="%SERVICE_DISPLAY_NAME%" start= auto
IF %ERRORLEVEL% NEQ 0 (
echo ERROR: Failed to create service. A service with this name might already exist or you lack permissions.
GOTO :END
)
echo Service created successfully.
echo --- Step 2: Set Service Description ---
sc description "%SERVICE_NAME%" "%SERVICE_DESCRIPTION%"
IF %ERRORLEVEL% NEQ 0 (
echo WARNING: Failed to set service description.
)
echo --- Step 3: Start Service ---
echo Starting service "%SERVICE_NAME%"...
sc start "%SERVICE_NAME%"
IF %ERRORLEVEL% NEQ 0 (
echo ERROR: Failed to start service. Check service logs.
GOTO :END
)
echo Service started successfully.
GOTO :END
:UNINSTALL
echo --- Stop Service ---
echo Stopping service "%SERVICE_NAME%"...
sc stop "%SERVICE_NAME%"
IF %ERRORLEVEL% NEQ 0 (
echo WARNING: Failed to stop service. The service might not be running or you lack permissions.
)
echo --- Delete Windows Service ---
echo Deleting service "%SERVICE_NAME%"...
sc delete "%SERVICE_NAME%"
IF %ERRORLEVEL% NEQ 0 (
echo ERROR: Failed to delete service. The service might not exist or you lack permissions.
GOTO :END
)
echo Service deleted successfully.
echo For complete removal, please manually delete the folder from which install.bat was run.
GOTO :END
:END
echo.
echo Operation completed.
endlocal
pause

View File

@ -14,7 +14,7 @@
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion> <FileVersion>1.0.0.0</FileVersion>
<Version>0.1.5</Version> <Version>0.1.6</Version>
<Company>OpenSource</Company> <Company>OpenSource</Company>
<Product>Process Monitoring Agent</Product> <Product>Process Monitoring Agent</Product>
<Description>A service for detailed monitoring processes and memory regions.</Description> <Description>A service for detailed monitoring processes and memory regions.</Description>
@ -26,5 +26,6 @@
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Management" Version="8.0.0" /> <PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>