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.main
parent
672fa2a89d
commit
a136fe587c
102
Program.cs
102
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()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same runtime path as the Windows Service OnStart/OnStop, but runnable under debugger (F5).
|
||||
/// Press Ctrl+C (or Stop Debugging) to shut down cleanly.
|
||||
/// </summary>
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue