Compare commits

..

No commits in common. "master" and "v0.1.5" have entirely different histories.

37 changed files with 36 additions and 680 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@ -1,5 +1,4 @@
/* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
// File: Controllers/ProcessController.cs
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
@ -23,7 +22,7 @@ namespace WebmrAPI.Controllers
_monitor = monitor;
_logger = logger;
}
internal static string GetFormattedJson<T>(T data, bool pretty)
private string GetFormattedJson<T>(T data, bool pretty)
{
var options = new JsonSerializerOptions
{
@ -269,53 +268,11 @@ namespace WebmrAPI.Controllers
}
}
[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)
private static IEnumerable<T1> Sort<T1, T2>(IEnumerable<T1> data, Func<T1, T2> selector, bool desc)
{
return desc ? data.OrderByDescending(selector) : data.OrderBy(selector);
}
internal static IEnumerable<T> Paginate<T>(IEnumerable<T> data, int limit, int offset)
private IEnumerable<T> Paginate<T>(IEnumerable<T> data, int limit, int offset)
{
if (offset > 0) data = data.Skip(offset);
if (limit > 0) data = data.Take(limit);

View File

@ -1,68 +0,0 @@
/* 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,5 +1,4 @@
/* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
// File: Exceptions/GettingModuleInfoException.cs
namespace WebmrAPI.Exceptions
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,65 +0,0 @@
# 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,8 +1,6 @@
/* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
// File: Services/ProcessMonitor.cs
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Runtime.Versioning;
using WebmrAPI.Configuration;
using WebmrAPI.Exceptions;
@ -18,22 +16,16 @@ namespace WebmrAPI.Services
private readonly ILogger<ProcessMonitor> _logger;
private readonly MonitoringSettings _config;
private LazyConcurrentContainer<ProcessInfo> _processes = new();
private LazyConcurrentContainer<WindowInfo> _windows = new();
private ScanProvider _provider;
public ILogger<ProcessMonitor> Logger { get => _logger; }
public MonitoringSettings Config { get => _config; }
public LazyConcurrentContainer<ProcessInfo> Processes { get => _processes; }
public LazyConcurrentContainer<WindowInfo> Windows { get => _windows; }
public IEnumerable<ProcessBaseInfo>? GetBufferedProcesses()
{
return _processes.Values;
}
public IEnumerable<WindowInfo>? GetBufferedWindows()
{
return _windows.Values;
}
async public Task<ProcessInfo?> GetProcessDetails(int pid, ScanTarget target = ScanTarget.ProcessDetails)
{
@ -57,18 +49,6 @@ namespace WebmrAPI.Services
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)
{
@ -107,46 +87,6 @@ namespace WebmrAPI.Services
try
{
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.");
}
catch (ProcessMonitorException ex)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,130 +0,0 @@
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,5 +1,4 @@
/* This software is licensed by the MIT License, see LICENSE file */
/* Copyright © 2024-2025 Gregory Lirent */
// File: Utils/ConcurrentObject.cs
namespace WebmrAPI.Utils
{

View File

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

View File

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

View File

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

View File

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