From ec2712962d0117dfdca7738ae20f0ce3483eab67 Mon Sep 17 00:00:00 2001 From: Francesco Saltori Date: Tue, 15 Jun 2021 08:59:45 +0200 Subject: [PATCH] Implement ServiceController.Stop(bool) overload (#52519) * Implement ServiceController.Stop(bool) overload with test * Add basic docs for new ServiceController.Stop overload * Name boolean parameter Co-authored-by: Ilya * Add a test for a valid use of ServiceController.Stop(bool) * Run tests for the new Stop(bool) overload only on .NETCoreApp * Add a test for manually stopping a service and its dependents with Stop(false) * Expose new Stop(bool) overload only for .NET Core 3.1+ * Target netcoreapp3.1 Co-authored-by: Ilya Co-authored-by: Viktor Hofer --- .../System.ServiceProcess.ServiceController.csproj | 8 +++- ....ServiceProcess.ServiceController.netcoreapp.cs | 13 ++++++ .../System.ServiceProcess.ServiceController.csproj | 5 ++- .../src/System/ServiceProcess/ServiceController.cs | 41 ++++++++++++++---- .../tests/ServiceControllerTests.cs | 2 +- .../tests/ServiceControllerTests.netcoreapp.cs | 50 ++++++++++++++++++++++ ...m.ServiceProcess.ServiceController.Tests.csproj | 3 ++ 7 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.netcoreapp.cs create mode 100644 src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.netcoreapp.cs diff --git a/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj index 456cce2..d9255a5 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.csproj @@ -1,15 +1,21 @@ - $(NetCoreAppCurrent);netstandard2.0;net461 + $(NetCoreAppCurrent);netcoreapp3.1;netstandard2.0;net461 enable + + + + + + diff --git a/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.netcoreapp.cs b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.netcoreapp.cs new file mode 100644 index 0000000..3fd806b --- /dev/null +++ b/src/libraries/System.ServiceProcess.ServiceController/ref/System.ServiceProcess.ServiceController.netcoreapp.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the https://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace System.ServiceProcess +{ + public partial class ServiceController : System.ComponentModel.Component + { + public void Stop(bool stopDependentServices) { } + } +} diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj b/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj index 31a2285..29f8589 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System.ServiceProcess.ServiceController.csproj @@ -1,7 +1,7 @@ true - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netstandard2.0-windows;netstandard2.0;net461-windows + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netcoreapp3.1-windows;netcoreapp3.1;netstandard2.0-windows;netstandard2.0;net461-windows $(NoWarn);CA2249 enable @@ -89,6 +89,9 @@ + + + diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs index 7f1ca65..64725f9 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs @@ -912,19 +912,42 @@ namespace System.ServiceProcess /// they will be stopped first. The DependentServices property lists this set /// of services. /// - public unsafe void Stop() + public void Stop() + { + Stop(stopDependentServices: true); + } + + /// + /// Stops the service and optionally any services that are dependent on this service. + /// + /// + /// If any other services depend on this one, you need to either pass true for + /// or stop them manually before calling this method. + /// + /// + /// true to stop all running dependent services together with the service; false to stop only the service. + /// +#if NETCOREAPP3_1_OR_GREATER + public +#else + private +#endif + unsafe void Stop(bool stopDependentServices) { using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_STOP); - // Before stopping this service, stop all the dependent services that are running. - // (It's OK not to cache the result of getting the DependentServices property because it caches on its own.) - for (int i = 0; i < DependentServices.Length; i++) + if (stopDependentServices) { - ServiceController currentDependent = DependentServices[i]; - currentDependent.Refresh(); - if (currentDependent.Status != ServiceControllerStatus.Stopped) + // Before stopping this service, stop all the dependent services that are running. + // (It's OK not to cache the result of getting the DependentServices property because it caches on its own.) + for (int i = 0; i < DependentServices.Length; i++) { - currentDependent.Stop(); - currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30)); + ServiceController currentDependent = DependentServices[i]; + currentDependent.Refresh(); + if (currentDependent.Status != ServiceControllerStatus.Stopped) + { + currentDependent.Stop(); + currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30)); + } } } diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs index f03e424..1f0ead8 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.cs @@ -7,7 +7,7 @@ namespace System.ServiceProcess.Tests { [OuterLoop(/* Modifies machine state */)] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Persistent issues starting test service on NETFX")] - public class ServiceControllerTests : IDisposable + public partial class ServiceControllerTests : IDisposable { private const int connectionTimeout = 30000; private readonly TestServiceProvider _testService; diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.netcoreapp.cs b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.netcoreapp.cs new file mode 100644 index 0000000..9b78dfe --- /dev/null +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/ServiceControllerTests.netcoreapp.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.ServiceProcess.Tests +{ + [OuterLoop(/* Modifies machine state */)] + public partial class ServiceControllerTests : IDisposable + { + [ConditionalFact(nameof(IsProcessElevated))] + public void Stop_FalseArg_WithDependentServices_ThrowsInvalidOperationException() + { + var controller = new ServiceController(_testService.TestServiceName); + controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout); + Assert.Throws(() => controller.Stop(stopDependentServices: false)); + } + + [ConditionalFact(nameof(IsProcessElevated))] + public void Stop_TrueArg_WithDependentServices_StopsTheServiceAndItsDependents() + { + var controller = new ServiceController(_testService.TestServiceName); + controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout); + + controller.Stop(stopDependentServices: true); + controller.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout); + + Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); + Assert.All(controller.DependentServices, service => Assert.Equal(ServiceControllerStatus.Stopped, service.Status)); + } + + [ConditionalFact(nameof(IsProcessElevated))] + public void StopTheServiceAndItsDependentsManually() + { + var controller = new ServiceController(_testService.TestServiceName); + controller.WaitForStatus(ServiceControllerStatus.Running, _testService.ControlTimeout); + + foreach (var dependentService in controller.DependentServices) + { + dependentService.Stop(stopDependentServices: false); + dependentService.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout); + } + controller.Stop(stopDependentServices: false); + controller.WaitForStatus(ServiceControllerStatus.Stopped, _testService.ControlTimeout); + + Assert.Equal(ServiceControllerStatus.Stopped, controller.Status); + Assert.All(controller.DependentServices, service => Assert.Equal(ServiceControllerStatus.Stopped, service.Status)); + } + } +} diff --git a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj index f09c00f..ca07717 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj +++ b/src/libraries/System.ServiceProcess.ServiceController/tests/System.ServiceProcess.ServiceController.Tests.csproj @@ -9,6 +9,9 @@ + + + -- 2.7.4