Last active
March 20, 2025 18:23
-
-
Save jbevain/bee178901b00d6d6538a47ea520d73e1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// dotnet publish | |
// sc create TempWatcher binPath= "C:\tmp\WatchTemp\bin\Release\net9.0\win-x64\publish\WatchTemp.exe" | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
class Service : IDisposable | |
{ | |
public static string Name = "TempWatcher"; | |
const string path = @"c:\temp"; | |
private readonly ManualResetEventSlim _stopEvent = new(initialState: false); | |
private readonly FileSystemWatcher _watcher; | |
public Service() | |
{ | |
if (Directory.Exists(path)) | |
{ | |
Delete(path); | |
} | |
_watcher = new FileSystemWatcher(path[0..^4]) | |
{ | |
NotifyFilter = NotifyFilters.DirectoryName, | |
IncludeSubdirectories = false, | |
}; | |
_watcher.Created += (sender, e) => | |
{ | |
if (!e.FullPath.Equals(path, StringComparison.OrdinalIgnoreCase)) | |
{ | |
return; | |
} | |
while (true) | |
{ | |
if (Delete(e.FullPath)) | |
{ | |
return; | |
} | |
else | |
{ | |
Thread.Sleep(1000); | |
} | |
} | |
}; | |
} | |
public void Run() | |
{ | |
_watcher.EnableRaisingEvents = true; | |
_stopEvent.Wait(); | |
_watcher.EnableRaisingEvents = false; | |
} | |
private static bool Delete(string path) | |
{ | |
try | |
{ | |
Directory.Delete(path, recursive: true); | |
return true; | |
} | |
catch | |
{ | |
return false; | |
} | |
} | |
public void Dispose() | |
{ | |
_stopEvent.Set(); | |
_watcher.Dispose(); | |
} | |
} | |
unsafe class ServiceRunner | |
{ | |
private static Service? _service; | |
private static ServiceStatus _status; | |
private static nint _handle; | |
private static char* _serviceName; | |
public static int Main(string[] args) | |
{ | |
_service = new Service(); | |
_serviceName = (char*)Marshal.StringToHGlobalUni(Service.Name); | |
ServiceTableEntry* serviceTable = stackalloc ServiceTableEntry[] | |
{ | |
new() | |
{ | |
lpServiceName = _serviceName, | |
lpServiceProc = &ServiceMain | |
}, | |
default | |
}; | |
if (!StartServiceCtrlDispatcherW(serviceTable)) | |
{ | |
return GetLastError(); | |
} | |
return 0; | |
} | |
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] | |
private static void ServiceMain(int argc, char** argv) | |
{ | |
_handle = RegisterServiceCtrlHandlerW(_serviceName, &ServiceControlHandler); | |
_status.dwServiceType = ServiceType.OwnProcess; | |
_status.dwServiceSpecificExitCode = 0; | |
ReportServiceStatus(ServiceState.StartPending, 3000); | |
_service = new Service(); | |
ReportServiceStatus(ServiceState.Running, 0); | |
_service.Run(); | |
} | |
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })] | |
private static void ServiceControlHandler(ServiceControl control) | |
{ | |
switch (control) | |
{ | |
case ServiceControl.Stop: | |
case ServiceControl.Shutdown: | |
ReportServiceStatus(ServiceState.StopPending, 0); | |
try | |
{ | |
_service?.Dispose(); | |
} | |
catch | |
{ | |
} | |
ReportServiceStatus(ServiceState.Stopped, 0); | |
break; | |
} | |
} | |
private static void ReportServiceStatus(ServiceState state, int waitHint) | |
{ | |
_status.dwCurrentState = state; | |
_status.dwWin32ExitCode = 0; | |
_status.dwWaitHint = waitHint; | |
if (state == ServiceState.StartPending) | |
{ | |
_status.dwControlsAccepted = 0; | |
} | |
else | |
{ | |
_status.dwControlsAccepted = ServiceAccept.Stop | ServiceAccept.Shutdown; | |
} | |
if (state is ServiceState.Running or ServiceState.Stopped) | |
{ | |
_status.dwCheckPoint = 0; | |
} | |
else | |
{ | |
_status.dwCheckPoint++; | |
} | |
SetServiceStatus(_handle, ref _status); | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct ServiceTableEntry | |
{ | |
public char* lpServiceName; | |
public delegate* unmanaged[Stdcall]<int, char**, void> lpServiceProc; | |
} | |
private enum ServiceControl : int | |
{ | |
Stop = 0x00000001, | |
Shutdown = 0x00000005, | |
} | |
[Flags] | |
private enum ServiceType : int | |
{ | |
OwnProcess = 0x00000010 | |
} | |
private enum ServiceState : int | |
{ | |
Stopped = 0x00000001, | |
StartPending = 0x00000002, | |
StopPending = 0x00000003, | |
Running = 0x00000004 | |
} | |
[Flags] | |
private enum ServiceAccept : int | |
{ | |
Stop = 0x00000001, | |
Shutdown = 0x00000004 | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct ServiceStatus | |
{ | |
public ServiceType dwServiceType; | |
public ServiceState dwCurrentState; | |
public ServiceAccept dwControlsAccepted; | |
public int dwWin32ExitCode; | |
public int dwServiceSpecificExitCode; | |
public int dwCheckPoint; | |
public int dwWaitHint; | |
} | |
[DllImport("kernel32.dll", SetLastError = true)] | |
private static extern int GetLastError(); | |
[DllImport("advapi32.dll", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static extern bool StartServiceCtrlDispatcherW(ServiceTableEntry* serviceTable); | |
[DllImport("advapi32.dll", SetLastError = true)] | |
private static extern nint RegisterServiceCtrlHandlerW(char* serviceName, delegate* unmanaged[Stdcall]<ServiceControl, void> serviceControlHandler); | |
[DllImport("advapi32.dll", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static extern bool SetServiceStatus(nint serviceStatusHandle, ref ServiceStatus serviceStatus); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment