From 2a817fe0931ca7c0ef8b72e6bc227deaff2ad166 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 27 Aug 2024 14:08:52 +0200 Subject: [PATCH] Implemented support for setting the Windows Service description --- Duplicati/WindowsService/Program.cs | 4 +- Duplicati/WindowsService/ServiceControl.cs | 57 +++++++------- Duplicati/WindowsService/ServiceInstaller.cs | 79 ++++++++++++-------- 3 files changed, 80 insertions(+), 60 deletions(-) diff --git a/Duplicati/WindowsService/Program.cs b/Duplicati/WindowsService/Program.cs index 593ecef38..a13976d94 100644 --- a/Duplicati/WindowsService/Program.cs +++ b/Duplicati/WindowsService/Program.cs @@ -83,7 +83,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, "\"" + selfexec + "\"" + " " + commandline); + ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME, ServiceControl.DISPLAY_NAME, ServiceControl.SERVICE_DESCRIPTION, "\"" + selfexec + "\"" + " " + commandline); Console.WriteLine("Duplicati service installation succeeded."); } catch (Exception ex) @@ -95,7 +95,7 @@ namespace Duplicati.WindowsService } else { - ServiceBase.Run(new ServiceBase[] { new ServiceControl(args) }); + ServiceBase.Run([new ServiceControl(args)]); } return 0; diff --git a/Duplicati/WindowsService/ServiceControl.cs b/Duplicati/WindowsService/ServiceControl.cs index 51a99273b..833166fb6 100644 --- a/Duplicati/WindowsService/ServiceControl.cs +++ b/Duplicati/WindowsService/ServiceControl.cs @@ -1,39 +1,39 @@ -// Copyright (C) 2024, 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. +// Copyright (C) 2024, 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 Duplicati.Service; using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.Versioning; namespace Duplicati.WindowsService { + [SupportedOSPlatform("windows")] public class ServiceControl : System.ServiceProcess.ServiceBase { private const string LOG_SOURCE = "Duplicati"; private const string LOG_NAME = "Application"; public const string SERVICE_NAME = "Duplicati"; public const string DISPLAY_NAME = "Duplicati service"; + public const string SERVICE_DESCRIPTION = "Duplicati running as a Windows Service"; private readonly System.Diagnostics.EventLog m_eventLog; @@ -46,12 +46,13 @@ namespace Duplicati.WindowsService { this.ServiceName = SERVICE_NAME; - m_eventLog = new System.Diagnostics.EventLog(); if (!System.Diagnostics.EventLog.SourceExists(LOG_SOURCE)) System.Diagnostics.EventLog.CreateEventSource(LOG_SOURCE, LOG_NAME); - - m_eventLog.Source = LOG_SOURCE; - m_eventLog.Log = LOG_NAME; + m_eventLog = new System.Diagnostics.EventLog + { + Source = LOG_SOURCE, + Log = LOG_NAME + }; m_verbose_messages = args != null && args.Any(x => string.Equals("--debug-service", x, StringComparison.OrdinalIgnoreCase)); m_cmdargs = (args ?? new string[0]).Where(x => !string.Equals("--debug-service", x, StringComparison.OrdinalIgnoreCase)).ToArray(); @@ -78,7 +79,7 @@ namespace Duplicati.WindowsService if (m_verbose_messages) m_eventLog.WriteEntry("Starting..."); - lock(m_lock) + lock (m_lock) if (m_runner == null) { if (m_verbose_messages) diff --git a/Duplicati/WindowsService/ServiceInstaller.cs b/Duplicati/WindowsService/ServiceInstaller.cs index 74fd171f1..6934388e4 100644 --- a/Duplicati/WindowsService/ServiceInstaller.cs +++ b/Duplicati/WindowsService/ServiceInstaller.cs @@ -1,28 +1,26 @@ -// Copyright (C) 2024, 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. +// Copyright (C) 2024, 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.Collections.Generic; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Text; namespace Duplicati.WindowsService { @@ -48,6 +46,18 @@ namespace Duplicati.WindowsService [return: MarshalAs(UnmanagedType.Bool)] private static extern bool DeleteService(IntPtr hService); + private const int SERVICE_CONFIG_DESCRIPTION = 0x01; + + [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); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct SERVICE_DESCRIPTION + { + public string lpDescription; + } + /// /// Access to the service. Before granting the requested access, the /// system checks the access token of the calling process. @@ -391,21 +401,30 @@ namespace Duplicati.WindowsService GENERIC_ALL = SC_MANAGER_ALL_ACCESS, } - public static void InstallService(string ServiceName, string DisplayName, string Path) + public static void InstallService(string ServiceName, string DisplayName, string Description, string Path) { var scMgrHandle = OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); try { if (scMgrHandle == IntPtr.Zero) - throw new Exception($"Win32 error { Marshal.GetLastWin32Error().ToString() } during install service (OpenSCManager)"); - + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during install service (OpenSCManager)"); + var serviceHandle = CreateService(scMgrHandle, ServiceName, DisplayName, (uint)SERVICE_ACCESS.SERVICE_ALL_ACCESS, (uint)SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS, (uint)SERVICE_START.SERVICE_AUTO_START, (uint)SERVICE_ERROR.SERVICE_ERROR_NORMAL, Path, null, null, null, null, null); if (serviceHandle == IntPtr.Zero) - throw new Exception($"Win32 error { Marshal.GetLastWin32Error().ToString() } during install service (CreateService)"); - + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during install service (CreateService)"); + + 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()}"); + CloseServiceHandle(serviceHandle); } finally @@ -421,17 +440,17 @@ namespace Duplicati.WindowsService try { if (scMgrHandle == IntPtr.Zero) - throw new Exception($"Win32 error { Marshal.GetLastWin32Error().ToString() } during delete service (OpenSCManager)"); + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during delete service (OpenSCManager)"); var serviceHandle = OpenService(scMgrHandle, ServiceName, (uint)SERVICE_ACCESS.SERVICE_ALL_ACCESS); if (serviceHandle == IntPtr.Zero) - throw new Exception($"Win32 error { Marshal.GetLastWin32Error().ToString() } during delete service (OpenService)"); + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during delete service (OpenService)"); try { if (DeleteService(serviceHandle) == false) - throw new Exception($"Win32 error { Marshal.GetLastWin32Error().ToString() } during delete service (DeleteService)"); + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during delete service (DeleteService)"); } finally {