From a136fe587c4f7993b1fbeaf9b107ef4b5c8ef3aa Mon Sep 17 00:00:00 2001 From: "mustafa.ahmed" Date: Thu, 30 Jul 2026 13:18:52 +0500 Subject: [PATCH] Add console/debug host so F5 runs without Windows Service SCM Support --console / debugger-attached interactive hosting with Ctrl+C shutdown, and launch profiles that pass --console. --- Program.cs | 102 +++++++++++++++++++++++++++++++-- Properties/launchSettings.json | 9 +++ 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/Program.cs b/Program.cs index 6069bf1..a47457a 100644 --- a/Program.cs +++ b/Program.cs @@ -1,30 +1,122 @@ 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) => - args.Any(a => string.Equals(a, "--test", StringComparison.OrdinalIgnoreCase)); + 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) { - // Console so mis-invocation is visible even before the daily log file is opened. - if (ArgsContainTestFlag(args)) - Console.WriteLine("Program entry (test): rawArgCount=" + args.Length + " rawArgs=" + string.Join(" ", 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."); + } } diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 452d107..4f8aec1 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -1,8 +1,17 @@ { "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { + "HikvisionAttendanceService (Debug Console)": { + "commandName": "Project", + "commandLineArgs": "--console", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + }, "HikvisionAttendanceService": { "commandName": "Project", + "commandLineArgs": "--console", "dotnetRunMessages": true, "environmentVariables": { "DOTNET_ENVIRONMENT": "Development"