From f9fc7dd2e1ae8043ebf38f2c9e8d9bb85fadd727 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 26 May 2026 17:07:52 -0400 Subject: [PATCH] feat(host): wire UseWindowsService so sc.exe-installed service runs cleanly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2 plan's blessed install path (scripts/install/Install-Services.ps1) registers the host via `sc.exe create binPath=...OtOpcUa.Host.exe`, but the binary never called `UseWindowsService`. Without it, the Service Control Manager waits ~30s for the process to call SetServiceStatus(Running) and then kills it — the install script's design was incomplete. Two changes: - Host.csproj: drop the `IsOSPlatform('Windows')` condition on the Microsoft.Extensions.Hosting.WindowsServices package reference so the package is always available. The runtime helper used by UseWindowsService gates on WindowsServiceHelpers.IsWindowsService() internally, so it's a no-op when running as a console app or under Linux/macOS — the binary stays cross-platform-buildable. - Program.cs: call builder.Host.UseWindowsService(options => options.ServiceName = "OtOpcUaHost") immediately after CreateBuilder. When the host is launched by SCM, WindowsServiceLifetime takes over the IHostLifetime slot and reports START/STOP correctly. When launched by `dotnet run` or `OtOpcUa.Host.exe` from a console, it's a no-op. Verified end-to-end on wonder-app-vd03.zmr.zimmer.com: `sc.exe create` followed by `sc.exe start OtOpcUaHost` transitions from START_PENDING to RUNNING; /login + /health/ready + /health/active all return 200; service survives SSH session close and auto-starts on boot per the AUTO_START flag set by the installer script. --- src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs | 6 ++++++ .../ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs index 06af6df..d627f2d 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs @@ -33,6 +33,12 @@ var builder = WebApplication.CreateBuilder(args); // regardless of ASPNETCORE_ENVIRONMENT. builder.WebHost.UseStaticWebAssets(); +// Windows Service support: when the EXE is started by Service Control Manager (sc.exe), +// the host needs to call SetServiceStatus to keep the SCM happy. UseWindowsService() +// installs the WindowsServiceLifetime IFF WindowsServiceHelpers.IsWindowsService() is +// true at runtime — so it's safely a no-op when running as a console app or on Linux. +builder.Host.UseWindowsService(options => options.ServiceName = "OtOpcUaHost"); + // Per-role appsettings overlay: appsettings.{role}.json (single role) or appsettings.admin-driver.json // (both). Optional — base appsettings.json carries enough to boot if these don't exist. var roleSuffix = roles.Length == 0 ? null : string.Join('-', roles.OrderBy(r => r, StringComparer.Ordinal)); diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj b/src/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj index 3b7e33b..bee4115 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj @@ -16,7 +16,11 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive