From 1a62332f1944615177c7f94a13d751b930db57bb Mon Sep 17 00:00:00 2001 From: JamBalaya56562 Date: Sun, 12 Jul 2026 21:38:36 +0900 Subject: [PATCH 1/2] Delay Windows service startup --- Duplicati/WindowsService/Program.cs | 2 +- Duplicati/WindowsService/ServiceInstaller.cs | 48 ++++++++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/Duplicati/WindowsService/Program.cs b/Duplicati/WindowsService/Program.cs index 89110c94d..a5c3ee496 100644 --- a/Duplicati/WindowsService/Program.cs +++ b/Duplicati/WindowsService/Program.cs @@ -111,7 +111,7 @@ namespace Duplicati.WindowsService //The path can also include arguments for an auto-start service. For example, "d:\myshare\myservice.exe arg1 arg2". These arguments are passed to the service entry point (typically the main function). try { - ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME, ServiceControl.DISPLAY_NAME, ServiceControl.SERVICE_DESCRIPTION, "\"" + selfexec + "\"" + " SERVER " + commandline); + ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME, ServiceControl.DISPLAY_NAME, ServiceControl.SERVICE_DESCRIPTION, "\"" + selfexec + "\"" + " SERVER " + commandline, delayedAutoStart: true); Console.WriteLine("Duplicati service installation succeeded."); if (!args.Any(x => string.Equals("install-only", x, StringComparison.OrdinalIgnoreCase))) { diff --git a/Duplicati/WindowsService/ServiceInstaller.cs b/Duplicati/WindowsService/ServiceInstaller.cs index b3c92734f..4312549bc 100644 --- a/Duplicati/WindowsService/ServiceInstaller.cs +++ b/Duplicati/WindowsService/ServiceInstaller.cs @@ -47,17 +47,29 @@ namespace Duplicati.WindowsService private static extern bool DeleteService(IntPtr hService); private const int SERVICE_CONFIG_DESCRIPTION = 0x01; + private const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 0x03; [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, [MarshalAs(UnmanagedType.Struct)] ref SERVICE_DESCRIPTION lpInfo); + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, ref SERVICE_DELAYED_AUTO_START_INFO lpInfo); + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SERVICE_DESCRIPTION { public string lpDescription; } + [StructLayout(LayoutKind.Sequential)] + private struct SERVICE_DELAYED_AUTO_START_INFO + { + [MarshalAs(UnmanagedType.Bool)] + public bool fDelayedAutostart; + } + private enum SC_STATUS_TYPE : int { SC_STATUS_PROCESS_INFO = 0 @@ -446,7 +458,7 @@ namespace Duplicati.WindowsService GENERIC_ALL = SC_MANAGER_ALL_ACCESS, } - public static void InstallService(string ServiceName, string DisplayName, string Description, string Path) + public static void InstallService(string ServiceName, string DisplayName, string Description, string Path, bool delayedAutoStart = false) { var scMgrHandle = OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); @@ -461,16 +473,36 @@ namespace Duplicati.WindowsService if (serviceHandle == IntPtr.Zero) throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during install service (CreateService)"); - var pinfo = new SERVICE_DESCRIPTION + try { - lpDescription = Description - }; + var pinfo = new SERVICE_DESCRIPTION + { + lpDescription = Description + }; - var res = ChangeServiceConfig2(serviceHandle, SERVICE_CONFIG_DESCRIPTION, ref pinfo); - if (!res) - System.Diagnostics.Trace.WriteLine($"Failed to set decription: {Marshal.GetLastWin32Error().ToString()}"); + var res = ChangeServiceConfig2(serviceHandle, SERVICE_CONFIG_DESCRIPTION, ref pinfo); + if (!res) + System.Diagnostics.Trace.WriteLine($"Failed to set description: {Marshal.GetLastWin32Error().ToString()}"); - CloseServiceHandle(serviceHandle); + if (delayedAutoStart) + { + var delayedInfo = new SERVICE_DELAYED_AUTO_START_INFO + { + fDelayedAutostart = true + }; + + if (!ChangeServiceConfig2(serviceHandle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, ref delayedInfo)) + { + var error = Marshal.GetLastWin32Error(); + DeleteService(serviceHandle); + throw new Exception($"Win32 error {error.ToString()} during install service (ChangeServiceConfig2 delayed auto-start)"); + } + } + } + finally + { + CloseServiceHandle(serviceHandle); + } } finally { From 445297dfc0e6785c729ea3fef8265fe625080fe6 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Sun, 12 Jul 2026 19:27:46 +0200 Subject: [PATCH 2/2] Avoid default parameters and make the agent load delayed as well --- Duplicati/WindowsService/Program.cs | 2 +- Duplicati/WindowsService/ServiceInstaller.cs | 50 ++++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Duplicati/WindowsService/Program.cs b/Duplicati/WindowsService/Program.cs index a5c3ee496..a872372d4 100644 --- a/Duplicati/WindowsService/Program.cs +++ b/Duplicati/WindowsService/Program.cs @@ -164,7 +164,7 @@ namespace Duplicati.WindowsService //The path can also include arguments for an auto-start service. For example, "d:\myshare\myservice.exe arg1 arg2". These arguments are passed to the service entry point (typically the main function). try { - ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME_AGENT, ServiceControl.DISPLAY_NAME_AGENT, ServiceControl.SERVICE_DESCRIPTION_AGENT, "\"" + selfexec + "\"" + " AGENT " + commandline); + ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME_AGENT, ServiceControl.DISPLAY_NAME_AGENT, ServiceControl.SERVICE_DESCRIPTION_AGENT, "\"" + selfexec + "\"" + " AGENT " + commandline, delayedAutoStart: true); Console.WriteLine("Duplicati agent service installation succeeded."); if (!args.Any(x => string.Equals("install-only-agent", x, StringComparison.OrdinalIgnoreCase))) { diff --git a/Duplicati/WindowsService/ServiceInstaller.cs b/Duplicati/WindowsService/ServiceInstaller.cs index 4312549bc..ac158217d 100644 --- a/Duplicati/WindowsService/ServiceInstaller.cs +++ b/Duplicati/WindowsService/ServiceInstaller.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2026, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2026, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Runtime.InteropServices; @@ -64,11 +64,11 @@ namespace Duplicati.WindowsService } [StructLayout(LayoutKind.Sequential)] - private struct SERVICE_DELAYED_AUTO_START_INFO - { - [MarshalAs(UnmanagedType.Bool)] - public bool fDelayedAutostart; - } + private struct SERVICE_DELAYED_AUTO_START_INFO + { + [MarshalAs(UnmanagedType.Bool)] + public bool fDelayedAutostart; + } private enum SC_STATUS_TYPE : int { @@ -458,7 +458,7 @@ namespace Duplicati.WindowsService GENERIC_ALL = SC_MANAGER_ALL_ACCESS, } - public static void InstallService(string ServiceName, string DisplayName, string Description, string Path, bool delayedAutoStart = false) + public static void InstallService(string ServiceName, string DisplayName, string Description, string Path, bool delayedAutoStart) { var scMgrHandle = OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS);