2025-07-02 16:06:50 +03:00
|
|
|
|
// File: Controllers/ProcessController.cs
|
|
|
|
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using System.Text.Json.Serialization;
|
|
|
|
|
using WebmrAPI.Models;
|
|
|
|
|
using WebmrAPI.Services;
|
2025-07-03 20:01:19 +03:00
|
|
|
|
using WebmrAPI.Services.Scanners;
|
2025-07-02 16:06:50 +03:00
|
|
|
|
|
|
|
|
|
namespace WebmrAPI.Controllers
|
|
|
|
|
{
|
|
|
|
|
[ApiController]
|
|
|
|
|
[Route("api/v1/[controller]")]
|
|
|
|
|
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
|
|
|
|
public class ProcessController : ControllerBase
|
|
|
|
|
{
|
|
|
|
|
private readonly ProcessMonitor _monitor;
|
|
|
|
|
private readonly ILogger<ProcessController> _logger;
|
|
|
|
|
|
|
|
|
|
public ProcessController(ProcessMonitor monitor, ILogger<ProcessController> logger)
|
|
|
|
|
{
|
|
|
|
|
_monitor = monitor;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
}
|
|
|
|
|
private string GetFormattedJson<T>(T data, bool pretty)
|
|
|
|
|
{
|
|
|
|
|
var options = new JsonSerializerOptions
|
|
|
|
|
{
|
|
|
|
|
WriteIndented = pretty,
|
|
|
|
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
|
|
|
|
Converters = { new JsonStringEnumConverter() }
|
|
|
|
|
};
|
|
|
|
|
return JsonSerializer.Serialize(data, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<ProcessBaseInfo>))]
|
2025-07-03 20:01:19 +03:00
|
|
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
2025-07-02 16:06:50 +03:00
|
|
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
2025-07-03 20:01:19 +03:00
|
|
|
|
public IActionResult GetProcesses(
|
|
|
|
|
[FromQuery] bool pretty = false,
|
|
|
|
|
[FromQuery] string sortBy = "",
|
|
|
|
|
[FromQuery] bool desc = false,
|
|
|
|
|
[FromQuery] int limit = 0,
|
|
|
|
|
[FromQuery] int offset = 0
|
|
|
|
|
)
|
2025-07-02 16:06:50 +03:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
var data = _monitor.GetBufferedProcesses();
|
|
|
|
|
|
|
|
|
|
if (data != null && !String.IsNullOrEmpty(sortBy))
|
|
|
|
|
{
|
|
|
|
|
sortBy = sortBy.ToLowerInvariant();
|
|
|
|
|
switch (sortBy)
|
|
|
|
|
{
|
|
|
|
|
case "pid": data = Sort(data, p => p.PID, desc); break;
|
|
|
|
|
case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break;
|
|
|
|
|
case "memsize": data = Sort(data, p => p.MemorySize, desc); break;
|
|
|
|
|
case "parentpid": data = Sort(data, p => p.ParentPID, desc); break;
|
|
|
|
|
case "name": data = Sort(data, p => p.Name, desc); break;
|
|
|
|
|
case "filename": data = Sort(data, p => p.FileName, desc); break;
|
|
|
|
|
case "commandline": data = Sort(data, p => p.CommandLine, desc); break;
|
|
|
|
|
case "threadcount": data = Sort(data, p => p.ThreadCount, desc); break;
|
|
|
|
|
case "status": data = Sort(data, p => p.Status, desc); break;
|
|
|
|
|
case "starttime": data = Sort(data, p => p.StartTime, desc); break;
|
|
|
|
|
case "cpuusage": data = Sort(data, p => p.CpuUsage, desc); break;
|
|
|
|
|
default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json");
|
2025-07-02 16:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "An error occurred when getting the list of processes");
|
|
|
|
|
return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred when receiving processes.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("{pid}")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessInfo))]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
2025-07-03 20:01:19 +03:00
|
|
|
|
async public Task<IActionResult> GetProcessById(int pid,
|
|
|
|
|
[FromQuery] bool pretty = false
|
|
|
|
|
)
|
2025-07-02 16:06:50 +03:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-07-03 20:01:19 +03:00
|
|
|
|
var data = await _monitor.GetProcessDetails(pid, ScanTarget.ProcessDetails);
|
2025-07-02 16:06:50 +03:00
|
|
|
|
if (data == null)
|
|
|
|
|
{
|
|
|
|
|
return NotFound($"The process with the PID {pid} was not found or its details could not be obtained.");
|
|
|
|
|
}
|
|
|
|
|
return Content(GetFormattedJson(data, pretty), "application/json");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, $"An error occurred while receiving process details for PID {pid}.");
|
|
|
|
|
return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving process details for PID {pid}.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 20:01:19 +03:00
|
|
|
|
[HttpGet("{pid}/base_info")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessBaseInfo))]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
|
|
|
async public Task<IActionResult> GetProcessBaseInfoById(int pid,
|
|
|
|
|
[FromQuery] bool pretty = false
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var data = await _monitor.GetProcessDetails(pid, 0);
|
|
|
|
|
|
|
|
|
|
if (data == null)
|
|
|
|
|
{
|
|
|
|
|
return NotFound($"The process with the PID {pid} was not found or its details could not be obtained.");
|
|
|
|
|
}
|
|
|
|
|
return Content(GetFormattedJson((ProcessBaseInfo)data, pretty), "application/json");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, $"An error occurred while receiving memory regions for PID {pid}.");
|
|
|
|
|
return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving memory regions for PID {pid}.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("{pid}/memory_regions")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<MemoryRegionInfo>))]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
|
|
|
async public Task<IActionResult> GetProcessMemoryPagesById(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.GetProcessDetails(pid, ScanTarget.MemoryRegions))?.MemoryRegions;
|
|
|
|
|
|
|
|
|
|
if (data == null)
|
|
|
|
|
{
|
|
|
|
|
return NotFound($"The process with the PID {pid} was not found or its details could not be obtained.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!String.IsNullOrEmpty(sortBy))
|
|
|
|
|
{
|
|
|
|
|
sortBy = sortBy.ToLowerInvariant();
|
|
|
|
|
switch (sortBy)
|
|
|
|
|
{
|
|
|
|
|
case "state": data = Sort(data, p => p.MemoryState, desc); break;
|
|
|
|
|
case "protection": data = Sort(data, p => p.MemoryPageProtection, desc); break;
|
|
|
|
|
case "type": data = Sort(data, p => p.MemoryType, desc); break;
|
|
|
|
|
case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break;
|
|
|
|
|
case "memsize": data = Sort(data, p => p.MemorySize, 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 memory regions for PID {pid}.");
|
|
|
|
|
return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving memory regions for PID {pid}.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("{pid}/modules")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<MemoryRegionInfo>))]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
|
|
|
async public Task<IActionResult> GetProcessModulesById(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.GetProcessDetails(pid, ScanTarget.Modules))?.Modules;
|
|
|
|
|
|
|
|
|
|
if (data == null)
|
|
|
|
|
{
|
|
|
|
|
return NotFound($"The process with the PID {pid} was not found or its details could not be obtained.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!String.IsNullOrEmpty(sortBy))
|
|
|
|
|
{
|
|
|
|
|
sortBy = sortBy.ToLowerInvariant();
|
|
|
|
|
switch (sortBy)
|
|
|
|
|
{
|
|
|
|
|
case "name": data = Sort(data, p => p.ModuleName, desc); break;
|
|
|
|
|
case "filename": data = Sort(data, p => p.FileName, desc); break;
|
|
|
|
|
case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break;
|
|
|
|
|
case "entryaddress": data = Sort(data, p => p.EntrypointRawAddress, desc); break;
|
|
|
|
|
case "memsize": data = Sort(data, p => p.MemorySize, 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 modules for PID {pid}.");
|
|
|
|
|
return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving modules for PID {pid}.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("{pid}/threads")]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<MemoryRegionInfo>))]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
|
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
|
|
|
async public Task<IActionResult> GetProcessThreadsById(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.GetProcessDetails(pid, ScanTarget.Threads))?.Threads;
|
|
|
|
|
|
|
|
|
|
if (data == null)
|
|
|
|
|
{
|
|
|
|
|
return NotFound($"The process with the PID {pid} was not found or its details could not be obtained.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!String.IsNullOrEmpty(sortBy))
|
|
|
|
|
{
|
|
|
|
|
sortBy = sortBy.ToLowerInvariant();
|
|
|
|
|
switch (sortBy)
|
|
|
|
|
{
|
|
|
|
|
case "id": data = Sort(data, p => p.ID, desc); break;
|
|
|
|
|
case "priority": data = Sort(data, p => p.CurrentPriority, desc); break;
|
|
|
|
|
case "basepriority": data = Sort(data, p => p.BasePriority, desc); break;
|
|
|
|
|
case "cpuusage": data = Sort(data, p => p.CpuUsage, 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}.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IEnumerable<T1> Sort<T1, T2>(IEnumerable<T1> data, Func<T1, T2> selector, bool desc)
|
|
|
|
|
{
|
|
|
|
|
return desc ? data.OrderByDescending(selector) : data.OrderBy(selector);
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
return data;
|
|
|
|
|
}
|
2025-07-02 16:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
}
|