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
118 changes: 82 additions & 36 deletions src/Task.Monitor.Interop.Win32/WinService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ public static class WinService
{
public const int SC_MANAGER_CONNECT = 0x0001;
public const uint SC_MANAGER_ALL_ACCESS = 0xF003F;
public const int SC_MANAGER_ENUMERATE_SERVICE = 0x0004;

public const uint SERVICE_TYPE_ALL = 0x00000030;
public const uint SERVICE_STATE_ALL = 0x00000003;

public const int SC_ENUM_PROCESS_INFO = 0;
public const uint INFO_LEVEL_STANDARD = 0;

public const uint SERVICE_ERROR_NORMAL = 0x00000001;
public const uint SERVICE_AUTO_START = 0x00000002;
Expand All @@ -16,32 +21,56 @@ public static class WinService
public const uint SERVICE_STOP = 0x00000020;
public const uint SERVICE_WIN32 = 0x00000030;
public const uint SERVICE_ALL_ACCESS = 0xF01FF;

public const uint DELETE = 0x00010000;

[DllImport(Libraries.Advapi32, SetLastError = true)]
public static extern IntPtr OpenSCManager(
string lpMachineName,
string lpDatabaseName,
int dwDesiredAccess);
public enum ServiceCurrentState : uint
{
SERVICE_STOPPED = 0x00000001,
SERVICE_START_PENDING = 0x00000002,
SERVICE_STOP_PENDING = 0x00000003,
SERVICE_RUNNING = 0x00000004,
SERVICE_CONTINUE_PENDING = 0x00000005,
SERVICE_PAUSE_PENDING = 0x00000006,
SERVICE_PAUSED = 0x00000007
}

public const uint DELETE = 0x00010000;

[DllImport(Libraries.Advapi32, SetLastError = true)]
public static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
int dwDesiredAccess);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct ENUM_SERVICE_STATUS_PROCESS
{
public string lpServiceName;
public string lpDisplayName;
public int dwServiceType;
public int dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
public int dwProcessId;
public int dwServiceFlags;
}

[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public int dwServiceType;
public int dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
public int dwProcessId;
public int dwServiceFlags;
}

[DllImport(Libraries.Advapi32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool QueryServiceStatusEx(
IntPtr hService,
int InfoLevel,
IntPtr lpBuffer,
uint cbBufSize,
out uint pcbBytesNeeded);

public static extern bool CloseServiceHandle(IntPtr hSCObject);

[DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr CreateService(
public static extern IntPtr CreateService(
IntPtr hSCManager,
string lpServiceName,
string lpDisplayName,
Expand All @@ -58,23 +87,40 @@ private static extern IntPtr CreateService(

[DllImport(Libraries.Advapi32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteService(IntPtr hService);
public static extern bool DeleteService(IntPtr hService);

[DllImport(Libraries.Advapi32, SetLastError = true)]
[DllImport(Libraries.Advapi32, EntryPoint = "EnumServicesStatusExW", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseServiceHandle(IntPtr hSCObject);
public static extern bool EnumServicesStatusEx(
IntPtr hSCManager,
uint InfoLevel,
uint dwServiceType,
uint dwServiceState,
IntPtr lpServices,
uint cbBufSize,
out uint pcbBytesNeeded,
out uint lpServicesReturned,
ref uint lpResumeHandle,
string pszGroupName);

[DllImport(Libraries.Advapi32, SetLastError = true)]
public static extern IntPtr OpenSCManager(
string lpMachineName,
string lpDatabaseName,
int dwDesiredAccess);

[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public int dwServiceType;
public int dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificExitCode;
public int dwCheckPoint;
public int dwWaitHint;
public int dwProcessId;
public int dwServiceFlags;
}
[DllImport(Libraries.Advapi32, SetLastError = true)]
public static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
int dwDesiredAccess);

[DllImport(Libraries.Advapi32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool QueryServiceStatusEx(
IntPtr hService,
int InfoLevel,
IntPtr lpBuffer,
uint cbBufSize,
out uint pcbBytesNeeded);
}
11 changes: 9 additions & 2 deletions src/Task.Monitor.System/PInvokeErrorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ public static void AssertOnLastError(string methodName)
{
#if DEBUG
int error = Marshal.GetLastPInvokeError();
Debug.Assert(error == 0, $"Failed {methodName}: {Marshal.GetPInvokeErrorMessage(error)}");
Debug.Assert(error == 0, $"Failed with {GetFormattedErrorMesage()}");
#endif
}
}

public static string GetFormattedErrorMesage()
{
int error = Marshal.GetLastPInvokeError();
string message = Marshal.GetPInvokeErrorMessage(error);
return $"PInvoke error ({error}): {message}";
}
}
10 changes: 5 additions & 5 deletions src/Task.Monitor.System/Process/ProcessService.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public sealed partial class ProcessService
processInfo.ProcessName);

processInfo.ModuleName = Path.GetFileName(entry.szExeFile);
processInfo.IsDaemon = ServiceUtils.GetService((int)entry.th32ProcessID, out ServiceController? _);
processInfo.IsDaemon = ServiceUtils.GetService((int)entry.th32ProcessID, out ServiceInfo? _);
processInfo.IsLowPriority = entry.pcPriClassBase < 8;
processInfo.UserName = GetProcessUserName(processHandle);
processInfo.CmdLine = GetProcessCommandLine((int)entry.th32ProcessID, processInfo.FileName);
Expand Down Expand Up @@ -84,8 +84,8 @@ public sealed partial class ProcessService
private static string GetProcessCommandLine(int pid, in string defaultValue)
{
try {
if (ServiceUtils.GetService(pid, out ServiceController? serviceController)) {
string imagePath = ServiceUtils.GetServiceImagePath(serviceController!.ServiceName) ?? defaultValue;
if (ServiceUtils.GetService(pid, out ServiceInfo? serviceInfo)) {
string imagePath = ServiceUtils.GetServiceImagePath(serviceInfo!.ServiceName) ?? defaultValue;
return Environment.ExpandEnvironmentVariables(imagePath);
}

Expand Down Expand Up @@ -240,8 +240,8 @@ private static string GetProcessProductName(
string processPath,
string defaultValue)
{
if (ServiceUtils.GetService((int)pid, out ServiceController? serviceController)) {
return serviceController?.DisplayName ?? defaultValue;
if (ServiceUtils.GetService((int)pid, out ServiceInfo? serviceInfo)) {
return serviceInfo?.DisplayName ?? defaultValue;
}

if (string.IsNullOrWhiteSpace(processPath)) {
Expand Down
7 changes: 7 additions & 0 deletions src/Task.Monitor.System/Process/ServiceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Task.Monitor.System.Process;

public sealed class ServiceInfo
{
public string ServiceName { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
}
111 changes: 99 additions & 12 deletions src/Task.Monitor.System/Process/ServiceUtils.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using Microsoft.Win32;
using Task.Monitor.Interop.Win32;
Expand All @@ -10,7 +12,7 @@ namespace Task.Monitor.System.Process;
public static partial class ServiceUtils
{
#if __WIN32__
private static readonly Dictionary<int, ServiceController> serviceMap = new();
private static readonly Dictionary<int, ServiceInfo> serviceMap = new();
private static readonly Lock criticalSection = new();

private static void BuildServiceMap()
Expand All @@ -20,13 +22,14 @@ private static void BuildServiceMap()
IntPtr hSCM = WinService.OpenSCManager(null!, null!, WinService.SC_MANAGER_CONNECT);

if (hSCM == IntPtr.Zero) {
Trace.WriteLine($"Failed to open SCM Manager: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
return;
}

ServiceController[] services = ServiceController.GetServices();
ServiceInfo[] services = GetServices();

for (int i = 0; i < services.Length; i++) {
ServiceController service = services[i];
ServiceInfo service = services[i];

IntPtr hService = WinService.OpenService(
hSCM,
Expand All @@ -49,7 +52,7 @@ private static void BuildServiceMap()
WinService.CloseServiceHandle(hSCM);
}

public static bool GetService(int pid, out ServiceController? service)
public static bool GetService(int pid, out ServiceInfo? service)
{
lock (criticalSection) {
if (serviceMap.Count == 0) {
Expand Down Expand Up @@ -83,26 +86,110 @@ private static int GetServiceProcessId(IntPtr hService)
pss,
(uint)Marshal.SizeOf<WinService.SERVICE_STATUS_PROCESS>(),
out _)) {


Trace.WriteLine($"GetServiceProcessId QueryServiceStatusEx: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
return 0;
}

var ssp = Marshal.PtrToStructure<WinService.SERVICE_STATUS_PROCESS>(pss);

if (ssp.dwCurrentState == (int)ServiceControllerStatus.Running ||
ssp.dwCurrentState == (int)ServiceControllerStatus.PausePending ||
ssp.dwCurrentState == (int)ServiceControllerStatus.Paused ||
ssp.dwCurrentState == (int)ServiceControllerStatus.StartPending ||
ssp.dwCurrentState == (int)ServiceControllerStatus.StopPending) {
if (ssp.dwCurrentState == (int)WinService.ServiceCurrentState.SERVICE_RUNNING ||
ssp.dwCurrentState == (int)WinService.ServiceCurrentState.SERVICE_PAUSE_PENDING ||
ssp.dwCurrentState == (int)WinService.ServiceCurrentState.SERVICE_PAUSED ||
ssp.dwCurrentState == (int)WinService.ServiceCurrentState.SERVICE_START_PENDING ||
ssp.dwCurrentState == (int)WinService.ServiceCurrentState.SERVICE_STOP_PENDING) {

pid = ssp.dwProcessId;
}

Marshal.FreeHGlobal(pss);
return pid;
}

internal static ServiceInfo[] GetServices()
{
ServiceInfo[] services = [];
IntPtr hSCM = WinService.OpenSCManager(null!, null!, WinService.SC_MANAGER_ENUMERATE_SERVICE);

if (hSCM == IntPtr.Zero) {
Trace.WriteLine($"Failed to open SCM Manager: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
return services;
}

IntPtr buffer = IntPtr.Zero;

uint bytesNeeded = 0;
uint servicesReturned = 0;
uint resumeHandle = 0;

WinService.EnumServicesStatusEx(
hSCM,
WinService.INFO_LEVEL_STANDARD,
WinService.SERVICE_TYPE_ALL,
WinService.SERVICE_STATE_ALL,
IntPtr.Zero,
0,
out bytesNeeded,
out servicesReturned,
ref resumeHandle,
null!);

if (bytesNeeded == 0) {
Trace.WriteLine($"EnumServicesStatusEx bytesNeeded returned 0: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
WinService.CloseServiceHandle(hSCM);
return services;
}

buffer = Marshal.AllocHGlobal((IntPtr)bytesNeeded);
resumeHandle = 0;

bool result = WinService.EnumServicesStatusEx(
hSCM,
WinService.INFO_LEVEL_STANDARD,
WinService.SERVICE_TYPE_ALL,
WinService.SERVICE_STATE_ALL,
buffer,
bytesNeeded,
out bytesNeeded,
out servicesReturned,
ref resumeHandle,
null!);

if (!result) {
Trace.WriteLine($"Failed to EnumServicesStatusEx: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");

if (buffer != IntPtr.Zero) {
Marshal.FreeHGlobal(buffer);
}

WinService.CloseServiceHandle(hSCM);
return services;
}

IntPtr currentPtr = buffer;
int structSize = Marshal.SizeOf<WinService.ENUM_SERVICE_STATUS_PROCESS>();
services = new ServiceInfo[servicesReturned];

for (int i = 0; i < servicesReturned; i++) {
var status = Marshal.PtrToStructure<WinService.ENUM_SERVICE_STATUS_PROCESS>(currentPtr);

services[i] = new ServiceInfo() {
ServiceName = status.lpServiceName,
DisplayName = status.lpDisplayName
};

currentPtr = IntPtr.Add(currentPtr, structSize);
}

if (buffer != IntPtr.Zero) {
Marshal.FreeHGlobal(buffer);
}

WinService.CloseServiceHandle(hSCM);
return services;
}

public static bool IsService(int pid) => ServiceUtils.GetService(pid, out ServiceController? _);
public static bool IsService(int pid) => ServiceUtils.GetService(pid, out ServiceInfo? _);
#endif
}

Expand Down
6 changes: 4 additions & 2 deletions src/Task.Monitor.System/SystemTerminal.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,27 @@ private bool CursorVisibleInternal

private void EnableAnsiTerminalCodesInternal()
{
// Not all terminals on Windows platforms (cmd.exe as an example) support ANSI escape codes.
// Not all terminals on Windows platforms (conhost.exe as an example) support ANSI escape codes.
// We attempt to enable here to support older terminals.

IntPtr consoleHandle = ProcessEnv.GetStdHandle(ProcessEnv.STD_OUTPUT_HANDLE);

if (consoleHandle == IntPtr.Zero || consoleHandle == new IntPtr(-1)) {
PInvokeErrorHelpers.AssertOnLastError(nameof(ProcessEnv.GetStdHandle));
Trace.WriteLine($"EnableAnsiTerminalCodesInternal GetStdHandle: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
return;
}

if (!ConsoleApi.GetConsoleMode(consoleHandle, out uint originalMode)) {
PInvokeErrorHelpers.AssertOnLastError(nameof(ConsoleApi.GetConsoleMode));
Trace.WriteLine($"EnableAnsiTerminalCodesInternal GetConsoleMode: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
return;
}

uint newMode = originalMode | ConsoleApi.ENABLE_VIRTUAL_TERMINAL_PROCESSING;

if (!ConsoleApi.SetConsoleMode(consoleHandle, newMode)) {
PInvokeErrorHelpers.AssertOnLastError(nameof(ConsoleApi.SetConsoleMode));
Trace.WriteLine($"EnableAnsiTerminalCodesInternal SetConsoleMode: {PInvokeErrorHelpers.GetFormattedErrorMesage()}");
}
}
#endif
Expand Down
Loading
Loading