diff --git a/build.psm1 b/build.psm1 index e060ee1437e..a2d807d4ab9 100644 --- a/build.psm1 +++ b/build.psm1 @@ -957,6 +957,7 @@ function Publish-PSTestTools { $tools = @( @{Path="${PSScriptRoot}/test/tools/TestExe";Output="testexe"} @{Path="${PSScriptRoot}/test/tools/WebListener";Output="WebListener"} + @{Path="${PSScriptRoot}/test/tools/TestService";Output="TestService"} ) $Options = Get-PSOptions -DefaultToNew diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 54e117cdd7b..e7be3d09b0b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1532,6 +1532,15 @@ public string Status } internal string serviceStatus = null; + /// + /// The following is the definition of the input parameter "Force". + /// This parameter is useful only when parameter "Stop" is enabled. + /// If "Force" is enabled, it will also stop the dependent services. + /// If not, it will send an error when this service has dependent ones. + /// + [Parameter] + public SwitchParameter Force { get; set; } + /// /// This is not a parameter for this cmdlet. /// @@ -1779,24 +1788,17 @@ protected override void ProcessRecord() { if (!service.Status.Equals(ServiceControllerStatus.Stopped)) { - //check for the dependent services as set-service dont have force parameter + // Check for the dependent services as set-service dont have force parameter ServiceController[] dependentServices = service.DependentServices; - if ((dependentServices != null) && (dependentServices.Length > 0)) + if ((!Force) && (dependentServices != null) && (dependentServices.Length > 0)) { WriteNonTerminatingError(service, null, "ServiceHasDependentServicesNoForce", ServiceResources.ServiceHasDependentServicesNoForce, ErrorCategory.InvalidOperation); return; } - ServiceController[] servicedependedon = service.ServicesDependedOn; - - if ((servicedependedon != null) && (servicedependedon.Length > 0)) - { - WriteNonTerminatingError(service, null, "ServiceIsDependentOnNoForce", ServiceResources.ServiceIsDependentOnNoForce, ErrorCategory.InvalidOperation); - return; - } // Stop service, pass 'true' to the force parameter as we have already checked for the dependent services. - DoStopService(service, force: true, waitForServiceToStop: true); + DoStopService(service, Force, waitForServiceToStop: true); } } else if (Status.Equals("Paused", StringComparison.CurrentCultureIgnoreCase)) diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index 6218714a18c..7f8538e5d02 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -132,9 +132,6 @@ Cannot stop service '{1} ({0})' because it has dependent services. - - Cannot stop service '{1} ({0})' because it is dependent on other services. - Service '{1} ({0})' cannot be stopped due to the following error: {2} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index c2920d61235..5020e3dce45 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -12,12 +12,28 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW net user $userName $testPass /add > $null $password = ConvertTo-SecureString $testPass -AsPlainText -Force $creds = [pscredential]::new(".\$userName", $password) + + $testservicename1 = "testservice1" + $testservicename2 = "testservice2" + $svcbinaryname = "TestService" + $svccmd = Get-Command $svcbinaryname + $svccmd | Should -Not -BeNullOrEmpty + $svcfullpath = $svccmd.Path + $testservice1 = New-Service -BinaryPathName $svcfullpath -Name $testservicename1 + $testservice1 | Should -Not -BeNullOrEmpty + $testservice2 = New-Service -BinaryPathName $svcfullpath -Name $testservicename2 -DependsOn $testservicename1 + $testservice2 | Should -Not -BeNullOrEmpty } } AfterAll { $global:PSDefaultParameterValues = $originalDefaultParameterValues if ($IsWindows) { net user $userName /delete > $null + + Stop-Service $testservicename2 + Stop-Service $testservicename1 + Remove-Service $testservicename2 + Remove-Service $testservicename1 } } @@ -329,4 +345,32 @@ Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnW } { & $cmdlet @parameters } | Should -Throw -ErrorId $errorid } + + Context "Set-Service test cases on the services with dependent relationship" { + BeforeEach { + { Set-Service -Status Running $testservicename2 } | Should -Not -Throw + (Get-Service $testservicename1).Status | Should -BeExactly "Running" + (Get-Service $testservicename2).Status | Should -BeExactly "Running" + } + + It "Set-Service can stop a service with dependency" { + $script = { Set-Service -Status Stopped $testservicename2 -ErrorAction Stop } + { & $script } | Should -Not -Throw + (Get-Service $testservicename2).Status | Should -BeExactly "Stopped" + } + + It "Set-Service cannot stop a service with running dependent service" { + $script = { Set-Service -Status Stopped $testservicename1 -ErrorAction Stop } + { & $script } | Should -Throw + (Get-Service $testservicename1).Status | Should -BeExactly "Running" + (Get-Service $testservicename2).Status | Should -BeExactly "Running" + } + + It "Set-Service can stop a service with running dependent service by parameter -Force" { + $script = { Set-Service -Status Stopped -Force $testservicename1 -ErrorAction Stop } + { & $script } | Should -Not -Throw + (Get-Service $testservicename1).Status | Should -BeExactly "Stopped" + (Get-Service $testservicename2).Status | Should -BeExactly "Stopped" + } + } } diff --git a/test/tools/OpenCover/OpenCover.psm1 b/test/tools/OpenCover/OpenCover.psm1 index 3ff2265742e..78502650761 100644 --- a/test/tools/OpenCover/OpenCover.psm1 +++ b/test/tools/OpenCover/OpenCover.psm1 @@ -667,7 +667,8 @@ function Invoke-OpenCover $updatedEnvPath = "${PowerShellExeDirectory}\Modules;$TestToolsModulesPath" $testToolsExePath = (Resolve-Path(Join-Path $TestPath -ChildPath "..\tools\TestExe\bin")).Path - $updatedProcessEnvPath = "${testToolsExePath};${env:PATH}" + $testServiceExePath = (Resolve-Path(Join-Path $TestPath -ChildPath "..\tools\TestService\bin")).Path + $updatedProcessEnvPath = "${testServiceExePath};${testToolsExePath};${env:PATH}" $startupArgs = "Set-ExecutionPolicy Bypass -Force -Scope Process; `$env:PSModulePath = '${updatedEnvPath}'; `$env:Path = '${updatedProcessEnvPath}';" $targetArgs = "${startupArgs}", "Invoke-Pester","${TestPath}","-OutputFormat $PesterLogFormat" diff --git a/test/tools/TestService/Program.cs b/test/tools/TestService/Program.cs new file mode 100644 index 00000000000..f3a5c5d53b4 --- /dev/null +++ b/test/tools/TestService/Program.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System.ServiceProcess; + +namespace TestService +{ + static class Program + { + static void Main() + { + ServiceBase[] ServicesToRun; + ServicesToRun = new ServiceBase[] + { + new Service1() + }; + ServiceBase.Run(ServicesToRun); + } + } +} diff --git a/test/tools/TestService/Service1.Designer.cs b/test/tools/TestService/Service1.Designer.cs new file mode 100644 index 00000000000..b8210d468c9 --- /dev/null +++ b/test/tools/TestService/Service1.Designer.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +namespace TestService +{ + partial class Service1 + { + private System.ComponentModel.IContainer components = null; + + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + this.ServiceName = "Service1"; + } + } +} diff --git a/test/tools/TestService/Service1.cs b/test/tools/TestService/Service1.cs new file mode 100644 index 00000000000..36f680fa5f9 --- /dev/null +++ b/test/tools/TestService/Service1.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System.ServiceProcess; + +namespace TestService +{ + public partial class Service1 : ServiceBase + { + public Service1() + { + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + } + + protected override void OnStop() + { + } + } +} diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj new file mode 100644 index 00000000000..2544875784f --- /dev/null +++ b/test/tools/TestService/TestService.csproj @@ -0,0 +1,16 @@ + + + + + + Very tiny windows service to do service testing + TestService + Exe + win7-x86;win7-x64 + + + + + + +