You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rdpwrap/src-csharp/RDPWrap.Common/ServiceHelper.cs

220 lines
9.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright 2024 sjackson0109 — Apache License 2.0
using System.Runtime.InteropServices;
namespace RDPWrap.Common;
/// <summary>
/// Wrappers around the Windows Service Control Manager API, mirroring the
/// SvcGetStart / SvcConfigStart / SvcStart / CheckTermsrvProcess helpers
/// in RDPWInst.dpr and GetTermSrvState in RDPConf MainUnit.pas.
/// </summary>
public static class ServiceHelper
{
// ── Start-type query / change ─────────────────────────────────────────────
/// <summary>
/// Returns the configured start type of <paramref name="serviceName"/>
/// (e.g. <c>SERVICE_AUTO_START</c>) or <c>-1</c> on failure.
/// </summary>
public static int GetStartType(string serviceName)
{
var hSC = NativeMethods.OpenSCManager(null, null, NativeMethods.SC_MANAGER_CONNECT);
if (hSC == IntPtr.Zero) return -1;
try
{
var hSvc = NativeMethods.OpenService(hSC, serviceName,
NativeMethods.SERVICE_QUERY_CONFIG);
if (hSvc == IntPtr.Zero) return -1;
try
{
NativeMethods.QueryServiceConfig(hSvc, IntPtr.Zero, 0, out uint needed);
var buf = Marshal.AllocHGlobal((int)needed);
try
{
if (!NativeMethods.QueryServiceConfig(hSvc, buf, needed, out _))
return -1;
// dwStartType is the second DWORD in QUERY_SERVICE_CONFIG
return Marshal.ReadInt32(buf, 4);
}
finally { Marshal.FreeHGlobal(buf); }
}
finally { NativeMethods.CloseServiceHandle(hSvc); }
}
finally { NativeMethods.CloseServiceHandle(hSC); }
}
/// <summary>
/// Changes the start type of <paramref name="serviceName"/> to
/// <paramref name="startType"/> (one of the <c>SERVICE_*_START</c> constants).
/// </summary>
public static bool SetStartType(string serviceName, uint startType)
{
var hSC = NativeMethods.OpenSCManager(null, null, NativeMethods.SC_MANAGER_CONNECT);
if (hSC == IntPtr.Zero) return false;
try
{
var hSvc = NativeMethods.OpenService(hSC, serviceName,
NativeMethods.SERVICE_CHANGE_CONFIG);
if (hSvc == IntPtr.Zero) return false;
try
{
return NativeMethods.ChangeServiceConfig(hSvc,
NativeMethods.SERVICE_NO_CHANGE, startType,
NativeMethods.SERVICE_NO_CHANGE,
null, null, IntPtr.Zero, null, null, null, null);
}
finally { NativeMethods.CloseServiceHandle(hSvc); }
}
finally { NativeMethods.CloseServiceHandle(hSC); }
}
// ── Start a service ───────────────────────────────────────────────────────
/// <summary>
/// Starts <paramref name="serviceName"/>. If the service is already
/// running (error 1056) it waits 2 s and retries once, matching the
/// original Delphi SvcStart behaviour.
/// Returns <c>true</c> on success.
/// </summary>
public static bool StartService(string serviceName)
{
var hSC = NativeMethods.OpenSCManager(null, null, NativeMethods.SC_MANAGER_CONNECT);
if (hSC == IntPtr.Zero) return false;
try
{
var hSvc = NativeMethods.OpenService(hSC, serviceName,
NativeMethods.SERVICE_START);
if (hSvc == IntPtr.Zero) return false;
try
{
if (NativeMethods.StartService(hSvc, 0, null)) return true;
var err = Marshal.GetLastWin32Error();
if (err == (int)NativeMethods.ERROR_SERVICE_ALREADY_RUNNING)
{
Thread.Sleep(2000);
return NativeMethods.StartService(hSvc, 0, null);
}
return false;
}
finally { NativeMethods.CloseServiceHandle(hSvc); }
}
finally { NativeMethods.CloseServiceHandle(hSC); }
}
// ── Status query ──────────────────────────────────────────────────────────
/// <summary>
/// Returns the <c>dwCurrentState</c> for <paramref name="serviceName"/>
/// (e.g. <c>SERVICE_RUNNING = 4</c>) or <c>-1</c> on failure.
/// </summary>
public static int GetCurrentState(string serviceName)
{
var hSC = NativeMethods.OpenSCManager(null, null, NativeMethods.SC_MANAGER_CONNECT);
if (hSC == IntPtr.Zero) return -1;
try
{
var hSvc = NativeMethods.OpenService(hSC, serviceName,
NativeMethods.SERVICE_QUERY_STATUS);
if (hSvc == IntPtr.Zero) return -1;
try
{
NativeMethods.QueryServiceStatusEx(hSvc,
NativeMethods.SC_STATUS_PROCESS_INFO,
IntPtr.Zero, 0, out uint needed);
var buf = Marshal.AllocHGlobal((int)needed);
try
{
if (!NativeMethods.QueryServiceStatusEx(hSvc,
NativeMethods.SC_STATUS_PROCESS_INFO,
buf, needed, out _)) return -1;
// dwCurrentState is the second DWORD in SERVICE_STATUS_PROCESS
return Marshal.ReadInt32(buf, 4);
}
finally { Marshal.FreeHGlobal(buf); }
}
finally { NativeMethods.CloseServiceHandle(hSvc); }
}
finally { NativeMethods.CloseServiceHandle(hSC); }
}
// ── Process-info enumeration ──────────────────────────────────────────────
/// <summary>
/// Represents the name and PID of a service process returned by
/// <see cref="EnumServiceProcesses"/>.
/// </summary>
public record ServiceProcessInfo(string ServiceName, string DisplayName, uint ProcessId, uint CurrentState);
/// <summary>
/// Enumerates all Win32 services (all states) and returns their
/// process information. Mirrors the EnumServicesStatusEx loop in
/// CheckTermsrvProcess.
/// </summary>
public static IReadOnlyList<ServiceProcessInfo> EnumServiceProcesses()
{
var result = new List<ServiceProcessInfo>();
var hSC = NativeMethods.OpenSCManager(null, null,
NativeMethods.SC_MANAGER_CONNECT | NativeMethods.SC_MANAGER_ENUMERATE_SERVICE);
if (hSC == IntPtr.Zero) return result;
try
{
uint resumeHandle = 0;
// Ask for the required buffer size
NativeMethods.EnumServicesStatusEx(hSC,
NativeMethods.SC_ENUM_PROCESS_INFO,
NativeMethods.SERVICE_WIN32,
NativeMethods.SERVICE_STATE_ALL,
IntPtr.Zero, 0,
out uint needed, out _, ref resumeHandle, null);
if (needed == 0) return result;
var buf = Marshal.AllocHGlobal((int)needed);
try
{
resumeHandle = 0;
if (!NativeMethods.EnumServicesStatusEx(hSC,
NativeMethods.SC_ENUM_PROCESS_INFO,
NativeMethods.SERVICE_WIN32,
NativeMethods.SERVICE_STATE_ALL,
buf, needed,
out _, out uint returned, ref resumeHandle, null))
return result;
// ENUM_SERVICE_STATUS_PROCESS layout (Unicode, packed):
// IntPtr lpServiceName (pointer to string)
// IntPtr lpDisplayName (pointer to string)
// SERVICE_STATUS_PROCESS (9 × DWORD = 36 bytes)
int ptrSize = IntPtr.Size;
int entrySize = ptrSize * 2 + 36; // 2 string pointers + status struct
for (int i = 0; i < (int)returned; i++)
{
var entryPtr = buf + i * entrySize;
var namePtr = Marshal.ReadIntPtr(entryPtr);
var dispPtr = Marshal.ReadIntPtr(entryPtr + ptrSize);
var svcName = Marshal.PtrToStringUni(namePtr) ?? string.Empty;
var dispName = Marshal.PtrToStringUni(dispPtr) ?? string.Empty;
// dwCurrentState at offset 4, dwProcessId at offset 28
var statusBase = entryPtr + ptrSize * 2;
uint currentState = (uint)Marshal.ReadInt32(statusBase + 4);
uint pid = (uint)Marshal.ReadInt32(statusBase + 28);
result.Add(new ServiceProcessInfo(svcName, dispName, pid, currentState));
}
}
finally { Marshal.FreeHGlobal(buf); }
}
finally { NativeMethods.CloseServiceHandle(hSC); }
return result;
}
}