Skip to content

Instantly share code, notes, and snippets.

@jbevain
Last active March 20, 2025 18:23
Show Gist options
  • Save jbevain/bee178901b00d6d6538a47ea520d73e1 to your computer and use it in GitHub Desktop.
Save jbevain/bee178901b00d6d6538a47ea520d73e1 to your computer and use it in GitHub Desktop.
// 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