using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; namespace HikvisionAttendanceService; internal static class Program { [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); private static bool ArgsContainFlag(string[] args, string flag) => args.Any(a => string.Equals(a, flag, StringComparison.OrdinalIgnoreCase)); private static bool ArgsContainTestFlag(string[] args) => ArgsContainFlag(args, "--test"); private static bool ShouldRunAsConsole(string[] args) { if (ArgsContainFlag(args, "--console") || ArgsContainFlag(args, "--debug")) return true; // F5 / Debug from Visual Studio / Cursor: run interactively instead of ServiceBase. if (Debugger.IsAttached) return true; // Double-click / direct run of exe outside SCM. if (Environment.UserInteractive) return true; return false; } private static async Task Main(string[] args) { if (ArgsContainTestFlag(args)) { Console.WriteLine("Program entry (test): rawArgCount=" + args.Length + " rawArgs=" + string.Join(" ", args)); await AttendanceTestMode.RunAsync(args).ConfigureAwait(false); return; } if (ShouldRunAsConsole(args)) { await RunConsoleHostAsync(args).ConfigureAwait(false); return; } ServiceBase.Run(new ServiceBase[] { new HikvisionAttendanceWindowsService() }); } /// /// Same runtime path as the Windows Service OnStart/OnStop, but runnable under debugger (F5). /// Press Ctrl+C (or Stop Debugging) to shut down cleanly. /// private static async Task RunConsoleHostAsync(string[] args) { try { // Make console output visible when debugging a WinExe project. if (Environment.UserInteractive) AllocConsole(); } catch { // ignore: console may already exist } Environment.CurrentDirectory = AppContext.BaseDirectory; var baseDir = AppContext.BaseDirectory; var configPath = Path.Combine(baseDir, "serviceconfig.json"); if (!File.Exists(configPath)) configPath = Path.Combine(baseDir, "appsettings.json"); var config = HikvisionAttendanceWindowsService.HikvisionServiceConfig.Load(configPath); var logger = new HikvisionAttendanceWindowsService.FileLogger(config.LogDirectory); Console.WriteLine("HikvisionAttendanceService console/debug host starting..."); Console.WriteLine("Config: " + configPath); Console.WriteLine("LogDirectory: " + config.LogDirectory); Console.WriteLine("Press Ctrl+C to stop."); logger.Ops(OpsMarkers.Service, "CONSOLE HOST START time=" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " config=\"" + configPath + "\" debuggerAttached=" + Debugger.IsAttached); using var cts = new CancellationTokenSource(); Console.CancelKeyPress += (_, e) => { e.Cancel = true; Console.WriteLine("Shutdown requested..."); logger.Ops(OpsMarkers.Service, "STOP REQUESTED reason=\"Ctrl+C\""); cts.Cancel(); }; using var manager = new HikvisionAttendanceManager(config, logger); try { await manager.RunAsync(cts.Token).ConfigureAwait(false); } catch (OperationCanceledException) { // expected on stop } catch (Exception ex) { logger.Error("Console/debug host failed", ex); logger.OpsError(OpsMarkers.Service, "CONSOLE HOST FAILED reason=\"" + ex.Message + "\""); Console.WriteLine("FATAL: " + ex.Message); throw; } logger.Ops(OpsMarkers.Service, "CONSOLE HOST END"); Console.WriteLine("HikvisionAttendanceService stopped."); } }