Merge pull request #7045 from JamBalaya56562/fix/windows-service-delayed-start

Delay Windows service startup to avoid boot timeout
This commit is contained in:
Kenneth Skovhede
2026-07-13 08:41:26 +02:00
committed by GitHub
2 changed files with 61 additions and 29 deletions
+2 -2
View File
@@ -113,7 +113,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)))
{
@@ -166,7 +166,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)))
{
+59 -27
View File
@@ -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;
@@ -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)
{
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
{